@ankhorage/zora 25.8.0 → 25.8.2
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/CHANGELOG.md +12 -0
- package/README.md +1 -1
- package/dist/features/grid-interactions/application/createGridInteractionsController.d.ts.map +1 -1
- package/dist/features/grid-interactions/application/createGridInteractionsController.js +21 -12
- package/dist/features/grid-interactions/application/createGridInteractionsController.js.map +1 -1
- package/package.json +4 -2
- package/src/features/grid-interactions/application/createGridInteractionsController.test.ts +114 -0
- package/src/features/grid-interactions/application/createGridInteractionsController.ts +33 -16
- package/src/nativePeerContract.test.ts +53 -0
- package/web-dist/components/grid-interactions/GridInteractions.js +19 -11
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 25.8.2
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- ed63654: Reconcile ZORA's published native peer contract with the released Surface Expo SDK 57 tuple.
|
|
8
|
+
|
|
9
|
+
## 25.8.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- 7a83559: Keep controlled GridInteractions move, resize, and pan gestures anchored to their start state.
|
|
14
|
+
|
|
3
15
|
## 25.8.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
# @ankhorage/zora
|
|
5
5
|
|
|
6
|
-
         
|
|
7
7
|
|
|
8
8
|
Opinionated React Native and React Native Web UI kit built on @ankhorage/surface.
|
|
9
9
|
|
package/dist/features/grid-interactions/application/createGridInteractionsController.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createGridInteractionsController.d.ts","sourceRoot":"","sources":["../../../../src/features/grid-interactions/application/createGridInteractionsController.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"createGridInteractionsController.d.ts","sourceRoot":"","sources":["../../../../src/features/grid-interactions/application/createGridInteractionsController.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAIV,0BAA0B,EAC1B,+BAA+B,EAChC,MAAM,kCAAkC,CAAC;AAqB1C,kGAAkG;AAClG,wBAAgB,gCAAgC,CAC9C,KAAK,EAAE,+BAA+B,GACrC,0BAA0B,CAoF5B"}
|
|
@@ -4,8 +4,8 @@ export function createGridInteractionsController(props) {
|
|
|
4
4
|
let currentProps = props;
|
|
5
5
|
let state = { kind: 'idle' };
|
|
6
6
|
let previousIntent;
|
|
7
|
-
const point = (pointer) => Number.isFinite(pointer.x) && Number.isFinite(pointer.y)
|
|
8
|
-
? viewportToWorld(pointer,
|
|
7
|
+
const point = (pointer, viewport = currentProps.viewport) => Number.isFinite(pointer.x) && Number.isFinite(pointer.y)
|
|
8
|
+
? viewportToWorld(pointer, viewport)
|
|
9
9
|
: undefined;
|
|
10
10
|
const emit = (intent) => {
|
|
11
11
|
if (JSON.stringify(previousIntent) === JSON.stringify(intent))
|
|
@@ -21,27 +21,33 @@ export function createGridInteractionsController(props) {
|
|
|
21
21
|
if (start === undefined)
|
|
22
22
|
return;
|
|
23
23
|
if (pointer.spaceKey === true || pointer.altKey === true) {
|
|
24
|
-
state = { kind: 'pan', start };
|
|
24
|
+
state = { kind: 'pan', start, viewport: currentProps.viewport };
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
27
|
const target = hitTestWorldRects(currentProps.items, start);
|
|
28
28
|
if (target === undefined || isProtected(target)) {
|
|
29
|
-
state = { kind: 'marquee', start };
|
|
29
|
+
state = { kind: 'marquee', start, viewport: currentProps.viewport };
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
if (handle !== undefined && target.resizable !== false) {
|
|
33
|
-
state = { kind: 'resize', start, item: target, handle };
|
|
33
|
+
state = { kind: 'resize', start, viewport: currentProps.viewport, item: target, handle };
|
|
34
34
|
return;
|
|
35
35
|
}
|
|
36
36
|
const itemIds = currentProps.selectedIds?.includes(target.id) === true
|
|
37
37
|
? currentProps.selectedIds
|
|
38
38
|
: [target.id];
|
|
39
|
-
state = {
|
|
39
|
+
state = {
|
|
40
|
+
kind: 'move',
|
|
41
|
+
start,
|
|
42
|
+
viewport: currentProps.viewport,
|
|
43
|
+
itemIds,
|
|
44
|
+
items: currentProps.items.filter((item) => itemIds.includes(item.id)),
|
|
45
|
+
};
|
|
40
46
|
},
|
|
41
47
|
move: (pointer) => {
|
|
42
48
|
if (!canEdit(currentProps))
|
|
43
49
|
return;
|
|
44
|
-
const current = point(pointer);
|
|
50
|
+
const current = point(pointer, viewportForState(state, currentProps.viewport));
|
|
45
51
|
if (current !== undefined)
|
|
46
52
|
emitPreview(state, current, currentProps, emit);
|
|
47
53
|
},
|
|
@@ -50,7 +56,7 @@ export function createGridInteractionsController(props) {
|
|
|
50
56
|
state = { kind: 'idle' };
|
|
51
57
|
return;
|
|
52
58
|
}
|
|
53
|
-
const current = point(pointer);
|
|
59
|
+
const current = point(pointer, viewportForState(state, currentProps.viewport));
|
|
54
60
|
if (current !== undefined)
|
|
55
61
|
emitPreview(state, current, currentProps, emit);
|
|
56
62
|
state = { kind: 'idle' };
|
|
@@ -97,7 +103,7 @@ function emitPreview(state, current, props, emit) {
|
|
|
97
103
|
emit({
|
|
98
104
|
type: 'pan',
|
|
99
105
|
itemIds: [],
|
|
100
|
-
viewport: panViewport(
|
|
106
|
+
viewport: panViewport(state.viewport, {
|
|
101
107
|
x: state.start.x - current.x,
|
|
102
108
|
y: state.start.y - current.y,
|
|
103
109
|
}),
|
|
@@ -117,8 +123,7 @@ function emitPreview(state, current, props, emit) {
|
|
|
117
123
|
}
|
|
118
124
|
const delta = resolveSnapDelta(state, current, props);
|
|
119
125
|
if (state.kind === 'move') {
|
|
120
|
-
|
|
121
|
-
emit({ type: 'move', itemIds: state.itemIds, rects: moveWorldRects(selected, delta) });
|
|
126
|
+
emit({ type: 'move', itemIds: state.itemIds, rects: moveWorldRects(state.items, delta) });
|
|
122
127
|
return;
|
|
123
128
|
}
|
|
124
129
|
emit({
|
|
@@ -135,7 +140,7 @@ function resolveSnapDelta(state, current, props) {
|
|
|
135
140
|
const raw = { x: current.x - state.start.x, y: current.y - state.start.y };
|
|
136
141
|
if (props.snap?.enabled !== true)
|
|
137
142
|
return raw;
|
|
138
|
-
const anchor = state.kind === 'resize' ? state.item :
|
|
143
|
+
const anchor = state.kind === 'resize' ? state.item : state.items.at(0);
|
|
139
144
|
if (anchor === undefined)
|
|
140
145
|
return raw;
|
|
141
146
|
const options = {
|
|
@@ -154,6 +159,10 @@ function resolveSnapDelta(state, current, props) {
|
|
|
154
159
|
}) - anchor.y,
|
|
155
160
|
};
|
|
156
161
|
}
|
|
162
|
+
/*** Keep pointer coordinates in the original gesture coordinate space across controlled updates. */
|
|
163
|
+
function viewportForState(state, fallback) {
|
|
164
|
+
return state.kind === 'idle' ? fallback : state.viewport;
|
|
165
|
+
}
|
|
157
166
|
/*** Keep all caller-declared protected items out of selection and edit intents. */
|
|
158
167
|
function isProtected(item) {
|
|
159
168
|
return (item.disabled === true ||
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"createGridInteractionsController.js","sourceRoot":"","sources":["../../../../src/features/grid-interactions/application/createGridInteractionsController.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type GridPoint,\n type GridRect,\n type GridResizeHandle,\n hitTestWorldRects,\n moveWorldRects,\n panViewport,\n resizeWorldRect,\n resolveWorldSnapCandidate,\n selectWorldRects,\n viewportToWorld,\n} from '@ankhorage/grid-view';\n\nimport type {\n GridInteractionIntent,\n GridInteractionItem,\n GridInteractionPointer,\n GridInteractionsController,\n GridInteractionsControllerProps,\n} from '../../../types/grid-interactions';\n\ntype InteractionState =\n | { readonly kind: 'idle' }\n | { readonly kind: 'marquee'; readonly start: GridPoint }\n | { readonly kind: 'pan'; readonly start: GridPoint }\n | { readonly kind: 'move'; readonly start: GridPoint; readonly itemIds: readonly string[] }\n | {\n readonly kind: 'resize';\n readonly start: GridPoint;\n readonly item: GridInteractionItem;\n readonly handle: GridResizeHandle;\n };\n\n/*** Create a platform-neutral controlled interaction controller using grid-view geometry only. */\nexport function createGridInteractionsController(\n props: GridInteractionsControllerProps,\n): GridInteractionsController {\n let currentProps = props;\n let state: InteractionState = { kind: 'idle' };\n let previousIntent: GridInteractionIntent | undefined;\n const point = (pointer: GridInteractionPointer) =>\n Number.isFinite(pointer.x) && Number.isFinite(pointer.y)\n ? viewportToWorld(pointer, currentProps.viewport)\n : undefined;\n const emit = (intent: GridInteractionIntent) => {\n if (JSON.stringify(previousIntent) === JSON.stringify(intent)) return;\n previousIntent = intent;\n currentProps.onIntent(intent);\n };\n\n return {\n begin: (pointer, handle) => {\n if (!canEdit(currentProps)) return;\n const start = point(pointer);\n if (start === undefined) return;\n if (pointer.spaceKey === true || pointer.altKey === true) {\n state = { kind: 'pan', start };\n return;\n }\n const target = hitTestWorldRects(currentProps.items, start);\n if (target === undefined || isProtected(target)) {\n state = { kind: 'marquee', start };\n return;\n }\n if (handle !== undefined && target.resizable !== false) {\n state = { kind: 'resize', start, item: target, handle };\n return;\n }\n const itemIds =\n currentProps.selectedIds?.includes(target.id) === true\n ? currentProps.selectedIds\n : [target.id];\n state = { kind: 'move', start, itemIds };\n },\n move: (pointer) => {\n if (!canEdit(currentProps)) return;\n const current = point(pointer);\n if (current !== undefined) emitPreview(state, current, currentProps, emit);\n },\n end: (pointer) => {\n if (!canEdit(currentProps)) {\n state = { kind: 'idle' };\n return;\n }\n const current = point(pointer);\n if (current !== undefined) emitPreview(state, current, currentProps, emit);\n state = { kind: 'idle' };\n },\n cancel: () => {\n state = { kind: 'idle' };\n },\n keyDown: (key, pointer = { x: 0, y: 0 }) => {\n if (!canEdit(currentProps)) return false;\n const focusedId = currentProps.selectedIds?.at(-1);\n const focused = currentProps.items.find((item) => item.id === focusedId);\n if (!focused || isProtected(focused)) return false;\n const increment = pointer.shiftKey === true ? 10 : 1;\n const delta = keyToDelta(key, increment);\n if (delta === undefined) return false;\n if (pointer.altKey === true) {\n emit({\n type: 'resize',\n itemIds: [focused.id],\n rects: [resizeWorldRect(focused, resizeHandleForDelta(delta), delta)],\n });\n return true;\n }\n emit({ type: 'move', itemIds: [focused.id], rects: moveWorldRects([focused], delta) });\n return true;\n },\n update: (nextProps) => {\n currentProps = nextProps;\n },\n };\n}\n\n/*** Determines whether the component-wide interaction policy permits edit intents. */\nfunction canEdit(props: GridInteractionsControllerProps): boolean {\n return props.interactionPolicy !== 'passive';\n}\n\n/*** Emit one controlled preview/intention without retaining or mutating caller-owned item data. */\nfunction emitPreview(\n state: InteractionState,\n current: GridPoint,\n props: GridInteractionsControllerProps,\n emit: (intent: GridInteractionIntent) => void,\n) {\n if (state.kind === 'idle') return;\n if (state.kind === 'pan') {\n emit({\n type: 'pan',\n itemIds: [],\n viewport: panViewport(props.viewport, {\n x: state.start.x - current.x,\n y: state.start.y - current.y,\n }),\n });\n return;\n }\n if (state.kind === 'marquee') {\n const marquee = normalizeRect(state.start, current);\n const itemIds = selectWorldRects(props.items, marquee, {\n mode: props.marqueeMode ?? 'intersect',\n }).filter((id) => {\n const item = props.items.find((candidate) => candidate.id === id);\n return item !== undefined && !isProtected(item);\n });\n emit({ type: 'marquee', itemIds, marquee });\n return;\n }\n const delta = resolveSnapDelta(state, current, props);\n if (state.kind === 'move') {\n const selected = props.items.filter((item) => state.itemIds.includes(item.id));\n emit({ type: 'move', itemIds: state.itemIds, rects: moveWorldRects(selected, delta) });\n return;\n }\n emit({\n type: 'resize',\n itemIds: [state.item.id],\n rects: [resizeWorldRect(state.item, state.handle, delta)],\n });\n}\n\n/*** Resolve explicit per-axis candidate snapping without inventing geometry outside grid-view. */\nfunction resolveSnapDelta(\n state: InteractionState,\n current: GridPoint,\n props: GridInteractionsControllerProps,\n): GridPoint {\n if (state.kind === 'idle' || state.kind === 'marquee' || state.kind === 'pan') {\n return { x: 0, y: 0 };\n }\n const raw = { x: current.x - state.start.x, y: current.y - state.start.y };\n if (props.snap?.enabled !== true) return raw;\n const anchor =\n state.kind === 'resize' ? state.item : props.items.find((item) => item.id === state.itemIds[0]);\n if (anchor === undefined) return raw;\n const options = {\n enabled: true,\n tolerancePixels: props.snap.tolerancePixels,\n priorities: props.snap.priorities,\n };\n return {\n x:\n resolveWorldSnapCandidate(anchor.x + raw.x, props.snap.x ?? [], {\n ...options,\n pixelsPerUnit: props.viewport.pixelsPerUnitX,\n }) - anchor.x,\n y:\n resolveWorldSnapCandidate(anchor.y + raw.y, props.snap.y ?? [], {\n ...options,\n pixelsPerUnit: props.viewport.pixelsPerUnitY,\n }) - anchor.y,\n };\n}\n\n/*** Keep all caller-declared protected items out of selection and edit intents. */\nfunction isProtected(item: GridInteractionItem): boolean {\n return (\n item.disabled === true ||\n item.locked === true ||\n item.readOnly === true ||\n item.passive === true\n );\n}\n\n/*** Normalize an arbitrary-direction drag into the world rectangle expected by grid-view selection. */\nfunction normalizeRect(start: GridPoint, end: GridPoint): GridRect {\n return {\n x: Math.min(start.x, end.x),\n y: Math.min(start.y, end.y),\n width: Math.abs(end.x - start.x),\n height: Math.abs(end.y - start.y),\n };\n}\n\n/** Maps accessible arrow keys to caller-controlled world-space edit increments. */\nfunction keyToDelta(key: string, increment: number): GridPoint | undefined {\n if (key === 'ArrowUp') return { x: 0, y: -increment };\n if (key === 'ArrowRight') return { x: increment, y: 0 };\n if (key === 'ArrowDown') return { x: 0, y: increment };\n if (key === 'ArrowLeft') return { x: -increment, y: 0 };\n return undefined;\n}\n\n/** Chooses the physical edge matching an accessible resize direction. */\nfunction resizeHandleForDelta(delta: GridPoint): GridResizeHandle {\n if (delta.x < 0) return delta.y < 0 ? 'top-left' : delta.y > 0 ? 'bottom-left' : 'left';\n return delta.y < 0 ? 'top-right' : delta.y > 0 ? 'bottom-right' : 'right';\n}\n"]}
|
|
1
|
+
{"version":3,"file":"createGridInteractionsController.js","sourceRoot":"","sources":["../../../../src/features/grid-interactions/application/createGridInteractionsController.ts"],"names":[],"mappings":"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","sourcesContent":["import {\n type GridPoint,\n type GridRect,\n type GridResizeHandle,\n type GridViewport,\n hitTestWorldRects,\n moveWorldRects,\n panViewport,\n resizeWorldRect,\n resolveWorldSnapCandidate,\n selectWorldRects,\n viewportToWorld,\n} from '@ankhorage/grid-view';\n\nimport type {\n GridInteractionIntent,\n GridInteractionItem,\n GridInteractionPointer,\n GridInteractionsController,\n GridInteractionsControllerProps,\n} from '../../../types/grid-interactions';\n\ntype InteractionState =\n | { readonly kind: 'idle' }\n | { readonly kind: 'marquee'; readonly start: GridPoint; readonly viewport: GridViewport }\n | { readonly kind: 'pan'; readonly start: GridPoint; readonly viewport: GridViewport }\n | {\n readonly kind: 'move';\n readonly start: GridPoint;\n readonly viewport: GridViewport;\n readonly itemIds: readonly string[];\n readonly items: readonly GridInteractionItem[];\n }\n | {\n readonly kind: 'resize';\n readonly start: GridPoint;\n readonly viewport: GridViewport;\n readonly item: GridInteractionItem;\n readonly handle: GridResizeHandle;\n };\n\n/*** Create a platform-neutral controlled interaction controller using grid-view geometry only. */\nexport function createGridInteractionsController(\n props: GridInteractionsControllerProps,\n): GridInteractionsController {\n let currentProps = props;\n let state: InteractionState = { kind: 'idle' };\n let previousIntent: GridInteractionIntent | undefined;\n const point = (pointer: GridInteractionPointer, viewport = currentProps.viewport) =>\n Number.isFinite(pointer.x) && Number.isFinite(pointer.y)\n ? viewportToWorld(pointer, viewport)\n : undefined;\n const emit = (intent: GridInteractionIntent) => {\n if (JSON.stringify(previousIntent) === JSON.stringify(intent)) return;\n previousIntent = intent;\n currentProps.onIntent(intent);\n };\n\n return {\n begin: (pointer, handle) => {\n if (!canEdit(currentProps)) return;\n const start = point(pointer);\n if (start === undefined) return;\n if (pointer.spaceKey === true || pointer.altKey === true) {\n state = { kind: 'pan', start, viewport: currentProps.viewport };\n return;\n }\n const target = hitTestWorldRects(currentProps.items, start);\n if (target === undefined || isProtected(target)) {\n state = { kind: 'marquee', start, viewport: currentProps.viewport };\n return;\n }\n if (handle !== undefined && target.resizable !== false) {\n state = { kind: 'resize', start, viewport: currentProps.viewport, item: target, handle };\n return;\n }\n const itemIds =\n currentProps.selectedIds?.includes(target.id) === true\n ? currentProps.selectedIds\n : [target.id];\n state = {\n kind: 'move',\n start,\n viewport: currentProps.viewport,\n itemIds,\n items: currentProps.items.filter((item) => itemIds.includes(item.id)),\n };\n },\n move: (pointer) => {\n if (!canEdit(currentProps)) return;\n const current = point(pointer, viewportForState(state, currentProps.viewport));\n if (current !== undefined) emitPreview(state, current, currentProps, emit);\n },\n end: (pointer) => {\n if (!canEdit(currentProps)) {\n state = { kind: 'idle' };\n return;\n }\n const current = point(pointer, viewportForState(state, currentProps.viewport));\n if (current !== undefined) emitPreview(state, current, currentProps, emit);\n state = { kind: 'idle' };\n },\n cancel: () => {\n state = { kind: 'idle' };\n },\n keyDown: (key, pointer = { x: 0, y: 0 }) => {\n if (!canEdit(currentProps)) return false;\n const focusedId = currentProps.selectedIds?.at(-1);\n const focused = currentProps.items.find((item) => item.id === focusedId);\n if (!focused || isProtected(focused)) return false;\n const increment = pointer.shiftKey === true ? 10 : 1;\n const delta = keyToDelta(key, increment);\n if (delta === undefined) return false;\n if (pointer.altKey === true) {\n emit({\n type: 'resize',\n itemIds: [focused.id],\n rects: [resizeWorldRect(focused, resizeHandleForDelta(delta), delta)],\n });\n return true;\n }\n emit({ type: 'move', itemIds: [focused.id], rects: moveWorldRects([focused], delta) });\n return true;\n },\n update: (nextProps) => {\n currentProps = nextProps;\n },\n };\n}\n\n/*** Determines whether the component-wide interaction policy permits edit intents. */\nfunction canEdit(props: GridInteractionsControllerProps): boolean {\n return props.interactionPolicy !== 'passive';\n}\n\n/*** Emit one controlled preview/intention without retaining or mutating caller-owned item data. */\nfunction emitPreview(\n state: InteractionState,\n current: GridPoint,\n props: GridInteractionsControllerProps,\n emit: (intent: GridInteractionIntent) => void,\n) {\n if (state.kind === 'idle') return;\n if (state.kind === 'pan') {\n emit({\n type: 'pan',\n itemIds: [],\n viewport: panViewport(state.viewport, {\n x: state.start.x - current.x,\n y: state.start.y - current.y,\n }),\n });\n return;\n }\n if (state.kind === 'marquee') {\n const marquee = normalizeRect(state.start, current);\n const itemIds = selectWorldRects(props.items, marquee, {\n mode: props.marqueeMode ?? 'intersect',\n }).filter((id) => {\n const item = props.items.find((candidate) => candidate.id === id);\n return item !== undefined && !isProtected(item);\n });\n emit({ type: 'marquee', itemIds, marquee });\n return;\n }\n const delta = resolveSnapDelta(state, current, props);\n if (state.kind === 'move') {\n emit({ type: 'move', itemIds: state.itemIds, rects: moveWorldRects(state.items, delta) });\n return;\n }\n emit({\n type: 'resize',\n itemIds: [state.item.id],\n rects: [resizeWorldRect(state.item, state.handle, delta)],\n });\n}\n\n/*** Resolve explicit per-axis candidate snapping without inventing geometry outside grid-view. */\nfunction resolveSnapDelta(\n state: InteractionState,\n current: GridPoint,\n props: GridInteractionsControllerProps,\n): GridPoint {\n if (state.kind === 'idle' || state.kind === 'marquee' || state.kind === 'pan') {\n return { x: 0, y: 0 };\n }\n const raw = { x: current.x - state.start.x, y: current.y - state.start.y };\n if (props.snap?.enabled !== true) return raw;\n const anchor = state.kind === 'resize' ? state.item : state.items.at(0);\n if (anchor === undefined) return raw;\n const options = {\n enabled: true,\n tolerancePixels: props.snap.tolerancePixels,\n priorities: props.snap.priorities,\n };\n return {\n x:\n resolveWorldSnapCandidate(anchor.x + raw.x, props.snap.x ?? [], {\n ...options,\n pixelsPerUnit: props.viewport.pixelsPerUnitX,\n }) - anchor.x,\n y:\n resolveWorldSnapCandidate(anchor.y + raw.y, props.snap.y ?? [], {\n ...options,\n pixelsPerUnit: props.viewport.pixelsPerUnitY,\n }) - anchor.y,\n };\n}\n\n/*** Keep pointer coordinates in the original gesture coordinate space across controlled updates. */\nfunction viewportForState(state: InteractionState, fallback: GridViewport): GridViewport {\n return state.kind === 'idle' ? fallback : state.viewport;\n}\n\n/*** Keep all caller-declared protected items out of selection and edit intents. */\nfunction isProtected(item: GridInteractionItem): boolean {\n return (\n item.disabled === true ||\n item.locked === true ||\n item.readOnly === true ||\n item.passive === true\n );\n}\n\n/*** Normalize an arbitrary-direction drag into the world rectangle expected by grid-view selection. */\nfunction normalizeRect(start: GridPoint, end: GridPoint): GridRect {\n return {\n x: Math.min(start.x, end.x),\n y: Math.min(start.y, end.y),\n width: Math.abs(end.x - start.x),\n height: Math.abs(end.y - start.y),\n };\n}\n\n/** Maps accessible arrow keys to caller-controlled world-space edit increments. */\nfunction keyToDelta(key: string, increment: number): GridPoint | undefined {\n if (key === 'ArrowUp') return { x: 0, y: -increment };\n if (key === 'ArrowRight') return { x: increment, y: 0 };\n if (key === 'ArrowDown') return { x: 0, y: increment };\n if (key === 'ArrowLeft') return { x: -increment, y: 0 };\n return undefined;\n}\n\n/** Chooses the physical edge matching an accessible resize direction. */\nfunction resizeHandleForDelta(delta: GridPoint): GridResizeHandle {\n if (delta.x < 0) return delta.y < 0 ? 'top-left' : delta.y > 0 ? 'bottom-left' : 'left';\n return delta.y < 0 ? 'top-right' : delta.y > 0 ? 'bottom-right' : 'right';\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ankhorage/zora",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "25.8.
|
|
4
|
+
"version": "25.8.2",
|
|
5
5
|
"description": "Opinionated React Native and React Native Web UI kit built on @ankhorage/surface.",
|
|
6
6
|
"homepage": "https://github.com/ankhorage/zora#readme",
|
|
7
7
|
"bugs": {
|
|
@@ -30,6 +30,7 @@
|
|
|
30
30
|
"@react-native-vector-icons/fontawesome5": "^13.1.3",
|
|
31
31
|
"@react-native-vector-icons/fontawesome6": "^13.1.4",
|
|
32
32
|
"@react-native-vector-icons/ionicons": "^13.1.3",
|
|
33
|
+
"@react-native-vector-icons/material-design-icons": "^13.1.3",
|
|
33
34
|
"react": "19.2.3",
|
|
34
35
|
"react-native": "0.86.x",
|
|
35
36
|
"react-native-gesture-handler": "~2.32.0",
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
"@ankhorage/grid-view": "^0.5.0",
|
|
49
50
|
"@ankhorage/reader": "^0.1.0",
|
|
50
51
|
"@ankhorage/runtime": "^4.1.1",
|
|
51
|
-
"@ankhorage/surface": "^9.4.
|
|
52
|
+
"@ankhorage/surface": "^9.4.5",
|
|
52
53
|
"@ankhorage/utility": "^1.9.85",
|
|
53
54
|
"chess.js": "^1.4.0",
|
|
54
55
|
"expo-document-picker": "~57.0.3",
|
|
@@ -139,6 +140,7 @@
|
|
|
139
140
|
"@react-native-vector-icons/fontawesome5": "^13.1.4",
|
|
140
141
|
"@react-native-vector-icons/fontawesome6": "^13.1.4",
|
|
141
142
|
"@react-native-vector-icons/ionicons": "^13.1.4",
|
|
143
|
+
"@react-native-vector-icons/material-design-icons": "^13.1.4",
|
|
142
144
|
"@types/bun": "^1.4.2",
|
|
143
145
|
"@types/node": "^26.6.5",
|
|
144
146
|
"@types/react": "^19.3.0",
|
|
@@ -112,4 +112,118 @@ describe('createGridInteractionsController', () => {
|
|
|
112
112
|
{ type: 'resize', itemIds: ['a'], rects: [{ id: 'a', x: 1, y: 1, width: 5, height: 4 }] },
|
|
113
113
|
]);
|
|
114
114
|
});
|
|
115
|
+
|
|
116
|
+
test('does not compound controlled move previews or reapply the release endpoint', () => {
|
|
117
|
+
const intents: unknown[] = [];
|
|
118
|
+
const initialItems = [{ id: 'a', x: 40, y: 40, width: 20, height: 20 }];
|
|
119
|
+
const controller = createGridInteractionsController({
|
|
120
|
+
viewport,
|
|
121
|
+
items: initialItems,
|
|
122
|
+
onIntent: (intent) => intents.push(intent),
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
controller.begin({ x: 80, y: 160 });
|
|
126
|
+
controller.move({ x: 100, y: 160 });
|
|
127
|
+
controller.update({
|
|
128
|
+
viewport,
|
|
129
|
+
items: [{ id: 'a', x: 50, y: 40, width: 20, height: 20 }],
|
|
130
|
+
onIntent: (intent) => intents.push(intent),
|
|
131
|
+
});
|
|
132
|
+
controller.move({ x: 120, y: 160 });
|
|
133
|
+
controller.update({
|
|
134
|
+
viewport,
|
|
135
|
+
items: [{ id: 'a', x: 60, y: 40, width: 20, height: 20 }],
|
|
136
|
+
onIntent: (intent) => intents.push(intent),
|
|
137
|
+
});
|
|
138
|
+
controller.end({ x: 120, y: 160 });
|
|
139
|
+
|
|
140
|
+
expect(intents).toEqual([
|
|
141
|
+
{ type: 'move', itemIds: ['a'], rects: [{ id: 'a', x: 50, y: 40, width: 20, height: 20 }] },
|
|
142
|
+
{ type: 'move', itemIds: ['a'], rects: [{ id: 'a', x: 60, y: 40, width: 20, height: 20 }] },
|
|
143
|
+
]);
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test('keeps controlled resize, negative multi-item movement, cancel, and pan non-compounding', () => {
|
|
147
|
+
const resizeIntents: unknown[] = [];
|
|
148
|
+
const resizeController = createGridInteractionsController({
|
|
149
|
+
viewport,
|
|
150
|
+
items: [{ id: 'a', x: 40, y: 40, width: 20, height: 20 }],
|
|
151
|
+
onIntent: (intent) => resizeIntents.push(intent),
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
resizeController.begin({ x: 120, y: 240 }, 'bottom-right');
|
|
155
|
+
resizeController.move({ x: 140, y: 280 });
|
|
156
|
+
resizeController.update({
|
|
157
|
+
viewport,
|
|
158
|
+
items: [{ id: 'a', x: 40, y: 40, width: 30, height: 30 }],
|
|
159
|
+
onIntent: (intent) => resizeIntents.push(intent),
|
|
160
|
+
});
|
|
161
|
+
resizeController.move({ x: 160, y: 320 });
|
|
162
|
+
resizeController.update({
|
|
163
|
+
viewport,
|
|
164
|
+
items: [{ id: 'a', x: 40, y: 40, width: 40, height: 40 }],
|
|
165
|
+
onIntent: (intent) => resizeIntents.push(intent),
|
|
166
|
+
});
|
|
167
|
+
resizeController.end({ x: 160, y: 320 });
|
|
168
|
+
|
|
169
|
+
const moveIntents: unknown[] = [];
|
|
170
|
+
const moveController = createGridInteractionsController({
|
|
171
|
+
viewport,
|
|
172
|
+
items: [
|
|
173
|
+
{ id: 'a', x: 40, y: 40, width: 20, height: 20 },
|
|
174
|
+
{ id: 'b', x: 80, y: 40, width: 20, height: 20 },
|
|
175
|
+
],
|
|
176
|
+
selectedIds: ['a', 'b'],
|
|
177
|
+
onIntent: (intent) => moveIntents.push(intent),
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
moveController.begin({ x: 80, y: 160 });
|
|
181
|
+
moveController.move({ x: 60, y: 120 });
|
|
182
|
+
moveController.cancel();
|
|
183
|
+
moveController.end({ x: 40, y: 80 });
|
|
184
|
+
|
|
185
|
+
const panIntents: unknown[] = [];
|
|
186
|
+
const panController = createGridInteractionsController({
|
|
187
|
+
viewport,
|
|
188
|
+
items: [],
|
|
189
|
+
onIntent: (intent) => panIntents.push(intent),
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
panController.begin({ x: 20, y: 40, spaceKey: true });
|
|
193
|
+
panController.move({ x: 40, y: 80 });
|
|
194
|
+
panController.update({
|
|
195
|
+
viewport: { ...viewport, offsetX: 5, offsetY: 2.5 },
|
|
196
|
+
items: [],
|
|
197
|
+
onIntent: (intent) => panIntents.push(intent),
|
|
198
|
+
});
|
|
199
|
+
panController.move({ x: 60, y: 120 });
|
|
200
|
+
panController.end({ x: 60, y: 120 });
|
|
201
|
+
|
|
202
|
+
expect(resizeIntents).toEqual([
|
|
203
|
+
{ type: 'resize', itemIds: ['a'], rects: [{ id: 'a', x: 40, y: 40, width: 30, height: 30 }] },
|
|
204
|
+
{ type: 'resize', itemIds: ['a'], rects: [{ id: 'a', x: 40, y: 40, width: 40, height: 40 }] },
|
|
205
|
+
]);
|
|
206
|
+
expect(moveIntents).toEqual([
|
|
207
|
+
{
|
|
208
|
+
type: 'move',
|
|
209
|
+
itemIds: ['a', 'b'],
|
|
210
|
+
rects: [
|
|
211
|
+
{ id: 'a', x: 30, y: 30, width: 20, height: 20 },
|
|
212
|
+
{ id: 'b', x: 70, y: 30, width: 20, height: 20 },
|
|
213
|
+
],
|
|
214
|
+
},
|
|
215
|
+
]);
|
|
216
|
+
expect(panIntents).toEqual([
|
|
217
|
+
{
|
|
218
|
+
type: 'pan',
|
|
219
|
+
itemIds: [],
|
|
220
|
+
viewport: { ...viewport, offsetX: 5, offsetY: 2.5 },
|
|
221
|
+
},
|
|
222
|
+
{
|
|
223
|
+
type: 'pan',
|
|
224
|
+
itemIds: [],
|
|
225
|
+
viewport: { ...viewport, offsetX: 10, offsetY: 5 },
|
|
226
|
+
},
|
|
227
|
+
]);
|
|
228
|
+
});
|
|
115
229
|
});
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
type GridPoint,
|
|
3
3
|
type GridRect,
|
|
4
4
|
type GridResizeHandle,
|
|
5
|
+
type GridViewport,
|
|
5
6
|
hitTestWorldRects,
|
|
6
7
|
moveWorldRects,
|
|
7
8
|
panViewport,
|
|
@@ -21,12 +22,19 @@ import type {
|
|
|
21
22
|
|
|
22
23
|
type InteractionState =
|
|
23
24
|
| { readonly kind: 'idle' }
|
|
24
|
-
| { readonly kind: 'marquee'; readonly start: GridPoint }
|
|
25
|
-
| { readonly kind: 'pan'; readonly start: GridPoint }
|
|
26
|
-
| {
|
|
25
|
+
| { readonly kind: 'marquee'; readonly start: GridPoint; readonly viewport: GridViewport }
|
|
26
|
+
| { readonly kind: 'pan'; readonly start: GridPoint; readonly viewport: GridViewport }
|
|
27
|
+
| {
|
|
28
|
+
readonly kind: 'move';
|
|
29
|
+
readonly start: GridPoint;
|
|
30
|
+
readonly viewport: GridViewport;
|
|
31
|
+
readonly itemIds: readonly string[];
|
|
32
|
+
readonly items: readonly GridInteractionItem[];
|
|
33
|
+
}
|
|
27
34
|
| {
|
|
28
35
|
readonly kind: 'resize';
|
|
29
36
|
readonly start: GridPoint;
|
|
37
|
+
readonly viewport: GridViewport;
|
|
30
38
|
readonly item: GridInteractionItem;
|
|
31
39
|
readonly handle: GridResizeHandle;
|
|
32
40
|
};
|
|
@@ -38,9 +46,9 @@ export function createGridInteractionsController(
|
|
|
38
46
|
let currentProps = props;
|
|
39
47
|
let state: InteractionState = { kind: 'idle' };
|
|
40
48
|
let previousIntent: GridInteractionIntent | undefined;
|
|
41
|
-
const point = (pointer: GridInteractionPointer) =>
|
|
49
|
+
const point = (pointer: GridInteractionPointer, viewport = currentProps.viewport) =>
|
|
42
50
|
Number.isFinite(pointer.x) && Number.isFinite(pointer.y)
|
|
43
|
-
? viewportToWorld(pointer,
|
|
51
|
+
? viewportToWorld(pointer, viewport)
|
|
44
52
|
: undefined;
|
|
45
53
|
const emit = (intent: GridInteractionIntent) => {
|
|
46
54
|
if (JSON.stringify(previousIntent) === JSON.stringify(intent)) return;
|
|
@@ -54,27 +62,33 @@ export function createGridInteractionsController(
|
|
|
54
62
|
const start = point(pointer);
|
|
55
63
|
if (start === undefined) return;
|
|
56
64
|
if (pointer.spaceKey === true || pointer.altKey === true) {
|
|
57
|
-
state = { kind: 'pan', start };
|
|
65
|
+
state = { kind: 'pan', start, viewport: currentProps.viewport };
|
|
58
66
|
return;
|
|
59
67
|
}
|
|
60
68
|
const target = hitTestWorldRects(currentProps.items, start);
|
|
61
69
|
if (target === undefined || isProtected(target)) {
|
|
62
|
-
state = { kind: 'marquee', start };
|
|
70
|
+
state = { kind: 'marquee', start, viewport: currentProps.viewport };
|
|
63
71
|
return;
|
|
64
72
|
}
|
|
65
73
|
if (handle !== undefined && target.resizable !== false) {
|
|
66
|
-
state = { kind: 'resize', start, item: target, handle };
|
|
74
|
+
state = { kind: 'resize', start, viewport: currentProps.viewport, item: target, handle };
|
|
67
75
|
return;
|
|
68
76
|
}
|
|
69
77
|
const itemIds =
|
|
70
78
|
currentProps.selectedIds?.includes(target.id) === true
|
|
71
79
|
? currentProps.selectedIds
|
|
72
80
|
: [target.id];
|
|
73
|
-
state = {
|
|
81
|
+
state = {
|
|
82
|
+
kind: 'move',
|
|
83
|
+
start,
|
|
84
|
+
viewport: currentProps.viewport,
|
|
85
|
+
itemIds,
|
|
86
|
+
items: currentProps.items.filter((item) => itemIds.includes(item.id)),
|
|
87
|
+
};
|
|
74
88
|
},
|
|
75
89
|
move: (pointer) => {
|
|
76
90
|
if (!canEdit(currentProps)) return;
|
|
77
|
-
const current = point(pointer);
|
|
91
|
+
const current = point(pointer, viewportForState(state, currentProps.viewport));
|
|
78
92
|
if (current !== undefined) emitPreview(state, current, currentProps, emit);
|
|
79
93
|
},
|
|
80
94
|
end: (pointer) => {
|
|
@@ -82,7 +96,7 @@ export function createGridInteractionsController(
|
|
|
82
96
|
state = { kind: 'idle' };
|
|
83
97
|
return;
|
|
84
98
|
}
|
|
85
|
-
const current = point(pointer);
|
|
99
|
+
const current = point(pointer, viewportForState(state, currentProps.viewport));
|
|
86
100
|
if (current !== undefined) emitPreview(state, current, currentProps, emit);
|
|
87
101
|
state = { kind: 'idle' };
|
|
88
102
|
},
|
|
@@ -131,7 +145,7 @@ function emitPreview(
|
|
|
131
145
|
emit({
|
|
132
146
|
type: 'pan',
|
|
133
147
|
itemIds: [],
|
|
134
|
-
viewport: panViewport(
|
|
148
|
+
viewport: panViewport(state.viewport, {
|
|
135
149
|
x: state.start.x - current.x,
|
|
136
150
|
y: state.start.y - current.y,
|
|
137
151
|
}),
|
|
@@ -151,8 +165,7 @@ function emitPreview(
|
|
|
151
165
|
}
|
|
152
166
|
const delta = resolveSnapDelta(state, current, props);
|
|
153
167
|
if (state.kind === 'move') {
|
|
154
|
-
|
|
155
|
-
emit({ type: 'move', itemIds: state.itemIds, rects: moveWorldRects(selected, delta) });
|
|
168
|
+
emit({ type: 'move', itemIds: state.itemIds, rects: moveWorldRects(state.items, delta) });
|
|
156
169
|
return;
|
|
157
170
|
}
|
|
158
171
|
emit({
|
|
@@ -173,8 +186,7 @@ function resolveSnapDelta(
|
|
|
173
186
|
}
|
|
174
187
|
const raw = { x: current.x - state.start.x, y: current.y - state.start.y };
|
|
175
188
|
if (props.snap?.enabled !== true) return raw;
|
|
176
|
-
const anchor =
|
|
177
|
-
state.kind === 'resize' ? state.item : props.items.find((item) => item.id === state.itemIds[0]);
|
|
189
|
+
const anchor = state.kind === 'resize' ? state.item : state.items.at(0);
|
|
178
190
|
if (anchor === undefined) return raw;
|
|
179
191
|
const options = {
|
|
180
192
|
enabled: true,
|
|
@@ -195,6 +207,11 @@ function resolveSnapDelta(
|
|
|
195
207
|
};
|
|
196
208
|
}
|
|
197
209
|
|
|
210
|
+
/*** Keep pointer coordinates in the original gesture coordinate space across controlled updates. */
|
|
211
|
+
function viewportForState(state: InteractionState, fallback: GridViewport): GridViewport {
|
|
212
|
+
return state.kind === 'idle' ? fallback : state.viewport;
|
|
213
|
+
}
|
|
214
|
+
|
|
198
215
|
/*** Keep all caller-declared protected items out of selection and edit intents. */
|
|
199
216
|
function isProtected(item: GridInteractionItem): boolean {
|
|
200
217
|
return (
|
|
@@ -2,6 +2,22 @@ import { readFile } from 'node:fs/promises';
|
|
|
2
2
|
|
|
3
3
|
import { expect, test } from 'bun:test';
|
|
4
4
|
|
|
5
|
+
const EXPO_57_NATIVE_PEER_VERSIONS = {
|
|
6
|
+
'@react-native-vector-icons/fontawesome': '13.1.4',
|
|
7
|
+
'@react-native-vector-icons/fontawesome5': '13.1.4',
|
|
8
|
+
'@react-native-vector-icons/fontawesome6': '13.1.4',
|
|
9
|
+
'@react-native-vector-icons/ionicons': '13.1.4',
|
|
10
|
+
'@react-native-vector-icons/material-design-icons': '13.1.4',
|
|
11
|
+
react: '19.2.3',
|
|
12
|
+
'react-native': '0.86.3',
|
|
13
|
+
'react-native-gesture-handler': '2.32.0',
|
|
14
|
+
'react-native-reanimated': '4.5.1',
|
|
15
|
+
'react-native-safe-area-context': '5.7.0',
|
|
16
|
+
'react-native-svg': '15.15.4',
|
|
17
|
+
'react-native-web': '0.21.3',
|
|
18
|
+
'react-native-worklets': '0.10.1',
|
|
19
|
+
} as const;
|
|
20
|
+
|
|
5
21
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
6
22
|
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
7
23
|
}
|
|
@@ -20,3 +36,40 @@ test('root-entry native imports expose only required portable peers', async () =
|
|
|
20
36
|
expect(isRecord(peerDependencies) && peerDependencies['expo-font']).toBeUndefined();
|
|
21
37
|
expect(isRecord(peerDependencies) && peerDependencies['@expo/vector-icons']).toBeUndefined();
|
|
22
38
|
});
|
|
39
|
+
|
|
40
|
+
test('declares a jointly satisfiable Expo 57 native peer contract with Surface', async () => {
|
|
41
|
+
const packageJson = JSON.parse(
|
|
42
|
+
await readFile(new URL('../package.json', import.meta.url), 'utf8'),
|
|
43
|
+
) as unknown;
|
|
44
|
+
if (!isRecord(packageJson)) throw new Error('Expected package.json to contain an object.');
|
|
45
|
+
|
|
46
|
+
const { dependencies, peerDependencies } = packageJson;
|
|
47
|
+
if (!isRecord(dependencies) || !isRecord(peerDependencies)) {
|
|
48
|
+
throw new Error('Expected package.json dependency sections to contain objects.');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
expect(dependencies['@ankhorage/surface']).toBe('^9.4.5');
|
|
52
|
+
|
|
53
|
+
const surfacePackageJson = JSON.parse(
|
|
54
|
+
await readFile(
|
|
55
|
+
new URL('../node_modules/@ankhorage/surface/package.json', import.meta.url),
|
|
56
|
+
'utf8',
|
|
57
|
+
),
|
|
58
|
+
) as unknown;
|
|
59
|
+
if (!isRecord(surfacePackageJson) || !isRecord(surfacePackageJson.peerDependencies)) {
|
|
60
|
+
throw new Error('Expected Surface to expose peer dependencies.');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const zoraPeers = new Map(Object.entries(peerDependencies));
|
|
64
|
+
const surfacePeers = new Map(Object.entries(surfacePackageJson.peerDependencies));
|
|
65
|
+
|
|
66
|
+
for (const [name, version] of Object.entries(EXPO_57_NATIVE_PEER_VERSIONS)) {
|
|
67
|
+
const zoraRange = zoraPeers.get(name);
|
|
68
|
+
const surfaceRange = surfacePeers.get(name);
|
|
69
|
+
|
|
70
|
+
expect(typeof zoraRange).toBe('string');
|
|
71
|
+
expect(typeof surfaceRange).toBe('string');
|
|
72
|
+
expect(Bun.semver.satisfies(version, zoraRange as string)).toBe(true);
|
|
73
|
+
expect(Bun.semver.satisfies(version, surfaceRange as string)).toBe(true);
|
|
74
|
+
}
|
|
75
|
+
});
|
|
@@ -24,7 +24,7 @@ function createGridInteractionsController(props) {
|
|
|
24
24
|
let currentProps = props;
|
|
25
25
|
let state = { kind: "idle" };
|
|
26
26
|
let previousIntent;
|
|
27
|
-
const point = (pointer) => Number.isFinite(pointer.x) && Number.isFinite(pointer.y) ? viewportToWorld(pointer,
|
|
27
|
+
const point = (pointer, viewport = currentProps.viewport) => Number.isFinite(pointer.x) && Number.isFinite(pointer.y) ? viewportToWorld(pointer, viewport) : undefined;
|
|
28
28
|
const emit = (intent) => {
|
|
29
29
|
if (JSON.stringify(previousIntent) === JSON.stringify(intent))
|
|
30
30
|
return;
|
|
@@ -39,25 +39,31 @@ function createGridInteractionsController(props) {
|
|
|
39
39
|
if (start === undefined)
|
|
40
40
|
return;
|
|
41
41
|
if (pointer.spaceKey === true || pointer.altKey === true) {
|
|
42
|
-
state = { kind: "pan", start };
|
|
42
|
+
state = { kind: "pan", start, viewport: currentProps.viewport };
|
|
43
43
|
return;
|
|
44
44
|
}
|
|
45
45
|
const target = hitTestWorldRects(currentProps.items, start);
|
|
46
46
|
if (target === undefined || isProtected(target)) {
|
|
47
|
-
state = { kind: "marquee", start };
|
|
47
|
+
state = { kind: "marquee", start, viewport: currentProps.viewport };
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
50
|
if (handle !== undefined && target.resizable !== false) {
|
|
51
|
-
state = { kind: "resize", start, item: target, handle };
|
|
51
|
+
state = { kind: "resize", start, viewport: currentProps.viewport, item: target, handle };
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
const itemIds = currentProps.selectedIds?.includes(target.id) === true ? currentProps.selectedIds : [target.id];
|
|
55
|
-
state = {
|
|
55
|
+
state = {
|
|
56
|
+
kind: "move",
|
|
57
|
+
start,
|
|
58
|
+
viewport: currentProps.viewport,
|
|
59
|
+
itemIds,
|
|
60
|
+
items: currentProps.items.filter((item) => itemIds.includes(item.id))
|
|
61
|
+
};
|
|
56
62
|
},
|
|
57
63
|
move: (pointer) => {
|
|
58
64
|
if (!canEdit(currentProps))
|
|
59
65
|
return;
|
|
60
|
-
const current = point(pointer);
|
|
66
|
+
const current = point(pointer, viewportForState(state, currentProps.viewport));
|
|
61
67
|
if (current !== undefined)
|
|
62
68
|
emitPreview(state, current, currentProps, emit);
|
|
63
69
|
},
|
|
@@ -66,7 +72,7 @@ function createGridInteractionsController(props) {
|
|
|
66
72
|
state = { kind: "idle" };
|
|
67
73
|
return;
|
|
68
74
|
}
|
|
69
|
-
const current = point(pointer);
|
|
75
|
+
const current = point(pointer, viewportForState(state, currentProps.viewport));
|
|
70
76
|
if (current !== undefined)
|
|
71
77
|
emitPreview(state, current, currentProps, emit);
|
|
72
78
|
state = { kind: "idle" };
|
|
@@ -111,7 +117,7 @@ function emitPreview(state, current, props, emit) {
|
|
|
111
117
|
emit({
|
|
112
118
|
type: "pan",
|
|
113
119
|
itemIds: [],
|
|
114
|
-
viewport: panViewport(
|
|
120
|
+
viewport: panViewport(state.viewport, {
|
|
115
121
|
x: state.start.x - current.x,
|
|
116
122
|
y: state.start.y - current.y
|
|
117
123
|
})
|
|
@@ -131,8 +137,7 @@ function emitPreview(state, current, props, emit) {
|
|
|
131
137
|
}
|
|
132
138
|
const delta = resolveSnapDelta(state, current, props);
|
|
133
139
|
if (state.kind === "move") {
|
|
134
|
-
|
|
135
|
-
emit({ type: "move", itemIds: state.itemIds, rects: moveWorldRects(selected, delta) });
|
|
140
|
+
emit({ type: "move", itemIds: state.itemIds, rects: moveWorldRects(state.items, delta) });
|
|
136
141
|
return;
|
|
137
142
|
}
|
|
138
143
|
emit({
|
|
@@ -148,7 +153,7 @@ function resolveSnapDelta(state, current, props) {
|
|
|
148
153
|
const raw = { x: current.x - state.start.x, y: current.y - state.start.y };
|
|
149
154
|
if (props.snap?.enabled !== true)
|
|
150
155
|
return raw;
|
|
151
|
-
const anchor = state.kind === "resize" ? state.item :
|
|
156
|
+
const anchor = state.kind === "resize" ? state.item : state.items.at(0);
|
|
152
157
|
if (anchor === undefined)
|
|
153
158
|
return raw;
|
|
154
159
|
const options = {
|
|
@@ -167,6 +172,9 @@ function resolveSnapDelta(state, current, props) {
|
|
|
167
172
|
}) - anchor.y
|
|
168
173
|
};
|
|
169
174
|
}
|
|
175
|
+
function viewportForState(state, fallback) {
|
|
176
|
+
return state.kind === "idle" ? fallback : state.viewport;
|
|
177
|
+
}
|
|
170
178
|
function isProtected(item) {
|
|
171
179
|
return item.disabled === true || item.locked === true || item.readOnly === true || item.passive === true;
|
|
172
180
|
}
|