Use Moveable package for BlockResizer

it's bigger, but solves some bugs I don't have time to track down
plus it will do clipping and other stuff

closes RCX-2259
flag=block_editor

test plan:
  - resize stuff
  > see that it works even better, and with less (of our) code

Change-Id: Iedb6f3c407a5b687b8e4dd85804436ccb12dcd4c
Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/355618
Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com>
Reviewed-by: Murilo Paiva <murilo.paiva@instructure.com>
QA-Review: Eric Saupe <eric.saupe@instructure.com>
Product-Review: Ed Schiebel <eschiebel@instructure.com>
This commit is contained in:
Ed Schiebel 2024-08-21 16:30:59 -06:00
parent 93e52cd926
commit 2123efaaf5
7 changed files with 253 additions and 312 deletions

View File

@ -299,15 +299,16 @@ describe "Block Editor", :ignore_js_errors do
it "adjusts the width when the height is changed" do
get "/courses/#{@course.id}/pages/#{@block_page.url}/edit"
wait_for_block_editor
f(".block.image-block").click # select the section
f(".block.image-block").click # select the block
expect(block_resize_handle_se).to be_displayed
expect(f(".block.image-block").size.width).to eq(100)
expect(f(".block.image-block").size.height).to eq(50)
drag_and_drop_element_by(block_resize_handle_se, 10, 50)
expect(f(".block.image-block").size.width).to eq(200)
expect(f(".block.image-block").size.height).to eq(100)
f("body").send_keys(:alt, :shift, :arrow_right)
expect(f(".block.image-block").size.width).to eq(110)
expect(f(".block.image-block").size.height).to eq(55)
end
end
end

View File

@ -79,7 +79,7 @@ module BlockEditorPage
# Blocks
def block_resize_handle_se
f(".block-resizer.se")
f(".block-resizer .moveable-se")
end
def block_toolbar

View File

