@tldraw/editor 3.15.0-next.f1dfcef63951 → 3.16.0-next.c30b1b5e551a
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist-cjs/index.d.ts +159 -44
- package/dist-cjs/index.js +20 -16
- package/dist-cjs/index.js.map +2 -2
- package/dist-cjs/lib/components/SVGContainer.js +1 -1
- package/dist-cjs/lib/components/SVGContainer.js.map +2 -2
- package/dist-cjs/lib/components/Shape.js +4 -26
- package/dist-cjs/lib/components/Shape.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultBrush.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultBrush.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultCanvas.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultCollaboratorHint.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultCursor.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultCursor.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultGrid.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultGrid.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultHandles.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultHandles.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultShapeIndicator.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultShapeWrapper.js +53 -0
- package/dist-cjs/lib/components/default-components/DefaultShapeWrapper.js.map +7 -0
- package/dist-cjs/lib/components/default-components/DefaultSnapIndictor.js +1 -1
- package/dist-cjs/lib/components/default-components/DefaultSnapIndictor.js.map +2 -2
- package/dist-cjs/lib/components/default-components/DefaultSpinner.js +27 -15
- package/dist-cjs/lib/components/default-components/DefaultSpinner.js.map +3 -3
- package/dist-cjs/lib/config/TLUserPreferences.js +7 -1
- package/dist-cjs/lib/config/TLUserPreferences.js.map +2 -2
- package/dist-cjs/lib/editor/Editor.js +88 -43
- package/dist-cjs/lib/editor/Editor.js.map +2 -2
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js +96 -101
- package/dist-cjs/lib/editor/managers/TextManager/TextManager.js.map +2 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js +7 -2
- package/dist-cjs/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.js.map +2 -2
- package/dist-cjs/lib/editor/shapes/ShapeUtil.js.map +2 -2
- package/dist-cjs/lib/editor/tools/StateNode.js +20 -1
- package/dist-cjs/lib/editor/tools/StateNode.js.map +2 -2
- package/dist-cjs/lib/editor/types/misc-types.js.map +1 -1
- package/dist-cjs/lib/hooks/useEditorComponents.js +2 -0
- package/dist-cjs/lib/hooks/useEditorComponents.js.map +2 -2
- package/dist-cjs/lib/license/Watermark.js +2 -2
- package/dist-cjs/lib/license/Watermark.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Arc2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Arc2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Circle2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Circle2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js +3 -1
- package/dist-cjs/lib/primitives/geometry/CubicBezier2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/Ellipse2d.js +1 -1
- package/dist-cjs/lib/primitives/geometry/Ellipse2d.js.map +2 -2
- package/dist-cjs/lib/primitives/geometry/geometry-constants.js +2 -2
- package/dist-cjs/lib/primitives/geometry/geometry-constants.js.map +2 -2
- package/dist-cjs/lib/primitives/intersect.js +4 -4
- package/dist-cjs/lib/primitives/intersect.js.map +2 -2
- package/dist-cjs/lib/primitives/utils.js +4 -0
- package/dist-cjs/lib/primitives/utils.js.map +2 -2
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js +0 -1
- package/dist-cjs/lib/utils/sync/TLLocalSyncClient.js.map +2 -2
- package/dist-cjs/version.js +3 -3
- package/dist-cjs/version.js.map +1 -1
- package/dist-esm/index.d.mts +159 -44
- package/dist-esm/index.mjs +47 -41
- package/dist-esm/index.mjs.map +2 -2
- package/dist-esm/lib/components/SVGContainer.mjs +1 -1
- package/dist-esm/lib/components/SVGContainer.mjs.map +2 -2
- package/dist-esm/lib/components/Shape.mjs +4 -26
- package/dist-esm/lib/components/Shape.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultBrush.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultBrush.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultCanvas.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultCollaboratorHint.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultCursor.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultCursor.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultGrid.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultGrid.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultHandles.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultHandles.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultShapeIndicator.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultShapeWrapper.mjs +23 -0
- package/dist-esm/lib/components/default-components/DefaultShapeWrapper.mjs.map +7 -0
- package/dist-esm/lib/components/default-components/DefaultSnapIndictor.mjs +1 -1
- package/dist-esm/lib/components/default-components/DefaultSnapIndictor.mjs.map +2 -2
- package/dist-esm/lib/components/default-components/DefaultSpinner.mjs +17 -15
- package/dist-esm/lib/components/default-components/DefaultSpinner.mjs.map +2 -2
- package/dist-esm/lib/config/TLUserPreferences.mjs +7 -1
- package/dist-esm/lib/config/TLUserPreferences.mjs.map +2 -2
- package/dist-esm/lib/editor/Editor.mjs +88 -43
- package/dist-esm/lib/editor/Editor.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs +96 -101
- package/dist-esm/lib/editor/managers/TextManager/TextManager.mjs.map +2 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs +7 -2
- package/dist-esm/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.mjs.map +2 -2
- package/dist-esm/lib/editor/shapes/ShapeUtil.mjs.map +2 -2
- package/dist-esm/lib/editor/tools/StateNode.mjs +20 -1
- package/dist-esm/lib/editor/tools/StateNode.mjs.map +2 -2
- package/dist-esm/lib/hooks/useEditorComponents.mjs +4 -0
- package/dist-esm/lib/hooks/useEditorComponents.mjs.map +2 -2
- package/dist-esm/lib/license/Watermark.mjs +2 -2
- package/dist-esm/lib/license/Watermark.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs +2 -2
- package/dist-esm/lib/primitives/geometry/Arc2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs +2 -2
- package/dist-esm/lib/primitives/geometry/Circle2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs +3 -1
- package/dist-esm/lib/primitives/geometry/CubicBezier2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs +2 -2
- package/dist-esm/lib/primitives/geometry/Ellipse2d.mjs.map +2 -2
- package/dist-esm/lib/primitives/geometry/geometry-constants.mjs +2 -2
- package/dist-esm/lib/primitives/geometry/geometry-constants.mjs.map +2 -2
- package/dist-esm/lib/primitives/intersect.mjs +5 -5
- package/dist-esm/lib/primitives/intersect.mjs.map +2 -2
- package/dist-esm/lib/primitives/utils.mjs +4 -0
- package/dist-esm/lib/primitives/utils.mjs.map +2 -2
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs +0 -1
- package/dist-esm/lib/utils/sync/TLLocalSyncClient.mjs.map +2 -2
- package/dist-esm/version.mjs +3 -3
- package/dist-esm/version.mjs.map +1 -1
- package/editor.css +21 -27
- package/package.json +9 -8
- package/src/index.ts +68 -62
- package/src/lib/components/SVGContainer.tsx +1 -1
- package/src/lib/components/Shape.tsx +6 -21
- package/src/lib/components/default-components/DefaultBrush.tsx +1 -1
- package/src/lib/components/default-components/DefaultCanvas.tsx +1 -1
- package/src/lib/components/default-components/DefaultCollaboratorHint.tsx +1 -1
- package/src/lib/components/default-components/DefaultCursor.tsx +1 -1
- package/src/lib/components/default-components/DefaultGrid.tsx +1 -1
- package/src/lib/components/default-components/DefaultHandles.tsx +5 -1
- package/src/lib/components/default-components/DefaultShapeIndicator.tsx +1 -1
- package/src/lib/components/default-components/DefaultShapeWrapper.tsx +35 -0
- package/src/lib/components/default-components/DefaultSnapIndictor.tsx +1 -1
- package/src/lib/components/default-components/DefaultSpinner.tsx +12 -12
- package/src/lib/config/TLUserPreferences.ts +7 -0
- package/src/lib/editor/Editor.test.ts +407 -0
- package/src/lib/editor/Editor.ts +106 -44
- package/src/lib/editor/managers/TextManager/TextManager.ts +108 -128
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.test.ts +21 -0
- package/src/lib/editor/managers/UserPreferencesManager/UserPreferencesManager.ts +8 -0
- package/src/lib/editor/shapes/ShapeUtil.ts +57 -0
- package/src/lib/editor/tools/StateNode.test.ts +285 -0
- package/src/lib/editor/tools/StateNode.ts +27 -1
- package/src/lib/editor/types/misc-types.ts +19 -0
- package/src/lib/hooks/useEditorComponents.tsx +8 -2
- package/src/lib/license/LicenseManager.test.ts +1 -1
- package/src/lib/license/Watermark.tsx +2 -2
- package/src/lib/primitives/geometry/Arc2d.ts +2 -2
- package/src/lib/primitives/geometry/Circle2d.ts +2 -2
- package/src/lib/primitives/geometry/CubicBezier2d.ts +4 -1
- package/src/lib/primitives/geometry/Ellipse2d.ts +2 -2
- package/src/lib/primitives/geometry/geometry-constants.ts +2 -1
- package/src/lib/primitives/intersect.test.ts +946 -0
- package/src/lib/primitives/intersect.ts +12 -5
- package/src/lib/primitives/utils.ts +11 -0
- package/src/lib/utils/sync/TLLocalSyncClient.ts +0 -1
- package/src/version.ts +3 -3
- package/src/lib/test/currentToolIdMask.test.ts +0 -49
|
@@ -7,7 +7,7 @@ export type SVGContainerProps = React.ComponentProps<'svg'>
|
|
|
7
7
|
/** @public @react */
|
|
8
8
|
export function SVGContainer({ children, className = '', ...rest }: SVGContainerProps) {
|
|
9
9
|
return (
|
|
10
|
-
<svg {...rest} className={classNames('tl-svg-container', className)}>
|
|
10
|
+
<svg {...rest} className={classNames('tl-svg-container', className)} aria-hidden="true">
|
|
11
11
|
{children}
|
|
12
12
|
</svg>
|
|
13
13
|
)
|
|
@@ -40,7 +40,7 @@ export const Shape = memo(function Shape({
|
|
|
40
40
|
}) {
|
|
41
41
|
const editor = useEditor()
|
|
42
42
|
|
|
43
|
-
const { ShapeErrorFallback } = useEditorComponents()
|
|
43
|
+
const { ShapeErrorFallback, ShapeWrapper } = useEditorComponents()
|
|
44
44
|
|
|
45
45
|
const containerRef = useRef<HTMLDivElement>(null)
|
|
46
46
|
const bgContainerRef = useRef<HTMLDivElement>(null)
|
|
@@ -145,37 +145,22 @@ export const Shape = memo(function Shape({
|
|
|
145
145
|
[editor]
|
|
146
146
|
)
|
|
147
147
|
|
|
148
|
-
if (!shape) return null
|
|
149
|
-
|
|
150
|
-
const isFilledShape = 'fill' in shape.props && shape.props.fill !== 'none'
|
|
148
|
+
if (!shape || !ShapeWrapper) return null
|
|
151
149
|
|
|
152
150
|
return (
|
|
153
151
|
<>
|
|
154
152
|
{util.backgroundComponent && (
|
|
155
|
-
<
|
|
156
|
-
ref={bgContainerRef}
|
|
157
|
-
className="tl-shape tl-shape-background"
|
|
158
|
-
data-shape-type={shape.type}
|
|
159
|
-
data-shape-id={shape.id}
|
|
160
|
-
draggable={false}
|
|
161
|
-
>
|
|
153
|
+
<ShapeWrapper ref={bgContainerRef} shape={shape} isBackground={true}>
|
|
162
154
|
<OptionalErrorBoundary fallback={ShapeErrorFallback} onError={annotateError}>
|
|
163
155
|
<InnerShapeBackground shape={shape} util={util} />
|
|
164
156
|
</OptionalErrorBoundary>
|
|
165
|
-
</
|
|
157
|
+
</ShapeWrapper>
|
|
166
158
|
)}
|
|
167
|
-
<
|
|
168
|
-
ref={containerRef}
|
|
169
|
-
className="tl-shape"
|
|
170
|
-
data-shape-type={shape.type}
|
|
171
|
-
data-shape-is-filled={isFilledShape}
|
|
172
|
-
data-shape-id={shape.id}
|
|
173
|
-
draggable={false}
|
|
174
|
-
>
|
|
159
|
+
<ShapeWrapper ref={containerRef} shape={shape} isBackground={false}>
|
|
175
160
|
<OptionalErrorBoundary fallback={ShapeErrorFallback as any} onError={annotateError}>
|
|
176
161
|
<InnerShape shape={shape} util={util} />
|
|
177
162
|
</OptionalErrorBoundary>
|
|
178
|
-
</
|
|
163
|
+
</ShapeWrapper>
|
|
179
164
|
</>
|
|
180
165
|
)
|
|
181
166
|
})
|
|
@@ -21,7 +21,7 @@ export const DefaultBrush = ({ brush, color, opacity, className }: TLBrushProps)
|
|
|
21
21
|
const h = toDomPrecision(Math.max(1, brush.h))
|
|
22
22
|
|
|
23
23
|
return (
|
|
24
|
-
<svg className="tl-overlays__item" ref={rSvg}>
|
|
24
|
+
<svg className="tl-overlays__item" ref={rSvg} aria-hidden="true">
|
|
25
25
|
{color ? (
|
|
26
26
|
<g className="tl-brush" opacity={opacity}>
|
|
27
27
|
<rect width={w} height={h} fill={color} opacity={0.75} />
|
|
@@ -139,7 +139,7 @@ export function DefaultCanvas({ className }: TLCanvasComponentProps) {
|
|
|
139
139
|
data-testid="canvas"
|
|
140
140
|
{...events}
|
|
141
141
|
>
|
|
142
|
-
<svg className="tl-svg-context">
|
|
142
|
+
<svg className="tl-svg-context" aria-hidden="true">
|
|
143
143
|
<defs>
|
|
144
144
|
{shapeSvgDefs}
|
|
145
145
|
<CursorDef />
|
|
@@ -39,7 +39,7 @@ export function DefaultCollaboratorHint({
|
|
|
39
39
|
const cursorHintId = useSharedSafeId('cursor_hint')
|
|
40
40
|
|
|
41
41
|
return (
|
|
42
|
-
<svg ref={rSvg} className={classNames('tl-overlays__item', className)}>
|
|
42
|
+
<svg ref={rSvg} className={classNames('tl-overlays__item', className)} aria-hidden="true">
|
|
43
43
|
<use
|
|
44
44
|
href={`#${cursorHintId}`}
|
|
45
45
|
color={color}
|
|
@@ -33,7 +33,7 @@ export const DefaultCursor = memo(function DefaultCursor({
|
|
|
33
33
|
|
|
34
34
|
return (
|
|
35
35
|
<div ref={rCursor} className={classNames('tl-overlays__item', className)}>
|
|
36
|
-
<svg className="tl-cursor">
|
|
36
|
+
<svg className="tl-cursor" aria-hidden="true">
|
|
37
37
|
<use href={`#${cursorId}`} color={color} />
|
|
38
38
|
</svg>
|
|
39
39
|
{chatMessage ? (
|
|
@@ -16,7 +16,7 @@ export function DefaultGrid({ x, y, z, size }: TLGridProps) {
|
|
|
16
16
|
const editor = useEditor()
|
|
17
17
|
const { gridSteps } = editor.options
|
|
18
18
|
return (
|
|
19
|
-
<svg className="tl-grid" version="1.1" xmlns="http://www.w3.org/2000/svg">
|
|
19
|
+
<svg className="tl-grid" version="1.1" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
20
20
|
<defs>
|
|
21
21
|
{gridSteps.map(({ min, mid, step }, i) => {
|
|
22
22
|
const s = step * size * z
|
|
@@ -7,5 +7,9 @@ export interface TLHandlesProps {
|
|
|
7
7
|
|
|
8
8
|
/** @public @react */
|
|
9
9
|
export const DefaultHandles = ({ children }: TLHandlesProps) => {
|
|
10
|
-
return
|
|
10
|
+
return (
|
|
11
|
+
<svg className="tl-user-handles tl-overlays__item" aria-hidden="true">
|
|
12
|
+
{children}
|
|
13
|
+
</svg>
|
|
14
|
+
)
|
|
11
15
|
}
|
|
@@ -86,7 +86,7 @@ export const DefaultShapeIndicator = memo(function DefaultShapeIndicator({
|
|
|
86
86
|
}, [hidden])
|
|
87
87
|
|
|
88
88
|
return (
|
|
89
|
-
<svg ref={rIndicator} className={classNames('tl-overlays__item', className)}>
|
|
89
|
+
<svg ref={rIndicator} className={classNames('tl-overlays__item', className)} aria-hidden="true">
|
|
90
90
|
<g className="tl-shape-indicator" stroke={color ?? 'var(--color-selected)'} opacity={opacity}>
|
|
91
91
|
<InnerIndicator editor={editor} id={shapeId} />
|
|
92
92
|
</g>
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { TLShape } from '@tldraw/tlschema'
|
|
2
|
+
import classNames from 'classnames'
|
|
3
|
+
import { forwardRef, ReactNode } from 'react'
|
|
4
|
+
|
|
5
|
+
/** @public */
|
|
6
|
+
export interface TLShapeWrapperProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
7
|
+
/** The shape being rendered. */
|
|
8
|
+
shape: TLShape
|
|
9
|
+
/** Whether this is the shapes regular, or background component. */
|
|
10
|
+
isBackground: boolean
|
|
11
|
+
/** The shape's rendered component. */
|
|
12
|
+
children: ReactNode
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** @public @react */
|
|
16
|
+
export const DefaultShapeWrapper = forwardRef(function DefaultShapeWrapper(
|
|
17
|
+
{ children, shape, isBackground, ...props }: TLShapeWrapperProps,
|
|
18
|
+
ref: React.Ref<HTMLDivElement>
|
|
19
|
+
) {
|
|
20
|
+
const isFilledShape = 'fill' in shape.props && shape.props.fill !== 'none'
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
ref={ref}
|
|
25
|
+
data-shape-type={shape.type}
|
|
26
|
+
data-shape-is-filled={isBackground ? undefined : isFilledShape}
|
|
27
|
+
data-shape-id={shape.id}
|
|
28
|
+
draggable={false}
|
|
29
|
+
{...props}
|
|
30
|
+
className={classNames('tl-shape', isBackground && 'tl-shape-background', props.className)}
|
|
31
|
+
>
|
|
32
|
+
{children}
|
|
33
|
+
</div>
|
|
34
|
+
)
|
|
35
|
+
})
|
|
@@ -163,7 +163,7 @@ export interface TLSnapIndicatorProps {
|
|
|
163
163
|
/** @public @react */
|
|
164
164
|
export function DefaultSnapIndicator({ className, line, zoom }: TLSnapIndicatorProps) {
|
|
165
165
|
return (
|
|
166
|
-
<svg className={classNames('tl-overlays__item', className)}>
|
|
166
|
+
<svg className={classNames('tl-overlays__item', className)} aria-hidden="true">
|
|
167
167
|
{line.type === 'points' ? (
|
|
168
168
|
<PointsSnapIndicator {...line} zoom={zoom} />
|
|
169
169
|
) : line.type === 'gaps' ? (
|
|
@@ -1,19 +1,19 @@
|
|
|
1
|
+
import classNames from 'classnames'
|
|
2
|
+
|
|
1
3
|
/** @public @react */
|
|
2
|
-
export function DefaultSpinner() {
|
|
4
|
+
export function DefaultSpinner(props: React.SVGProps<SVGSVGElement>) {
|
|
3
5
|
return (
|
|
4
|
-
<svg
|
|
6
|
+
<svg
|
|
7
|
+
width={16}
|
|
8
|
+
height={16}
|
|
9
|
+
viewBox="0 0 16 16"
|
|
10
|
+
aria-hidden="false"
|
|
11
|
+
{...props}
|
|
12
|
+
className={classNames('tl-spinner', props.className)}
|
|
13
|
+
>
|
|
5
14
|
<g strokeWidth={2} fill="none" fillRule="evenodd">
|
|
6
15
|
<circle strokeOpacity={0.25} cx={8} cy={8} r={7} stroke="currentColor" />
|
|
7
|
-
<path strokeLinecap="round" d="M15 8c0-4.5-4.5-7-7-7" stroke="currentColor"
|
|
8
|
-
<animateTransform
|
|
9
|
-
attributeName="transform"
|
|
10
|
-
type="rotate"
|
|
11
|
-
from="0 8 8"
|
|
12
|
-
to="360 8 8"
|
|
13
|
-
dur="1s"
|
|
14
|
-
repeatCount="indefinite"
|
|
15
|
-
/>
|
|
16
|
-
</path>
|
|
16
|
+
<path strokeLinecap="round" d="M15 8c0-4.5-4.5-7-7-7" stroke="currentColor" />
|
|
17
17
|
</g>
|
|
18
18
|
</svg>
|
|
19
19
|
)
|
|
@@ -17,6 +17,7 @@ export interface TLUserPreferences {
|
|
|
17
17
|
// N.B. These are duplicated in TLdrawAppUser.
|
|
18
18
|
locale?: string | null
|
|
19
19
|
animationSpeed?: number | null
|
|
20
|
+
areKeyboardShortcutsEnabled?: boolean | null
|
|
20
21
|
edgeScrollSpeed?: number | null
|
|
21
22
|
colorScheme?: 'light' | 'dark' | 'system'
|
|
22
23
|
isSnapMode?: boolean | null
|
|
@@ -44,6 +45,7 @@ export const userTypeValidator: T.Validator<TLUserPreferences> = T.object<TLUser
|
|
|
44
45
|
// N.B. These are duplicated in TLdrawAppUser.
|
|
45
46
|
locale: T.string.nullable().optional(),
|
|
46
47
|
animationSpeed: T.number.nullable().optional(),
|
|
48
|
+
areKeyboardShortcutsEnabled: T.boolean.nullable().optional(),
|
|
47
49
|
edgeScrollSpeed: T.number.nullable().optional(),
|
|
48
50
|
colorScheme: T.literalEnum('light', 'dark', 'system').optional(),
|
|
49
51
|
isSnapMode: T.boolean.nullable().optional(),
|
|
@@ -61,6 +63,7 @@ const Versions = {
|
|
|
61
63
|
AddDynamicSizeMode: 6,
|
|
62
64
|
AllowSystemColorScheme: 7,
|
|
63
65
|
AddPasteAtCursor: 8,
|
|
66
|
+
AddKeyboardShortcuts: 9,
|
|
64
67
|
} as const
|
|
65
68
|
|
|
66
69
|
const CURRENT_VERSION = Math.max(...Object.values(Versions))
|
|
@@ -96,6 +99,9 @@ function migrateSnapshot(data: { version: number; user: any }) {
|
|
|
96
99
|
if (data.version < Versions.AddPasteAtCursor) {
|
|
97
100
|
data.user.isPasteAtCursorMode = false
|
|
98
101
|
}
|
|
102
|
+
if (data.version < Versions.AddKeyboardShortcuts) {
|
|
103
|
+
data.user.areKeyboardShortcutsEnabled = true
|
|
104
|
+
}
|
|
99
105
|
|
|
100
106
|
// finally
|
|
101
107
|
data.version = CURRENT_VERSION
|
|
@@ -139,6 +145,7 @@ export const defaultUserPreferences = Object.freeze({
|
|
|
139
145
|
// N.B. These are duplicated in TLdrawAppUser.
|
|
140
146
|
edgeScrollSpeed: 1,
|
|
141
147
|
animationSpeed: userPrefersReducedMotion() ? 0 : 1,
|
|
148
|
+
areKeyboardShortcutsEnabled: true,
|
|
142
149
|
isSnapMode: false,
|
|
143
150
|
isWrapMode: false,
|
|
144
151
|
isDynamicSizeMode: false,
|
|
@@ -425,3 +425,410 @@ describe('getShapesAtPoint', () => {
|
|
|
425
425
|
expect(hollowShapesWithHitInside[0].id).toBe(ids.hollowShape)
|
|
426
426
|
})
|
|
427
427
|
})
|
|
428
|
+
|
|
429
|
+
describe('selectAll', () => {
|
|
430
|
+
const selectAllIds = {
|
|
431
|
+
pageShape1: createShapeId('pageShape1'),
|
|
432
|
+
pageShape2: createShapeId('pageShape2'),
|
|
433
|
+
pageShape3: createShapeId('pageShape3'),
|
|
434
|
+
container1: createShapeId('container1'),
|
|
435
|
+
containerChild1: createShapeId('containerChild1'),
|
|
436
|
+
containerChild2: createShapeId('containerChild2'),
|
|
437
|
+
containerChild3: createShapeId('containerChild3'),
|
|
438
|
+
containerGrandchild1: createShapeId('containerGrandchild1'),
|
|
439
|
+
container2: createShapeId('container2'),
|
|
440
|
+
container2Child1: createShapeId('container2Child1'),
|
|
441
|
+
container2Child2: createShapeId('container2Child2'),
|
|
442
|
+
container2Grandchild1: createShapeId('container2Grandchild1'),
|
|
443
|
+
lockedShape: createShapeId('lockedShape'),
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
beforeEach(() => {
|
|
447
|
+
// Clear any existing shapes
|
|
448
|
+
editor.selectAll().deleteShapes(editor.getSelectedShapeIds())
|
|
449
|
+
|
|
450
|
+
// Create shapes directly on the page (no parentId means they're children of the page)
|
|
451
|
+
editor.createShapes([
|
|
452
|
+
{
|
|
453
|
+
id: selectAllIds.pageShape1,
|
|
454
|
+
type: 'my-custom-shape',
|
|
455
|
+
x: 100,
|
|
456
|
+
y: 100,
|
|
457
|
+
props: { w: 100, h: 100 },
|
|
458
|
+
},
|
|
459
|
+
{
|
|
460
|
+
id: selectAllIds.pageShape2,
|
|
461
|
+
type: 'my-custom-shape',
|
|
462
|
+
x: 300,
|
|
463
|
+
y: 100,
|
|
464
|
+
props: { w: 100, h: 100 },
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
id: selectAllIds.pageShape3,
|
|
468
|
+
type: 'my-custom-shape',
|
|
469
|
+
x: 500,
|
|
470
|
+
y: 100,
|
|
471
|
+
props: { w: 100, h: 100 },
|
|
472
|
+
},
|
|
473
|
+
{
|
|
474
|
+
id: selectAllIds.lockedShape,
|
|
475
|
+
type: 'my-custom-shape',
|
|
476
|
+
x: 700,
|
|
477
|
+
y: 100,
|
|
478
|
+
props: { w: 100, h: 100 },
|
|
479
|
+
isLocked: true,
|
|
480
|
+
},
|
|
481
|
+
])
|
|
482
|
+
|
|
483
|
+
// Create a container shape (simulating a frame or group)
|
|
484
|
+
editor.createShape({
|
|
485
|
+
id: selectAllIds.container1,
|
|
486
|
+
type: 'my-custom-shape',
|
|
487
|
+
x: 100,
|
|
488
|
+
y: 300,
|
|
489
|
+
props: { w: 400, h: 200 },
|
|
490
|
+
})
|
|
491
|
+
|
|
492
|
+
// Create children inside the container (parentId set to container1)
|
|
493
|
+
editor.createShapes([
|
|
494
|
+
{
|
|
495
|
+
id: selectAllIds.containerChild1,
|
|
496
|
+
type: 'my-custom-shape',
|
|
497
|
+
parentId: selectAllIds.container1,
|
|
498
|
+
x: 120,
|
|
499
|
+
y: 320,
|
|
500
|
+
props: { w: 50, h: 50 },
|
|
501
|
+
},
|
|
502
|
+
{
|
|
503
|
+
id: selectAllIds.containerChild2,
|
|
504
|
+
type: 'my-custom-shape',
|
|
505
|
+
parentId: selectAllIds.container1,
|
|
506
|
+
x: 200,
|
|
507
|
+
y: 320,
|
|
508
|
+
props: { w: 50, h: 50 },
|
|
509
|
+
},
|
|
510
|
+
{
|
|
511
|
+
id: selectAllIds.containerChild3,
|
|
512
|
+
type: 'my-custom-shape',
|
|
513
|
+
parentId: selectAllIds.container1,
|
|
514
|
+
x: 280,
|
|
515
|
+
y: 320,
|
|
516
|
+
props: { w: 50, h: 50 },
|
|
517
|
+
},
|
|
518
|
+
])
|
|
519
|
+
|
|
520
|
+
// Create a grandchild inside one of the container children
|
|
521
|
+
editor.createShape({
|
|
522
|
+
id: selectAllIds.containerGrandchild1,
|
|
523
|
+
type: 'my-custom-shape',
|
|
524
|
+
parentId: selectAllIds.containerChild3,
|
|
525
|
+
x: 290,
|
|
526
|
+
y: 330,
|
|
527
|
+
props: { w: 30, h: 30 },
|
|
528
|
+
})
|
|
529
|
+
|
|
530
|
+
// Create a second container (simulating a group)
|
|
531
|
+
editor.createShape({
|
|
532
|
+
id: selectAllIds.container2,
|
|
533
|
+
type: 'my-custom-shape',
|
|
534
|
+
x: 600,
|
|
535
|
+
y: 300,
|
|
536
|
+
props: { w: 200, h: 200 },
|
|
537
|
+
})
|
|
538
|
+
|
|
539
|
+
// Create children inside the second container
|
|
540
|
+
editor.createShapes([
|
|
541
|
+
{
|
|
542
|
+
id: selectAllIds.container2Child1,
|
|
543
|
+
type: 'my-custom-shape',
|
|
544
|
+
parentId: selectAllIds.container2,
|
|
545
|
+
x: 620,
|
|
546
|
+
y: 320,
|
|
547
|
+
props: { w: 50, h: 50 },
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
id: selectAllIds.container2Child2,
|
|
551
|
+
type: 'my-custom-shape',
|
|
552
|
+
parentId: selectAllIds.container2,
|
|
553
|
+
x: 680,
|
|
554
|
+
y: 320,
|
|
555
|
+
props: { w: 50, h: 50 },
|
|
556
|
+
},
|
|
557
|
+
])
|
|
558
|
+
|
|
559
|
+
// Create a grandchild in the second container
|
|
560
|
+
editor.createShape({
|
|
561
|
+
id: selectAllIds.container2Grandchild1,
|
|
562
|
+
type: 'my-custom-shape',
|
|
563
|
+
parentId: selectAllIds.container2Child1,
|
|
564
|
+
x: 630,
|
|
565
|
+
y: 330,
|
|
566
|
+
props: { w: 30, h: 30 },
|
|
567
|
+
})
|
|
568
|
+
|
|
569
|
+
// Clear selection
|
|
570
|
+
editor.selectNone()
|
|
571
|
+
})
|
|
572
|
+
|
|
573
|
+
it('when no shapes are selected, selects all page-level shapes (excluding locked ones)', () => {
|
|
574
|
+
// Initially no shapes selected
|
|
575
|
+
expect(editor.getSelectedShapeIds()).toEqual([])
|
|
576
|
+
|
|
577
|
+
// Call selectAll
|
|
578
|
+
editor.selectAll()
|
|
579
|
+
|
|
580
|
+
// Should select all page-level shapes (excluding locked ones)
|
|
581
|
+
const selectedIds = editor.getSelectedShapeIds()
|
|
582
|
+
expect(Array.from(selectedIds).sort()).toEqual(
|
|
583
|
+
[
|
|
584
|
+
selectAllIds.pageShape1,
|
|
585
|
+
selectAllIds.pageShape2,
|
|
586
|
+
selectAllIds.pageShape3,
|
|
587
|
+
selectAllIds.container1,
|
|
588
|
+
selectAllIds.container2,
|
|
589
|
+
].sort()
|
|
590
|
+
)
|
|
591
|
+
|
|
592
|
+
// Should NOT include locked shape or children/grandchildren
|
|
593
|
+
expect(selectedIds).not.toContain(selectAllIds.lockedShape)
|
|
594
|
+
expect(selectedIds).not.toContain(selectAllIds.containerChild1)
|
|
595
|
+
expect(selectedIds).not.toContain(selectAllIds.containerChild2)
|
|
596
|
+
expect(selectedIds).not.toContain(selectAllIds.containerChild3)
|
|
597
|
+
expect(selectedIds).not.toContain(selectAllIds.containerGrandchild1)
|
|
598
|
+
expect(selectedIds).not.toContain(selectAllIds.container2Child1)
|
|
599
|
+
expect(selectedIds).not.toContain(selectAllIds.container2Child2)
|
|
600
|
+
expect(selectedIds).not.toContain(selectAllIds.container2Grandchild1)
|
|
601
|
+
})
|
|
602
|
+
|
|
603
|
+
it('when shapes are selected only on the page, all children of the page should be selected (but not their descendants)', () => {
|
|
604
|
+
// Select some page-level shapes
|
|
605
|
+
editor.select(selectAllIds.pageShape1, selectAllIds.pageShape2)
|
|
606
|
+
|
|
607
|
+
// Call selectAll
|
|
608
|
+
editor.selectAll()
|
|
609
|
+
|
|
610
|
+
// Should select all page-level shapes (excluding locked ones), but not descendants
|
|
611
|
+
const selectedIds = editor.getSelectedShapeIds()
|
|
612
|
+
expect(Array.from(selectedIds).sort()).toEqual(
|
|
613
|
+
[
|
|
614
|
+
selectAllIds.pageShape1,
|
|
615
|
+
selectAllIds.pageShape2,
|
|
616
|
+
selectAllIds.pageShape3,
|
|
617
|
+
selectAllIds.container1,
|
|
618
|
+
selectAllIds.container2,
|
|
619
|
+
].sort()
|
|
620
|
+
)
|
|
621
|
+
|
|
622
|
+
// Should NOT include children or grandchildren or locked shapes
|
|
623
|
+
expect(selectedIds).not.toContain(selectAllIds.containerChild1)
|
|
624
|
+
expect(selectedIds).not.toContain(selectAllIds.containerChild2)
|
|
625
|
+
expect(selectedIds).not.toContain(selectAllIds.containerChild3)
|
|
626
|
+
expect(selectedIds).not.toContain(selectAllIds.containerGrandchild1)
|
|
627
|
+
expect(selectedIds).not.toContain(selectAllIds.container2Child1)
|
|
628
|
+
expect(selectedIds).not.toContain(selectAllIds.container2Child2)
|
|
629
|
+
expect(selectedIds).not.toContain(selectAllIds.container2Grandchild1)
|
|
630
|
+
expect(selectedIds).not.toContain(selectAllIds.lockedShape)
|
|
631
|
+
})
|
|
632
|
+
|
|
633
|
+
it('when shapes are selected within a container, only children of the container should be selected (not their descendants)', () => {
|
|
634
|
+
// Select some container children
|
|
635
|
+
editor.select(selectAllIds.containerChild1, selectAllIds.containerChild2)
|
|
636
|
+
|
|
637
|
+
// Call selectAll
|
|
638
|
+
editor.selectAll()
|
|
639
|
+
|
|
640
|
+
// Should select all container children (but not their descendants)
|
|
641
|
+
const selectedIds = editor.getSelectedShapeIds()
|
|
642
|
+
expect(Array.from(selectedIds).sort()).toEqual(
|
|
643
|
+
[
|
|
644
|
+
selectAllIds.containerChild1,
|
|
645
|
+
selectAllIds.containerChild2,
|
|
646
|
+
selectAllIds.containerChild3,
|
|
647
|
+
].sort()
|
|
648
|
+
)
|
|
649
|
+
|
|
650
|
+
// Should NOT include page-level shapes or grandchildren
|
|
651
|
+
expect(selectedIds).not.toContain(selectAllIds.pageShape1)
|
|
652
|
+
expect(selectedIds).not.toContain(selectAllIds.pageShape2)
|
|
653
|
+
expect(selectedIds).not.toContain(selectAllIds.pageShape3)
|
|
654
|
+
expect(selectedIds).not.toContain(selectAllIds.container1)
|
|
655
|
+
expect(selectedIds).not.toContain(selectAllIds.container2)
|
|
656
|
+
expect(selectedIds).not.toContain(selectAllIds.containerGrandchild1)
|
|
657
|
+
expect(selectedIds).not.toContain(selectAllIds.container2Child1)
|
|
658
|
+
expect(selectedIds).not.toContain(selectAllIds.container2Child2)
|
|
659
|
+
expect(selectedIds).not.toContain(selectAllIds.container2Grandchild1)
|
|
660
|
+
})
|
|
661
|
+
|
|
662
|
+
it('when shapes are selected within a second container, only children of that container should be selected', () => {
|
|
663
|
+
// Select some second container children
|
|
664
|
+
editor.select(selectAllIds.container2Child1)
|
|
665
|
+
|
|
666
|
+
// Call selectAll
|
|
667
|
+
editor.selectAll()
|
|
668
|
+
|
|
669
|
+
// Should select all second container children (but not their descendants)
|
|
670
|
+
const selectedIds = editor.getSelectedShapeIds()
|
|
671
|
+
expect(Array.from(selectedIds).sort()).toEqual(
|
|
672
|
+
[selectAllIds.container2Child1, selectAllIds.container2Child2].sort()
|
|
673
|
+
)
|
|
674
|
+
|
|
675
|
+
// Should NOT include page-level shapes or other container's children or grandchildren
|
|
676
|
+
expect(selectedIds).not.toContain(selectAllIds.pageShape1)
|
|
677
|
+
expect(selectedIds).not.toContain(selectAllIds.pageShape2)
|
|
678
|
+
expect(selectedIds).not.toContain(selectAllIds.pageShape3)
|
|
679
|
+
expect(selectedIds).not.toContain(selectAllIds.container1)
|
|
680
|
+
expect(selectedIds).not.toContain(selectAllIds.container2)
|
|
681
|
+
expect(selectedIds).not.toContain(selectAllIds.containerChild1)
|
|
682
|
+
expect(selectedIds).not.toContain(selectAllIds.containerChild2)
|
|
683
|
+
expect(selectedIds).not.toContain(selectAllIds.containerChild3)
|
|
684
|
+
expect(selectedIds).not.toContain(selectAllIds.containerGrandchild1)
|
|
685
|
+
expect(selectedIds).not.toContain(selectAllIds.container2Grandchild1)
|
|
686
|
+
})
|
|
687
|
+
|
|
688
|
+
it('when shapes are selected that belong to different parents, no change/history entry should be made', () => {
|
|
689
|
+
// Select shapes from different parents (page and container)
|
|
690
|
+
editor.select(selectAllIds.pageShape1, selectAllIds.containerChild1)
|
|
691
|
+
|
|
692
|
+
const initialSelectedIds = editor.getSelectedShapeIds()
|
|
693
|
+
|
|
694
|
+
// Spy on setSelectedShapes to verify it's not called
|
|
695
|
+
const setSelectedShapesSpy = jest.spyOn(editor, 'setSelectedShapes')
|
|
696
|
+
|
|
697
|
+
// Call selectAll
|
|
698
|
+
editor.selectAll()
|
|
699
|
+
|
|
700
|
+
// Selection should remain unchanged
|
|
701
|
+
expect(editor.getSelectedShapeIds()).toEqual(initialSelectedIds)
|
|
702
|
+
|
|
703
|
+
// setSelectedShapes should not have been called (the method returns early)
|
|
704
|
+
expect(setSelectedShapesSpy).not.toHaveBeenCalled()
|
|
705
|
+
|
|
706
|
+
setSelectedShapesSpy.mockRestore()
|
|
707
|
+
})
|
|
708
|
+
|
|
709
|
+
it('when shapes are selected that belong to different containers, no change/history entry should be made', () => {
|
|
710
|
+
// Select shapes from different containers
|
|
711
|
+
editor.select(selectAllIds.containerChild1, selectAllIds.container2Child1)
|
|
712
|
+
|
|
713
|
+
const initialSelectedIds = editor.getSelectedShapeIds()
|
|
714
|
+
|
|
715
|
+
// Spy on setSelectedShapes to verify it's not called
|
|
716
|
+
const setSelectedShapesSpy = jest.spyOn(editor, 'setSelectedShapes')
|
|
717
|
+
|
|
718
|
+
// Call selectAll
|
|
719
|
+
editor.selectAll()
|
|
720
|
+
|
|
721
|
+
// Selection should remain unchanged
|
|
722
|
+
expect(editor.getSelectedShapeIds()).toEqual(initialSelectedIds)
|
|
723
|
+
|
|
724
|
+
// setSelectedShapes should not have been called
|
|
725
|
+
expect(setSelectedShapesSpy).not.toHaveBeenCalled()
|
|
726
|
+
|
|
727
|
+
setSelectedShapesSpy.mockRestore()
|
|
728
|
+
})
|
|
729
|
+
|
|
730
|
+
it('should not select locked shapes', () => {
|
|
731
|
+
// Select a page-level shape
|
|
732
|
+
editor.select(selectAllIds.pageShape1)
|
|
733
|
+
|
|
734
|
+
// Call selectAll
|
|
735
|
+
editor.selectAll()
|
|
736
|
+
|
|
737
|
+
// Should select all page-level shapes except locked ones
|
|
738
|
+
const selectedIds = editor.getSelectedShapeIds()
|
|
739
|
+
expect(selectedIds).not.toContain(selectAllIds.lockedShape)
|
|
740
|
+
expect(selectedIds).toContain(selectAllIds.pageShape1)
|
|
741
|
+
expect(selectedIds).toContain(selectAllIds.pageShape2)
|
|
742
|
+
expect(selectedIds).toContain(selectAllIds.pageShape3)
|
|
743
|
+
expect(selectedIds).toContain(selectAllIds.container1)
|
|
744
|
+
expect(selectedIds).toContain(selectAllIds.container2)
|
|
745
|
+
})
|
|
746
|
+
|
|
747
|
+
it('should handle empty container by selecting all siblings at the same level', () => {
|
|
748
|
+
// Create an empty container
|
|
749
|
+
const emptyContainerId = createShapeId('emptyContainer')
|
|
750
|
+
editor.createShape({
|
|
751
|
+
id: emptyContainerId,
|
|
752
|
+
type: 'my-custom-shape',
|
|
753
|
+
x: 800,
|
|
754
|
+
y: 400,
|
|
755
|
+
props: { w: 100, h: 100 },
|
|
756
|
+
})
|
|
757
|
+
|
|
758
|
+
// Clear selection first
|
|
759
|
+
editor.selectNone()
|
|
760
|
+
|
|
761
|
+
// Select the empty container
|
|
762
|
+
editor.select(emptyContainerId)
|
|
763
|
+
|
|
764
|
+
// Call selectAll - since the empty container has no children, it should select all siblings (page-level shapes)
|
|
765
|
+
editor.selectAll()
|
|
766
|
+
|
|
767
|
+
// Should select all page-level shapes (including the empty container itself)
|
|
768
|
+
const selectedIds = editor.getSelectedShapeIds()
|
|
769
|
+
expect(Array.from(selectedIds).sort()).toEqual(
|
|
770
|
+
[
|
|
771
|
+
selectAllIds.pageShape1,
|
|
772
|
+
selectAllIds.pageShape2,
|
|
773
|
+
selectAllIds.pageShape3,
|
|
774
|
+
selectAllIds.container1,
|
|
775
|
+
selectAllIds.container2,
|
|
776
|
+
emptyContainerId,
|
|
777
|
+
].sort()
|
|
778
|
+
)
|
|
779
|
+
|
|
780
|
+
// Should NOT include locked shapes or children/grandchildren
|
|
781
|
+
expect(selectedIds).not.toContain(selectAllIds.lockedShape)
|
|
782
|
+
expect(selectedIds).not.toContain(selectAllIds.containerChild1)
|
|
783
|
+
expect(selectedIds).not.toContain(selectAllIds.containerChild2)
|
|
784
|
+
expect(selectedIds).not.toContain(selectAllIds.containerChild3)
|
|
785
|
+
expect(selectedIds).not.toContain(selectAllIds.containerGrandchild1)
|
|
786
|
+
expect(selectedIds).not.toContain(selectAllIds.container2Child1)
|
|
787
|
+
expect(selectedIds).not.toContain(selectAllIds.container2Child2)
|
|
788
|
+
expect(selectedIds).not.toContain(selectAllIds.container2Grandchild1)
|
|
789
|
+
})
|
|
790
|
+
|
|
791
|
+
it('should work correctly when selecting all shapes of same parent type', () => {
|
|
792
|
+
// Select all container children
|
|
793
|
+
editor.select(
|
|
794
|
+
selectAllIds.containerChild1,
|
|
795
|
+
selectAllIds.containerChild2,
|
|
796
|
+
selectAllIds.containerChild3
|
|
797
|
+
)
|
|
798
|
+
|
|
799
|
+
// Call selectAll - should maintain the same selection since all children are already selected
|
|
800
|
+
editor.selectAll()
|
|
801
|
+
|
|
802
|
+
// Should still have all container children selected
|
|
803
|
+
const selectedIds = editor.getSelectedShapeIds()
|
|
804
|
+
expect(Array.from(selectedIds).sort()).toEqual(
|
|
805
|
+
[
|
|
806
|
+
selectAllIds.containerChild1,
|
|
807
|
+
selectAllIds.containerChild2,
|
|
808
|
+
selectAllIds.containerChild3,
|
|
809
|
+
].sort()
|
|
810
|
+
)
|
|
811
|
+
})
|
|
812
|
+
|
|
813
|
+
it('should handle mixed selection levels gracefully by doing nothing', () => {
|
|
814
|
+
// Select a mix: page shape (parent=page), container (parent=page), and container child (parent=container1)
|
|
815
|
+
// These all have different parent IDs so selectAll should do nothing
|
|
816
|
+
editor.select(selectAllIds.pageShape1, selectAllIds.containerChild1)
|
|
817
|
+
|
|
818
|
+
const initialSelectedIds = Array.from(editor.getSelectedShapeIds())
|
|
819
|
+
|
|
820
|
+
// Spy on setSelectedShapes to verify it's not called
|
|
821
|
+
const setSelectedShapesSpy = jest.spyOn(editor, 'setSelectedShapes')
|
|
822
|
+
|
|
823
|
+
// Call selectAll
|
|
824
|
+
editor.selectAll()
|
|
825
|
+
|
|
826
|
+
// Selection should remain unchanged since shapes have different parents
|
|
827
|
+
expect(Array.from(editor.getSelectedShapeIds())).toEqual(initialSelectedIds)
|
|
828
|
+
|
|
829
|
+
// setSelectedShapes should not have been called
|
|
830
|
+
expect(setSelectedShapesSpy).not.toHaveBeenCalled()
|
|
831
|
+
|
|
832
|
+
setSelectedShapesSpy.mockRestore()
|
|
833
|
+
})
|
|
834
|
+
})
|