@ -7,6 +7,7 @@
"dependencies": {
"@craftjs/core": "^0.2.10",
"@instructure/ui-color-picker": "^8",
"react-moveable": "0.56.0",
"styled-components": ">= 4",
"react-contenteditable": "^3.3.7",
"re-resizable": "6.9.16",

View File

@ -18,55 +18,14 @@
import React, {useCallback, useEffect, useRef, useState} from 'react'
import {useNode, type Node} from '@craftjs/core'
import {getToolbarPos as getToolbarPosUtil} from '../../utils/renderNodeHelpers'
import {getAspectRatio} from '../../utils/size'
import Moveable, {type OnResize} from 'react-moveable'
const CORNER_OFFSET = 5 // half the corder box width
type Rect = {
top: number
left: number
type Sz = {
width: number
height: number
}
type Corner = 'nw' | 'ne' | 'se' | 'sw'
type CoordVal = {
left: number
top: number
}
export const getNewSz = (
corner: string,
currRect: Rect,
dragStart: {x: number; y: number},
event: DragEvent
) => {
let width = 0
switch (corner) {
case 'nw':
case 'sw':
width = currRect.width - (event.clientX - dragStart.x)
break
case 'ne':
case 'se':
width = currRect.width + (event.clientX - dragStart.x)
break
}
let height = 0
switch (corner) {
case 'nw':
case 'ne':
height = currRect.height - (event.clientY - dragStart.y)
break
case 'sw':
case 'se':
height = currRect.height + (event.clientY - dragStart.y)
break
}
return {width, height}
}
type BlockResizeProps = {
mountPoint: HTMLElement
}
@ -82,19 +41,20 @@ const BlockResizer = ({mountPoint}: BlockResizeProps) => {
node: n,
}
})
const [nwRef, setnwRef] = useState<HTMLDivElement | null>(null)
const [neRef, setneRef] = useState<HTMLDivElement | null>(null)
const [seRef, setseRef] = useState<HTMLDivElement | null>(null)
const [swRef, setswRef] = useState<HTMLDivElement | null>(null)
const [currRect, setCurrRect] = useState<Rect>({left: 0, top: 0, width: 0, height: 0})
const [currSz, setCurrSz] = useState<Sz>(() => {
const {width, height} = node.data.props
if (width) {
return {width, height}
} else if (node.dom) {
const rect = node.dom.getBoundingClientRect()
return {width: rect.width, height: rect.height}
} else {
return {width: 0, height: 0}
}
})
const dragHandleStart = useRef({x: 0, y: 0})
const isDragging = useRef(false)
const getToolbarPos = useCallback(() => {
return getToolbarPosUtil(node.dom as HTMLElement, mountPoint, null, false)
}, [mountPoint, node.dom])
const handleResizeKeys = useCallback(
event => {
if (!node.dom) return
@ -107,8 +67,8 @@ const BlockResizer = ({mountPoint}: BlockResizeProps) => {
}
const step = event.shiftKey ? 10 : 1
let newWidth = currRect.width
let newHeight = currRect.height
let newWidth = currSz.width
let newHeight = currSz.height
switch (event.key) {
case 'ArrowRight':
newWidth += step * dir
@ -127,8 +87,8 @@ const BlockResizer = ({mountPoint}: BlockResizeProps) => {
newWidth = Math.max(newWidth, 24)
newHeight = Math.max(newHeight, 24)
if (maintainAspectRatio) {
const aspectRatio = getAspectRatio(currRect.width, currRect.height)
if (newHeight !== currRect.height) {
const aspectRatio = getAspectRatio(currSz.width, currSz.height)
if (newHeight !== currSz.height) {
newWidth = newHeight * aspectRatio
} else {
newHeight = newWidth / aspectRatio
@ -137,15 +97,15 @@ const BlockResizer = ({mountPoint}: BlockResizeProps) => {
const myblock = node.dom as HTMLElement
myblock.style.width = `${newWidth}px`
myblock.style.height = `${newHeight}px`
const {top, left} = getToolbarPos()
setCurrRect({left, top, width: newWidth, height: newHeight})
setCurrSz({width: newWidth, height: newHeight})
setProp((props: any) => {
props.width = newWidth
props.height = newHeight
})
}
},
[currRect.height, currRect.width, getToolbarPos, maintainAspectRatio, node.dom, setProp]
[currSz.height, currSz.width, maintainAspectRatio, node.dom, setProp]
)
useEffect(() => {
@ -155,249 +115,57 @@ const BlockResizer = ({mountPoint}: BlockResizeProps) => {
}
}, [handleResizeKeys])
useEffect(() => {
if (node.dom && (currRect.width === 0 || currRect.height === 0)) {
const {top, left} = getToolbarPosUtil(node.dom as HTMLElement, mountPoint, null, false)
const {width, height} = (node.dom as HTMLElement).getBoundingClientRect()
setCurrRect({left, top, width, height})
}
}, [currRect.height, currRect.width, mountPoint, node.dom])
useEffect(() => {
if (
!isDragging.current &&
'width' in node.data.props &&
(node.data.props.width !== currRect.width || node.data.props.height !== currRect.height)
(node.data.props.width !== currSz.width || node.data.props.height !== currSz.height)
) {
// assume height is there too then
const {top, left} = getToolbarPosUtil(node.dom as HTMLElement, mountPoint, null, false)
setCurrRect({
left,
top,
setCurrSz({
width: node.data.props.width,
height: node.data.props.height,
})
}
}, [
currRect.height,
currRect.left,
currRect.top,
currRect.width,
mountPoint,
node.data.props,
node.dom,
])
}, [currSz.height, currSz.width, node.data.props.height, node.data.props.width, node.data.props])
const handleDrag = useCallback(
(event: DragEvent) => {
const myblock = node.dom as HTMLElement
const corner = (event.currentTarget as HTMLElement).dataset.corner
if (!corner) return
let {width, height} = getNewSz(corner, currRect, dragHandleStart.current, event)
if (width > 0 && height > 0) {
width = Math.max(width, 24)
height = Math.max(height, 24)
if (maintainAspectRatio) {
const aspectRatio = getAspectRatio(currRect.width, currRect.height)
if (aspectRatio > 1) {
width = height * aspectRatio
} else {
height = width / aspectRatio
}
}
myblock.style.width = `${width}px`
myblock.style.height = `${height}px`
const {top, left} = getToolbarPos()
setCurrRect({left, top, width, height})
}
},
[currRect, getToolbarPos, maintainAspectRatio, node.dom]
)
const handleDragEnd = useCallback(
(event: Event) => {
event.currentTarget?.removeEventListener('drag', handleDrag)
event.currentTarget?.removeEventListener('dragend', handleDragEnd)
const {width, height} = (node.dom as HTMLElement).getBoundingClientRect()
const {top, left} = getToolbarPos()
setProp((props: any) => {
props.width = width
props.height = height
})
setCurrRect({left, top, width, height})
isDragging.current = false
const myToolbar = document.querySelector('.block-editor-editor .block-toolbar') as HTMLElement
if (myToolbar) {
myToolbar.style.visibility = 'initial'
}
},
[getToolbarPos, handleDrag, node.dom, setProp]
)
const handleDragStart = useCallback(
(event: DragEvent) => {
event.stopPropagation()
if (!event.dataTransfer) return
// @ts-expect-error
event.dataTransfer.mozShowFailAnimation = false // see https://github.com/whatwg/html/issues/10039
event.dataTransfer.setDragImage(event.target as HTMLElement, 0, 0)
const target = event.currentTarget as HTMLElement
dragHandleStart.current = {x: event.clientX, y: event.clientY}
target.addEventListener('drag', handleDrag)
target.addEventListener('dragend', handleDragEnd)
const handleResizeStart = useCallback(() => {
isDragging.current = true
const myToolbar = document.querySelector('.block-editor-editor .block-toolbar') as HTMLElement
const myToolbar = mountPoint.querySelector('.block-editor-editor .block-toolbar') as HTMLElement
if (myToolbar) {
myToolbar.style.visibility = 'hidden'
}
},
[handleDrag, handleDragEnd]
)
}, [mountPoint])
useEffect(() => {
if (nwRef && neRef && seRef && swRef) {
nwRef.addEventListener('dragstart', handleDragStart)
neRef.addEventListener('dragstart', handleDragStart)
seRef.addEventListener('dragstart', handleDragStart)
swRef.addEventListener('dragstart', handleDragStart)
}
return () => {
nwRef?.removeEventListener('dragstart', handleDragStart)
neRef?.removeEventListener('dragstart', handleDragStart)
seRef?.removeEventListener('dragstart', handleDragStart)
swRef?.removeEventListener('dragstart', handleDragStart)
}
}, [handleDragEnd, handleDragStart, neRef, nwRef, seRef, swRef])
const handleResize = useCallback(({target, width, height, delta}: OnResize) => {
delta[0] && (target!.style.width = `${width}px`)
delta[1] && (target!.style.height = `${height}px`)
setCurrSz({width, height})
}, [])
const getCoord = (corner: Corner): CoordVal => {
switch (corner) {
case 'nw':
return {left: currRect.left, top: currRect.top}
case 'ne':
return {
left: currRect.left + currRect.width,
top: currRect.top,
const handleResizeEnd = useCallback(() => {
isDragging.current = false
setProp((props: any) => {
props.width = currSz.width
props.height = currSz.height
})
const myToolbar = mountPoint.querySelector('.block-editor-editor .block-toolbar') as HTMLElement
if (myToolbar) {
myToolbar.style.visibility = 'visible'
}
case 'se':
return {
left: currRect.left + currRect.width,
top: currRect.top + currRect.height,
}
case 'sw':
return {
left: currRect.left,
top: currRect.top + currRect.height,
}
}
}
const renderCorners = () => {
const nw = getCoord('nw')
const ne = getCoord('ne')
const se = getCoord('se')
const sw = getCoord('sw')
}, [currSz.height, currSz.width, mountPoint, setProp])
return (
<>
<div
ref={el => setnwRef(el)}
data-corner="nw"
className="block-resizer nw"
draggable="true"
style={{
left: `${nw.left - CORNER_OFFSET}px`,
top: `${nw.top - CORNER_OFFSET}px`,
}}
<Moveable
className="block-resizer"
target={node.dom}
resizable={true}
throttleSize={1}
keepRatio={maintainAspectRatio}
useResizeObserver={true}
onResizeStart={handleResizeStart}
onResize={handleResize}
onResizeEnd={handleResizeEnd}
/>
<div
ref={el => setneRef(el)}
data-corner="ne"
className="block-resizer ne"
draggable="true"
style={{
left: `${ne.left - CORNER_OFFSET}px`,
top: `${ne.top - CORNER_OFFSET}px`,
}}
/>
<div
ref={el => setseRef(el)}
data-corner="se"
className="block-resizer se"
draggable="true"
style={{
left: `${se.left - CORNER_OFFSET}px`,
top: `${se.top - CORNER_OFFSET}px`,
}}
/>
<div
ref={el => setswRef(el)}
data-corner="sw"
className="block-resizer sw"
draggable="true"
style={{
left: `${sw.left - CORNER_OFFSET}px`,
top: `${sw.top - CORNER_OFFSET}px`,
}}
/>
</>
)
}
const renderEdges = () => {
const nw = getCoord('nw')
const ne = getCoord('ne')
const sw = getCoord('sw')
return (
<>
<div
className="block-resizer edge n"
style={{
top: `${nw.top}px`,
left: `${nw.left}px`,
width: `${currRect.width}px`,
height: '1px',
}}
/>
<div
className="block-resizer edge e"
style={{
top: `${ne.top}px`,
left: `${ne.left}px`,
width: '1px',
height: `${currRect.height}px`,
}}
/>
<div
className="block-resizer edge s"
style={{
top: `${sw.top}px`,
left: `${sw.left}px`,
width: `${currRect.width}px`,
height: '1px',
}}
/>
<div
className="block-resizer edge w"
style={{
top: `${nw.top}px`,
left: `${nw.left}px`,
width: '1px',
height: `${currRect.height}px`,
}}
/>
</>
)
}
return (
<>
{renderEdges()}
{renderCorners()}
</>
)
}

View File

@ -49,6 +49,8 @@ jest.mock('@craftjs/core', () => {
describe('BlockResizer', () => {
beforeAll(() => {
nodeDomNode.style.width = '100px'
nodeDomNode.style.height = '125px'
document.body.appendChild(nodeDomNode)
const mountNode = document.createElement('div')
mountNode.id = 'mountNode'
@ -58,17 +60,25 @@ describe('BlockResizer', () => {
it('renders', () => {
const mountNode = document.getElementById('mountNode') as HTMLElement
render(<BlockResizer mountPoint={mountNode} />)
expect(document.querySelector('.block-resizer.nw')).toBeInTheDocument()
expect(document.querySelector('.block-resizer.ne')).toBeInTheDocument()
expect(document.querySelector('.block-resizer.sw')).toBeInTheDocument()
expect(document.querySelector('.block-resizer.se')).toBeInTheDocument()
expect(document.querySelector('.block-resizer.edge.n')).toBeInTheDocument()
expect(document.querySelector('.block-resizer.edge.e')).toBeInTheDocument()
expect(document.querySelector('.block-resizer.edge.s')).toBeInTheDocument()
expect(document.querySelector('.block-resizer.edge.w')).toBeInTheDocument()
expect(document.querySelector('.block-resizer.edge.n')).toHaveStyle({width: '100px'})
expect(document.querySelector('.block-resizer.edge.e')).toHaveStyle({height: '125px'})
expect(document.querySelector('.block-resizer.edge.s')).toHaveStyle({width: '100px'})
expect(document.querySelector('.block-resizer.edge.w')).toHaveStyle({height: '125px'})
expect(document.querySelector('.block-resizer .moveable-nw')).toBeInTheDocument()
expect(document.querySelector('.block-resizer .moveable-ne')).toBeInTheDocument()
expect(document.querySelector('.block-resizer .moveable-sw')).toBeInTheDocument()
expect(document.querySelector('.block-resizer .moveable-se')).toBeInTheDocument()
expect(document.querySelector('.block-resizer .moveable-n')).toBeInTheDocument()
expect(document.querySelector('.block-resizer .moveable-s')).toBeInTheDocument()
expect(document.querySelector('.block-resizer .moveable-e')).toBeInTheDocument()
expect(document.querySelector('.block-resizer .moveable-w')).toBeInTheDocument()
const edges = document.querySelectorAll('.block-resizer .moveable-line')
expect(edges).toHaveLength(4)
expect(edges[0]).toHaveStyle({width: '101px'})
expect(edges[1]).toHaveStyle({width: '126px'})
expect(edges[2]).toHaveStyle({width: '101px'})
expect(edges[3]).toHaveStyle({width: '126px'})
expect(edges[0]).toHaveStyle({height: '1px'})
expect(edges[1]).toHaveStyle({height: '1px'})
expect(edges[2]).toHaveStyle({height: '1px'})
expect(edges[3]).toHaveStyle({height: '1px'})
})
})

View File

@ -16,6 +16,16 @@
* with this program. If not, see <http://www.gnu.org/licenses/>.
*/
.block-resizer .moveable-control.moveable-origin {
display: none;
}
/* per the Moveable documentation, we need !important */
.block-resizer .moveable-control.moveable-direction {
border-radius: 0;
border: 2px solid var(--moveable-color);
background-color: #fff;
}
.block-editor-editor {
--active-border-color: var(--ic-brand-primary); /*rgba(38,128,235,1); */
--hover-border-color: #ababab;

151
yarn.lock
View File

@ -1299,6 +1299,13 @@
dependencies:
statuses "^2.0.1"
"@cfcs/core@^0.0.6":
version "0.0.6"
resolved "https://registry.yarnpkg.com/@cfcs/core/-/core-0.0.6.tgz#9f8499dcd2ad29fd96d8fa72055411cd4a249121"
integrity sha512-FxfJMwoLB8MEMConeXUCqtMGqxdtePQxRBOiGip9ULcYYam3WfCgoY6xdnMaSkYvRvmosp5iuG+TiPofm65+Pw==
dependencies:
"@egjs/component" "^3.0.2"
"@cnakazawa/watch@^1.0.3":
version "1.0.4"
resolved "https://registry.yarnpkg.com/@cnakazawa/watch/-/watch-1.0.4.tgz#f864ae85004d0fcab6f50be9141c4da368d1656a"
@ -1340,11 +1347,38 @@
dependencies:
"@jridgewell/trace-mapping" "0.3.9"
"@daybrush/utils@^1.1.1", "@daybrush/utils@^1.13.0", "@daybrush/utils@^1.4.0", "@daybrush/utils@^1.6.0", "@daybrush/utils@^1.7.1":
version "1.13.0"
resolved "https://registry.yarnpkg.com/@daybrush/utils/-/utils-1.13.0.tgz#ea70a60864130da476406fdd1d465e3068aea0ff"
integrity sha512-ALK12C6SQNNHw1enXK+UO8bdyQ+jaWNQ1Af7Z3FNxeAwjYhQT7do+TRE4RASAJ3ObaS2+TJ7TXR3oz2Gzbw0PQ==
"@discoveryjs/json-ext@^0.5.0", "@discoveryjs/json-ext@^0.5.3", "@discoveryjs/json-ext@^0.5.7":
version "0.5.7"
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70"
integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==
"@egjs/agent@^2.2.1":
version "2.4.3"
resolved "https://registry.yarnpkg.com/@egjs/agent/-/agent-2.4.3.tgz#6d44e2fb1ff7bab242c07f82732fe60305ac6f06"
integrity sha512-XvksSENe8wPeFlEVouvrOhKdx8HMniJ3by7sro2uPF3M6QqWwjzVcmvwoPtdjiX8O1lfRoLhQMp1a7NGlVTdIA==
"@egjs/children-differ@^1.0.1":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@egjs/children-differ/-/children-differ-1.0.1.tgz#5465fa80671d5ca3564ebe912f48b05b3e8a14fd"
integrity sha512-DRvyqMf+CPCOzAopQKHtW+X8iN6Hy6SFol+/7zCUiE5y4P/OB8JP8FtU4NxtZwtafvSL4faD5KoQYPj3JHzPFQ==
dependencies:
"@egjs/list-differ" "^1.0.0"
"@egjs/component@^3.0.2":
version "3.0.5"
resolved "https://registry.yarnpkg.com/@egjs/component/-/component-3.0.5.tgz#2dc86e835d5dc5055cdf46c7cd794eb45330e1b6"
integrity sha512-cLcGizTrrUNA2EYE3MBmEDt2tQv1joVP1Q3oDisZ5nw0MZDx2kcgEXM+/kZpfa/PAkFvYVhRUZwytIQWoN3V/w==
"@egjs/list-differ@^1.0.0":
version "1.0.1"
resolved "https://registry.yarnpkg.com/@egjs/list-differ/-/list-differ-1.0.1.tgz#5772b0f8b87973bb67827f6c7d7df8d7f64a22eb"
integrity sha512-OTFTDQcWS+1ZREOdCWuk5hCBgYO4OsD30lXcOCyVOAjXMhgL5rBRDnt/otb6Nz8CzU0L/igdcaQBDLWc4t9gvg==
"@emotion/babel-plugin@^11.11.0":
version "11.11.0"
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.11.0.tgz#c2d872b6a7767a9d176d007f5b31f7d504bb5d6c"
@ -5277,6 +5311,28 @@
dependencies:
any-observable "^0.3.0"
"@scena/dragscroll@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@scena/dragscroll/-/dragscroll-1.4.0.tgz#220b2430c16119cd3e70044ee533a5b9a43cffd7"
integrity sha512-3O8daaZD9VXA9CP3dra6xcgt/qrm0mg0xJCwiX6druCteQ9FFsXffkF8PrqxY4Z4VJ58fFKEa0RlKqbsi/XnRA==
dependencies:
"@daybrush/utils" "^1.6.0"
"@scena/event-emitter" "^1.0.2"
"@scena/event-emitter@^1.0.2", "@scena/event-emitter@^1.0.5":
version "1.0.5"
resolved "https://registry.yarnpkg.com/@scena/event-emitter/-/event-emitter-1.0.5.tgz#047e3acef93cf238d7ce3a8cc5a12ec6bd9c3bb1"
integrity sha512-AzY4OTb0+7ynefmWFQ6hxDdk0CySAq/D4efljfhtRHCOP7MBF9zUfhKG3TJiroVjASqVgkRJFdenS8ArZo6Olg==
dependencies:
"@daybrush/utils" "^1.1.1"
"@scena/matrix@^1.0.0", "@scena/matrix@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@scena/matrix/-/matrix-1.1.1.tgz#5297f71825c72e2c2c8f802f924f482ed200c43c"
integrity sha512-JVKBhN0tm2Srl+Yt+Ywqu0oLgLcdemDQlD1OxmN9jaCTwaFPZ7tY8n6dhVgMEaR9qcR7r+kAlMXnSfNyYdE+Vg==
dependencies:
"@daybrush/utils" "^1.4.0"
"@sentry-internal/tracing@7.81.0":
version "7.81.0"
resolved "https://registry.yarnpkg.com/@sentry-internal/tracing/-/tracing-7.81.0.tgz#bf63b817f8471462432fcfe466d3e021e84aef1f"
@ -11167,6 +11223,21 @@ css-select@^4.1.3:
domutils "^2.6.0"
nth-check "^2.0.0"
css-styled@^1.0.8, css-styled@~1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/css-styled/-/css-styled-1.0.8.tgz#c9c05dc4abdef5571033090bfb8cfc5e19429974"
integrity sha512-tCpP7kLRI8dI95rCh3Syl7I+v7PP+2JYOzWkl0bUEoSbJM+u8ITbutjlQVf0NC2/g4ULROJPi16sfwDIO8/84g==
dependencies:
"@daybrush/utils" "^1.13.0"
css-to-mat@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/css-to-mat/-/css-to-mat-1.1.1.tgz#0dd10dcf9ec17df15708c8ff07a74fbd0b9a3fe5"
integrity sha512-kvpxFYZb27jRd2vium35G7q5XZ2WJ9rWjDUMNT36M3Hc41qCrLXFM5iEKMGXcrPsKfXEN+8l/riB4QzwwwiEyQ==
dependencies:
"@daybrush/utils" "^1.13.0"
"@scena/matrix" "^1.0.0"
css-to-react-native@3.2.0:
version "3.2.0"
resolved "https://registry.yarnpkg.com/css-to-react-native/-/css-to-react-native-3.2.0.tgz#cdd8099f71024e149e4f6fe17a7d46ecd55f1e32"
@ -14011,6 +14082,11 @@ fragment-cache@^0.2.1:
dependencies:
map-cache "^0.2.2"
framework-utils@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/framework-utils/-/framework-utils-1.1.0.tgz#a3b528bce838dfd623148847dc92371b09d0da2d"
integrity sha512-KAfqli5PwpFJ8o3psRNs8svpMGyCSAe8nmGcjQ0zZBWN2H6dZDnq+ABp3N3hdUmFeMrLtjOCTXD4yplUJIWceg==
fresh@0.5.2, fresh@~0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
@ -14199,6 +14275,14 @@ gensync@^1.0.0-beta.1, gensync@^1.0.0-beta.2:
resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
gesto@^1.19.3, gesto@^1.19.4:
version "1.19.4"
resolved "https://registry.yarnpkg.com/gesto/-/gesto-1.19.4.tgz#14921ca89e4e70c14307c4d942df04f59eb00749"
integrity sha512-hfr/0dWwh0Bnbb88s3QVJd1ZRJeOWcgHPPwmiH6NnafDYvhTsxg+SLYu+q/oPNh9JS3V+nlr6fNs8kvPAtcRDQ==
dependencies:
"@daybrush/utils" "^1.13.0"
"@scena/event-emitter" "^1.0.2"
get-caller-file@^1.0.1:
version "1.0.3"
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.3.tgz#f978fa4c90d1dfe7ff2d6beda2a515e713bdcf4a"
@ -17918,6 +18002,16 @@ keycode@^2, keycode@^2.2.0, keycode@^2.2.1:
resolved "https://registry.yarnpkg.com/keycode/-/keycode-2.2.1.tgz#09c23b2be0611d26117ea2501c2c391a01f39eff"
integrity sha512-Rdgz9Hl9Iv4QKi8b0OlCRQEzp4AgVxyCtz5S/+VIHezDmrDhkp2N2TqBWOLz0/gbeREXOOiI9/4b8BY9uw2vFg==
keycon@^1.2.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/keycon/-/keycon-1.4.0.tgz#bf2a633f3c3b659ea564045938cff33e584cebd5"
integrity sha512-p1NAIxiRMH3jYfTeXRs2uWbVJ1WpEjpi8ktzUyBJsX7/wn2qu2VRXktneBLNtKNxJmlUYxRi9gOJt1DuthXR7A==
dependencies:
"@cfcs/core" "^0.0.6"
"@daybrush/utils" "^1.7.1"
"@scena/event-emitter" "^1.0.2"
keycode "^2.2.0"
keygrip@~1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/keygrip/-/keygrip-1.1.0.tgz#871b1681d5e159c62a445b0c74b615e0917e7226"
@ -20293,6 +20387,13 @@ outvariant@^1.2.1, outvariant@^1.4.0:
resolved "https://registry.yarnpkg.com/outvariant/-/outvariant-1.4.2.tgz#f54f19240eeb7f15b28263d5147405752d8e2066"
integrity sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==
overlap-area@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/overlap-area/-/overlap-area-1.1.0.tgz#1fcaa21bdb9cb1ace973d9aa299ae6b56557a4c2"
integrity sha512-3dlJgJCaVeXH0/eZjYVJvQiLVVrPO4U1ZGqlATtx6QGO3b5eNM6+JgUKa7oStBTdYuGTk7gVoABCW6Tp+dhRdw==
dependencies:
"@daybrush/utils" "^1.7.1"
p-all@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/p-all/-/p-all-2.1.0.tgz#91419be56b7dee8fe4c5db875d55e0da084244a0"
@ -21779,6 +21880,14 @@ react-contenteditable@^3.3.7:
fast-deep-equal "^3.1.3"
prop-types "^15.7.1"
react-css-styled@^1.1.9:
version "1.1.9"
resolved "https://registry.yarnpkg.com/react-css-styled/-/react-css-styled-1.1.9.tgz#a7cc948e49f72b2f7fb1393bd85416a8293afab3"
integrity sha512-M7fJZ3IWFaIHcZEkoFOnkjdiUFmwd8d+gTh2bpqMOcnxy/0Gsykw4dsL4QBiKsxcGow6tETUa4NAUcmJF+/nfw==
dependencies:
css-styled "~1.0.8"
framework-utils "^1.1.0"
react-csv@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/react-csv/-/react-csv-2.2.2.tgz#5bbf0d72a846412221a14880f294da9d6def9bfb"
@ -21954,6 +22063,25 @@ react-moment-proptypes@^1.4.0:
dependencies:
moment ">=1.6.0"
react-moveable@0.56.0:
version "0.56.0"
resolved "https://registry.yarnpkg.com/react-moveable/-/react-moveable-0.56.0.tgz#3537565079468aa9d241c17d0a5e78c56a562843"
integrity sha512-FmJNmIOsOA36mdxbrc/huiE4wuXSRlmon/o+/OrfNhSiYYYL0AV5oObtPluEhb2Yr/7EfYWBHTxF5aWAvjg1SA==
dependencies:
"@daybrush/utils" "^1.13.0"
"@egjs/agent" "^2.2.1"
"@egjs/children-differ" "^1.0.1"
"@egjs/list-differ" "^1.0.0"
"@scena/dragscroll" "^1.4.0"
"@scena/event-emitter" "^1.0.5"
"@scena/matrix" "^1.1.1"
css-to-mat "^1.1.1"
framework-utils "^1.1.0"
gesto "^1.19.3"
overlap-area "^1.1.0"
react-css-styled "^1.1.9"
react-selecto "^1.25.0"
react-popper@^2.2.5:
version "2.2.5"
resolved "https://registry.yarnpkg.com/react-popper/-/react-popper-2.2.5.tgz#1214ef3cec86330a171671a4fbcbeeb65ee58e96"
@ -22019,6 +22147,13 @@ react-router@6.16.0:
dependencies:
"@remix-run/router" "1.9.0"
react-selecto@^1.25.0:
version "1.26.3"
resolved "https://registry.yarnpkg.com/react-selecto/-/react-selecto-1.26.3.tgz#f9081c006cee2e2fed85ac1811cfe17136cf81a5"
integrity sha512-Ubik7kWSnZyQEBNro+1k38hZaI1tJarE+5aD/qsqCOA1uUBSjgKVBy3EWRzGIbdmVex7DcxznFZLec/6KZNvwQ==
dependencies:
selecto "~1.26.3"
react-shallow-renderer@^16.13.1:
version "16.14.1"
resolved "https://registry.yarnpkg.com/react-shallow-renderer/-/react-shallow-renderer-16.14.1.tgz#bf0d02df8a519a558fd9b8215442efa5c840e124"
@ -23300,6 +23435,22 @@ select-hose@^2.0.0:
resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca"
integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==
selecto@~1.26.3:
version "1.26.3"
resolved "https://registry.yarnpkg.com/selecto/-/selecto-1.26.3.tgz#12f259112b943d395731524e3bb0115da7372212"
integrity sha512-gZHgqMy5uyB6/2YDjv3Qqaf7bd2hTDOpPdxXlrez4R3/L0GiEWDCFaUfrflomgqdb3SxHF2IXY0Jw0EamZi7cw==
dependencies:
"@daybrush/utils" "^1.13.0"
"@egjs/children-differ" "^1.0.1"
"@scena/dragscroll" "^1.4.0"
"@scena/event-emitter" "^1.0.5"
css-styled "^1.0.8"
css-to-mat "^1.1.1"
framework-utils "^1.1.0"
gesto "^1.19.4"
keycon "^1.2.0"
overlap-area "^1.1.0"
selenium-webdriver@^4.0.0-alpha.1, selenium-webdriver@^4.0.0-alpha.8:
version "4.4.0"
resolved "https://registry.yarnpkg.com/selenium-webdriver/-/selenium-webdriver-4.4.0.tgz#3f280504f6c0ac64a24b176304213b5a49ec2553"