mythik-react 0.1.0
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/LICENSE +201 -0
- package/NOTICE +4 -0
- package/README.md +83 -0
- package/dist/MythikApp.d.ts +61 -0
- package/dist/MythikApp.d.ts.map +1 -0
- package/dist/MythikApp.js +381 -0
- package/dist/MythikApp.js.map +1 -0
- package/dist/MythikRenderer.d.ts +31 -0
- package/dist/MythikRenderer.d.ts.map +1 -0
- package/dist/MythikRenderer.js +900 -0
- package/dist/MythikRenderer.js.map +1 -0
- package/dist/animation/index.d.ts +7 -0
- package/dist/animation/index.d.ts.map +1 -0
- package/dist/animation/index.js +5 -0
- package/dist/animation/index.js.map +1 -0
- package/dist/animation/stylesheet-singleton.d.ts +12 -0
- package/dist/animation/stylesheet-singleton.d.ts.map +1 -0
- package/dist/animation/stylesheet-singleton.js +107 -0
- package/dist/animation/stylesheet-singleton.js.map +1 -0
- package/dist/animation/useElementAnimations.d.ts +30 -0
- package/dist/animation/useElementAnimations.d.ts.map +1 -0
- package/dist/animation/useElementAnimations.js +254 -0
- package/dist/animation/useElementAnimations.js.map +1 -0
- package/dist/animation/usePrefersReducedMotion.d.ts +2 -0
- package/dist/animation/usePrefersReducedMotion.d.ts.map +1 -0
- package/dist/animation/usePrefersReducedMotion.js +29 -0
- package/dist/animation/usePrefersReducedMotion.js.map +1 -0
- package/dist/animation/useShapeAnimations.d.ts +21 -0
- package/dist/animation/useShapeAnimations.d.ts.map +1 -0
- package/dist/animation/useShapeAnimations.js +119 -0
- package/dist/animation/useShapeAnimations.js.map +1 -0
- package/dist/app-context.d.ts +15 -0
- package/dist/app-context.d.ts.map +1 -0
- package/dist/app-context.js +9 -0
- package/dist/app-context.js.map +1 -0
- package/dist/background/BackgroundLayer.d.ts +7 -0
- package/dist/background/BackgroundLayer.d.ts.map +1 -0
- package/dist/background/BackgroundLayer.js +50 -0
- package/dist/background/BackgroundLayer.js.map +1 -0
- package/dist/background/BackgroundStack.d.ts +19 -0
- package/dist/background/BackgroundStack.d.ts.map +1 -0
- package/dist/background/BackgroundStack.js +59 -0
- package/dist/background/BackgroundStack.js.map +1 -0
- package/dist/background/BlobLayer.d.ts +12 -0
- package/dist/background/BlobLayer.d.ts.map +1 -0
- package/dist/background/BlobLayer.js +60 -0
- package/dist/background/BlobLayer.js.map +1 -0
- package/dist/background/index.d.ts +3 -0
- package/dist/background/index.d.ts.map +1 -0
- package/dist/background/index.js +3 -0
- package/dist/background/index.js.map +1 -0
- package/dist/css-hover.d.ts +15 -0
- package/dist/css-hover.d.ts.map +1 -0
- package/dist/css-hover.js +51 -0
- package/dist/css-hover.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +11 -0
- package/dist/index.js.map +1 -0
- package/dist/primitives/accordion.d.ts +12 -0
- package/dist/primitives/accordion.d.ts.map +1 -0
- package/dist/primitives/accordion.js +25 -0
- package/dist/primitives/accordion.js.map +1 -0
- package/dist/primitives/area-chart.d.ts +14 -0
- package/dist/primitives/area-chart.d.ts.map +1 -0
- package/dist/primitives/area-chart.js +18 -0
- package/dist/primitives/area-chart.js.map +1 -0
- package/dist/primitives/audio-player.d.ts +9 -0
- package/dist/primitives/audio-player.d.ts.map +1 -0
- package/dist/primitives/audio-player.js +5 -0
- package/dist/primitives/audio-player.js.map +1 -0
- package/dist/primitives/bar-chart.d.ts +14 -0
- package/dist/primitives/bar-chart.d.ts.map +1 -0
- package/dist/primitives/bar-chart.js +22 -0
- package/dist/primitives/bar-chart.js.map +1 -0
- package/dist/primitives/box.d.ts +21 -0
- package/dist/primitives/box.d.ts.map +1 -0
- package/dist/primitives/box.js +54 -0
- package/dist/primitives/box.js.map +1 -0
- package/dist/primitives/button.d.ts +14 -0
- package/dist/primitives/button.d.ts.map +1 -0
- package/dist/primitives/button.js +28 -0
- package/dist/primitives/button.js.map +1 -0
- package/dist/primitives/camera.d.ts +15 -0
- package/dist/primitives/camera.d.ts.map +1 -0
- package/dist/primitives/camera.js +25 -0
- package/dist/primitives/camera.js.map +1 -0
- package/dist/primitives/checkbox.d.ts +12 -0
- package/dist/primitives/checkbox.d.ts.map +1 -0
- package/dist/primitives/checkbox.js +24 -0
- package/dist/primitives/checkbox.js.map +1 -0
- package/dist/primitives/divider.d.ts +9 -0
- package/dist/primitives/divider.d.ts.map +1 -0
- package/dist/primitives/divider.js +10 -0
- package/dist/primitives/divider.js.map +1 -0
- package/dist/primitives/drawer.d.ts +21 -0
- package/dist/primitives/drawer.d.ts.map +1 -0
- package/dist/primitives/drawer.js +38 -0
- package/dist/primitives/drawer.js.map +1 -0
- package/dist/primitives/file-upload.d.ts +27 -0
- package/dist/primitives/file-upload.d.ts.map +1 -0
- package/dist/primitives/file-upload.js +225 -0
- package/dist/primitives/file-upload.js.map +1 -0
- package/dist/primitives/grid.d.ts +13 -0
- package/dist/primitives/grid.d.ts.map +1 -0
- package/dist/primitives/grid.js +13 -0
- package/dist/primitives/grid.js.map +1 -0
- package/dist/primitives/icon.d.ts +22 -0
- package/dist/primitives/icon.d.ts.map +1 -0
- package/dist/primitives/icon.js +52 -0
- package/dist/primitives/icon.js.map +1 -0
- package/dist/primitives/image.d.ts +13 -0
- package/dist/primitives/image.d.ts.map +1 -0
- package/dist/primitives/image.js +38 -0
- package/dist/primitives/image.js.map +1 -0
- package/dist/primitives/index.d.ts +57 -0
- package/dist/primitives/index.d.ts.map +1 -0
- package/dist/primitives/index.js +106 -0
- package/dist/primitives/index.js.map +1 -0
- package/dist/primitives/input.d.ts +32 -0
- package/dist/primitives/input.d.ts.map +1 -0
- package/dist/primitives/input.js +192 -0
- package/dist/primitives/input.js.map +1 -0
- package/dist/primitives/kanban-board.d.ts +13 -0
- package/dist/primitives/kanban-board.d.ts.map +1 -0
- package/dist/primitives/kanban-board.js +5 -0
- package/dist/primitives/kanban-board.js.map +1 -0
- package/dist/primitives/line-chart.d.ts +14 -0
- package/dist/primitives/line-chart.d.ts.map +1 -0
- package/dist/primitives/line-chart.js +17 -0
- package/dist/primitives/line-chart.js.map +1 -0
- package/dist/primitives/list.d.ts +13 -0
- package/dist/primitives/list.d.ts.map +1 -0
- package/dist/primitives/list.js +10 -0
- package/dist/primitives/list.js.map +1 -0
- package/dist/primitives/modal.d.ts +20 -0
- package/dist/primitives/modal.d.ts.map +1 -0
- package/dist/primitives/modal.js +60 -0
- package/dist/primitives/modal.js.map +1 -0
- package/dist/primitives/pie-chart.d.ts +15 -0
- package/dist/primitives/pie-chart.d.ts.map +1 -0
- package/dist/primitives/pie-chart.js +36 -0
- package/dist/primitives/pie-chart.js.map +1 -0
- package/dist/primitives/screen-outlet.d.ts +9 -0
- package/dist/primitives/screen-outlet.d.ts.map +1 -0
- package/dist/primitives/screen-outlet.js +92 -0
- package/dist/primitives/screen-outlet.js.map +1 -0
- package/dist/primitives/screen.d.ts +9 -0
- package/dist/primitives/screen.d.ts.map +1 -0
- package/dist/primitives/screen.js +10 -0
- package/dist/primitives/screen.js.map +1 -0
- package/dist/primitives/scroll.d.ts +11 -0
- package/dist/primitives/scroll.d.ts.map +1 -0
- package/dist/primitives/scroll.js +10 -0
- package/dist/primitives/scroll.js.map +1 -0
- package/dist/primitives/select.d.ts +19 -0
- package/dist/primitives/select.d.ts.map +1 -0
- package/dist/primitives/select.js +109 -0
- package/dist/primitives/select.js.map +1 -0
- package/dist/primitives/signature.d.ts +13 -0
- package/dist/primitives/signature.d.ts.map +1 -0
- package/dist/primitives/signature.js +45 -0
- package/dist/primitives/signature.js.map +1 -0
- package/dist/primitives/skeleton.d.ts +14 -0
- package/dist/primitives/skeleton.d.ts.map +1 -0
- package/dist/primitives/skeleton.js +41 -0
- package/dist/primitives/skeleton.js.map +1 -0
- package/dist/primitives/slider.d.ts +15 -0
- package/dist/primitives/slider.d.ts.map +1 -0
- package/dist/primitives/slider.js +7 -0
- package/dist/primitives/slider.js.map +1 -0
- package/dist/primitives/spacer.d.ts +9 -0
- package/dist/primitives/spacer.d.ts.map +1 -0
- package/dist/primitives/spacer.js +9 -0
- package/dist/primitives/spacer.js.map +1 -0
- package/dist/primitives/spatial-map-editing.d.ts +472 -0
- package/dist/primitives/spatial-map-editing.d.ts.map +1 -0
- package/dist/primitives/spatial-map-editing.js +886 -0
- package/dist/primitives/spatial-map-editing.js.map +1 -0
- package/dist/primitives/spatial-map.d.ts +1073 -0
- package/dist/primitives/spatial-map.d.ts.map +1 -0
- package/dist/primitives/spatial-map.js +1705 -0
- package/dist/primitives/spatial-map.js.map +1 -0
- package/dist/primitives/stack.d.ts +13 -0
- package/dist/primitives/stack.d.ts.map +1 -0
- package/dist/primitives/stack.js +12 -0
- package/dist/primitives/stack.js.map +1 -0
- package/dist/primitives/table.d.ts +115 -0
- package/dist/primitives/table.d.ts.map +1 -0
- package/dist/primitives/table.js +498 -0
- package/dist/primitives/table.js.map +1 -0
- package/dist/primitives/tabs.d.ts +17 -0
- package/dist/primitives/tabs.d.ts.map +1 -0
- package/dist/primitives/tabs.js +13 -0
- package/dist/primitives/tabs.js.map +1 -0
- package/dist/primitives/text.d.ts +11 -0
- package/dist/primitives/text.d.ts.map +1 -0
- package/dist/primitives/text.js +69 -0
- package/dist/primitives/text.js.map +1 -0
- package/dist/primitives/textarea.d.ts +15 -0
- package/dist/primitives/textarea.d.ts.map +1 -0
- package/dist/primitives/textarea.js +23 -0
- package/dist/primitives/textarea.js.map +1 -0
- package/dist/primitives/toast-container.d.ts +15 -0
- package/dist/primitives/toast-container.d.ts.map +1 -0
- package/dist/primitives/toast-container.js +160 -0
- package/dist/primitives/toast-container.js.map +1 -0
- package/dist/primitives/toggle.d.ts +12 -0
- package/dist/primitives/toggle.d.ts.map +1 -0
- package/dist/primitives/toggle.js +18 -0
- package/dist/primitives/toggle.js.map +1 -0
- package/dist/primitives/touchable.d.ts +10 -0
- package/dist/primitives/touchable.d.ts.map +1 -0
- package/dist/primitives/touchable.js +6 -0
- package/dist/primitives/touchable.js.map +1 -0
- package/dist/primitives/use-design-tokens.d.ts +127 -0
- package/dist/primitives/use-design-tokens.d.ts.map +1 -0
- package/dist/primitives/use-design-tokens.js +251 -0
- package/dist/primitives/use-design-tokens.js.map +1 -0
- package/dist/primitives/use-theme.d.ts +11 -0
- package/dist/primitives/use-theme.d.ts.map +1 -0
- package/dist/primitives/use-theme.js +17 -0
- package/dist/primitives/use-theme.js.map +1 -0
- package/dist/primitives/wizard.d.ts +11 -0
- package/dist/primitives/wizard.d.ts.map +1 -0
- package/dist/primitives/wizard.js +15 -0
- package/dist/primitives/wizard.js.map +1 -0
- package/dist/runtime/context-dispatcher.d.ts +3 -0
- package/dist/runtime/context-dispatcher.d.ts.map +1 -0
- package/dist/runtime/context-dispatcher.js +11 -0
- package/dist/runtime/context-dispatcher.js.map +1 -0
- package/dist/runtime/row-dispatcher.d.ts +19 -0
- package/dist/runtime/row-dispatcher.d.ts.map +1 -0
- package/dist/runtime/row-dispatcher.js +25 -0
- package/dist/runtime/row-dispatcher.js.map +1 -0
- package/dist/types.d.ts +10 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/dist/use-device-context.d.ts +8 -0
- package/dist/use-device-context.d.ts.map +1 -0
- package/dist/use-device-context.js +54 -0
- package/dist/use-device-context.js.map +1 -0
- package/package.json +59 -0
|
@@ -0,0 +1,886 @@
|
|
|
1
|
+
export const DEFAULT_GRID_SIZE = 20;
|
|
2
|
+
export const DEFAULT_SNAP_THRESHOLD = 8;
|
|
3
|
+
function finiteNumber(value, fallback) {
|
|
4
|
+
return typeof value === 'number' && Number.isFinite(value) ? value : fallback;
|
|
5
|
+
}
|
|
6
|
+
function positiveFiniteNumber(value, fallback) {
|
|
7
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
8
|
+
}
|
|
9
|
+
function nonNegativeFiniteNumber(value, fallback) {
|
|
10
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : fallback;
|
|
11
|
+
}
|
|
12
|
+
function normalizeScaleLimit(value, fallback) {
|
|
13
|
+
return typeof value === 'number' && Number.isFinite(value) && value > 0 ? value : fallback;
|
|
14
|
+
}
|
|
15
|
+
function normalizeGridSize(size) {
|
|
16
|
+
if (typeof size === 'number') {
|
|
17
|
+
const value = positiveFiniteNumber(size, DEFAULT_GRID_SIZE);
|
|
18
|
+
return { x: value, y: value };
|
|
19
|
+
}
|
|
20
|
+
return {
|
|
21
|
+
x: positiveFiniteNumber(size?.x, DEFAULT_GRID_SIZE),
|
|
22
|
+
y: positiveFiniteNumber(size?.y, DEFAULT_GRID_SIZE),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
function normalizePointConfig(point) {
|
|
26
|
+
return {
|
|
27
|
+
x: finiteNumber(point?.x, 0),
|
|
28
|
+
y: finiteNumber(point?.y, 0),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
function normalizeSnapPolicy(policy) {
|
|
32
|
+
const enabled = policy?.enabled === true;
|
|
33
|
+
return {
|
|
34
|
+
enabled,
|
|
35
|
+
grid: {
|
|
36
|
+
enabled: enabled && policy.grid?.enabled === true,
|
|
37
|
+
size: normalizeGridSize(policy?.grid?.size),
|
|
38
|
+
offset: normalizePointConfig(policy?.grid?.offset),
|
|
39
|
+
threshold: nonNegativeFiniteNumber(policy?.grid?.threshold, DEFAULT_SNAP_THRESHOLD),
|
|
40
|
+
},
|
|
41
|
+
itemCenters: {
|
|
42
|
+
enabled: enabled && policy?.itemCenters?.enabled === true,
|
|
43
|
+
threshold: nonNegativeFiniteNumber(policy?.itemCenters?.threshold, DEFAULT_SNAP_THRESHOLD),
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function normalizeGuidePolicy(policy) {
|
|
48
|
+
const enabled = policy?.enabled === true;
|
|
49
|
+
return {
|
|
50
|
+
enabled,
|
|
51
|
+
showCoordinates: enabled && policy?.showCoordinates === true,
|
|
52
|
+
showSnapLines: enabled && policy?.showSnapLines !== false,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
export function getEditPolicy(mode, policy) {
|
|
56
|
+
const editing = mode === 'edit';
|
|
57
|
+
const configured = {
|
|
58
|
+
bounds: policy?.bounds ?? 'viewBox',
|
|
59
|
+
keyboardStep: policy?.keyboardStep ?? 1,
|
|
60
|
+
keyboardLargeStep: policy?.keyboardLargeStep ?? 10,
|
|
61
|
+
coordinatePrecision: policy?.coordinatePrecision ?? 0,
|
|
62
|
+
};
|
|
63
|
+
const minScale = normalizeScaleLimit(policy?.minScale, 0.1);
|
|
64
|
+
const rawMaxScale = normalizeScaleLimit(policy?.maxScale, 10);
|
|
65
|
+
const maxScale = rawMaxScale > minScale ? rawMaxScale : 10;
|
|
66
|
+
return {
|
|
67
|
+
dragItems: editing && policy?.dragItems !== false,
|
|
68
|
+
dragZones: editing && policy?.dragZones !== false,
|
|
69
|
+
keyboardMoveItems: editing && policy?.keyboardMoveItems !== false,
|
|
70
|
+
keyboardMoveZones: editing && policy?.keyboardMoveZones !== false,
|
|
71
|
+
resizeItems: editing && policy?.resizeItems !== false,
|
|
72
|
+
resizeZones: editing && policy?.resizeZones !== false,
|
|
73
|
+
rotateItems: editing && policy?.rotateItems !== false,
|
|
74
|
+
keyboardResizeItems: editing && policy?.keyboardResizeItems !== false,
|
|
75
|
+
keyboardResizeZones: editing && policy?.keyboardResizeZones !== false,
|
|
76
|
+
keyboardRotateItems: editing && policy?.keyboardRotateItems !== false,
|
|
77
|
+
shapeEditZones: editing && policy?.shapeEditZones !== false,
|
|
78
|
+
keyboardShapeEditZones: editing && policy?.keyboardShapeEditZones !== false,
|
|
79
|
+
bounds: configured.bounds,
|
|
80
|
+
boundsMode: 'position',
|
|
81
|
+
keyboardStep: configured.keyboardStep,
|
|
82
|
+
keyboardLargeStep: configured.keyboardLargeStep,
|
|
83
|
+
resizeStep: positiveFiniteNumber(policy?.resizeStep, 0.05),
|
|
84
|
+
resizeLargeStep: positiveFiniteNumber(policy?.resizeLargeStep, 0.25),
|
|
85
|
+
rotationStep: nonNegativeFiniteNumber(policy?.rotationStep, 5),
|
|
86
|
+
rotationLargeStep: nonNegativeFiniteNumber(policy?.rotationLargeStep, 15),
|
|
87
|
+
minScale,
|
|
88
|
+
maxScale,
|
|
89
|
+
handles: {
|
|
90
|
+
visible: editing && policy?.handles?.visible !== false,
|
|
91
|
+
resize: editing && policy?.handles?.resize !== false,
|
|
92
|
+
rotate: editing && policy?.handles?.rotate !== false,
|
|
93
|
+
},
|
|
94
|
+
coordinatePrecision: configured.coordinatePrecision,
|
|
95
|
+
snap: editing
|
|
96
|
+
? normalizeSnapPolicy(policy?.snap)
|
|
97
|
+
: normalizeSnapPolicy(),
|
|
98
|
+
guides: editing
|
|
99
|
+
? normalizeGuidePolicy(policy?.guides)
|
|
100
|
+
: normalizeGuidePolicy(),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
export function clampPointToRect(point, rect) {
|
|
104
|
+
return {
|
|
105
|
+
x: Math.min(rect.x + rect.width, Math.max(rect.x, point.x)),
|
|
106
|
+
y: Math.min(rect.y + rect.height, Math.max(rect.y, point.y)),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
export function roundPoint(point, precision) {
|
|
110
|
+
const safePrecision = Number.isFinite(precision) && precision > 0 ? Math.floor(precision) : 0;
|
|
111
|
+
const factor = 10 ** safePrecision;
|
|
112
|
+
return {
|
|
113
|
+
x: Math.round(point.x * factor) / factor,
|
|
114
|
+
y: Math.round(point.y * factor) / factor,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
export function normalizeEditPosition(point, rect, policy) {
|
|
118
|
+
const bounded = policy.bounds === 'viewBox'
|
|
119
|
+
? clampPointToRect(point, rect)
|
|
120
|
+
: point;
|
|
121
|
+
return roundPoint(bounded, policy.coordinatePrecision);
|
|
122
|
+
}
|
|
123
|
+
export function normalizeSpatialTransform(transform, policy) {
|
|
124
|
+
const rawScaleX = finiteNumber(transform?.scaleX, 1);
|
|
125
|
+
const rawScaleY = finiteNumber(transform?.scaleY, 1);
|
|
126
|
+
return {
|
|
127
|
+
scaleX: Math.min(policy.maxScale, Math.max(policy.minScale, rawScaleX)),
|
|
128
|
+
scaleY: Math.min(policy.maxScale, Math.max(policy.minScale, rawScaleY)),
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
function finitePoint(point) {
|
|
132
|
+
return Boolean(point
|
|
133
|
+
&& typeof point === 'object'
|
|
134
|
+
&& Number.isFinite(point.x)
|
|
135
|
+
&& Number.isFinite(point.y));
|
|
136
|
+
}
|
|
137
|
+
function parsePolygonPoints(points) {
|
|
138
|
+
if (typeof points === 'string') {
|
|
139
|
+
return points
|
|
140
|
+
.trim()
|
|
141
|
+
.split(/\s+/)
|
|
142
|
+
.map((pair) => {
|
|
143
|
+
const [x, y] = pair.split(',').map(Number);
|
|
144
|
+
return { x, y };
|
|
145
|
+
})
|
|
146
|
+
.filter(finitePoint);
|
|
147
|
+
}
|
|
148
|
+
return points
|
|
149
|
+
.map((point) => (Array.isArray(point) ? { x: point[0], y: point[1] } : point))
|
|
150
|
+
.filter(finitePoint);
|
|
151
|
+
}
|
|
152
|
+
function boundsFromPoints(points) {
|
|
153
|
+
if (points.length === 0)
|
|
154
|
+
return null;
|
|
155
|
+
const xs = points.map((point) => point.x);
|
|
156
|
+
const ys = points.map((point) => point.y);
|
|
157
|
+
const minX = Math.min(...xs);
|
|
158
|
+
const maxX = Math.max(...xs);
|
|
159
|
+
const minY = Math.min(...ys);
|
|
160
|
+
const maxY = Math.max(...ys);
|
|
161
|
+
if (!Number.isFinite(minX) || !Number.isFinite(maxX) || !Number.isFinite(minY) || !Number.isFinite(maxY)) {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
return { x: minX, y: minY, width: maxX - minX, height: maxY - minY };
|
|
165
|
+
}
|
|
166
|
+
export function getSpatialItemLocalBounds(item) {
|
|
167
|
+
if (item.localBounds
|
|
168
|
+
&& Number.isFinite(item.localBounds.x)
|
|
169
|
+
&& Number.isFinite(item.localBounds.y)
|
|
170
|
+
&& Number.isFinite(item.localBounds.width)
|
|
171
|
+
&& Number.isFinite(item.localBounds.height)
|
|
172
|
+
&& item.localBounds.width > 0
|
|
173
|
+
&& item.localBounds.height > 0) {
|
|
174
|
+
return item.localBounds;
|
|
175
|
+
}
|
|
176
|
+
const shape = item.shape;
|
|
177
|
+
if (shape.type === 'rect') {
|
|
178
|
+
return { x: -shape.width / 2, y: -shape.height / 2, width: shape.width, height: shape.height };
|
|
179
|
+
}
|
|
180
|
+
if (shape.type === 'circle') {
|
|
181
|
+
return { x: -shape.radius, y: -shape.radius, width: shape.radius * 2, height: shape.radius * 2 };
|
|
182
|
+
}
|
|
183
|
+
if (shape.type === 'ellipse') {
|
|
184
|
+
return { x: -shape.radiusX, y: -shape.radiusY, width: shape.radiusX * 2, height: shape.radiusY * 2 };
|
|
185
|
+
}
|
|
186
|
+
if (shape.type === 'polygon') {
|
|
187
|
+
return boundsFromPoints(parsePolygonPoints(shape.points));
|
|
188
|
+
}
|
|
189
|
+
return null;
|
|
190
|
+
}
|
|
191
|
+
function validLocalBounds(bounds) {
|
|
192
|
+
return Boolean(bounds
|
|
193
|
+
&& Number.isFinite(bounds.x)
|
|
194
|
+
&& Number.isFinite(bounds.y)
|
|
195
|
+
&& Number.isFinite(bounds.width)
|
|
196
|
+
&& Number.isFinite(bounds.height)
|
|
197
|
+
&& bounds.width > 0
|
|
198
|
+
&& bounds.height > 0);
|
|
199
|
+
}
|
|
200
|
+
export function getSpatialZoneMapBounds(zone) {
|
|
201
|
+
if (validLocalBounds(zone.localBounds))
|
|
202
|
+
return zone.localBounds;
|
|
203
|
+
const shape = zone.shape;
|
|
204
|
+
if (shape.type === 'rect') {
|
|
205
|
+
return { x: shape.x, y: shape.y, width: shape.width, height: shape.height };
|
|
206
|
+
}
|
|
207
|
+
if (shape.type === 'circle') {
|
|
208
|
+
return { x: shape.cx - shape.radius, y: shape.cy - shape.radius, width: shape.radius * 2, height: shape.radius * 2 };
|
|
209
|
+
}
|
|
210
|
+
if (shape.type === 'ellipse') {
|
|
211
|
+
return { x: shape.cx - shape.radiusX, y: shape.cy - shape.radiusY, width: shape.radiusX * 2, height: shape.radiusY * 2 };
|
|
212
|
+
}
|
|
213
|
+
if (shape.type === 'polygon') {
|
|
214
|
+
return boundsFromPoints(parsePolygonPoints(shape.points));
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
export function getSpatialZonePosition(zone) {
|
|
219
|
+
return finitePoint(zone.position) ? zone.position : { x: 0, y: 0 };
|
|
220
|
+
}
|
|
221
|
+
export function normalizeZonePosition(zone, position, rect, policy) {
|
|
222
|
+
if (policy.bounds !== 'viewBox')
|
|
223
|
+
return roundPoint(position, policy.coordinatePrecision);
|
|
224
|
+
const bounds = getSpatialZoneTransformedBounds({ ...zone, position }, policy);
|
|
225
|
+
if (!bounds)
|
|
226
|
+
return roundPoint(position, policy.coordinatePrecision);
|
|
227
|
+
const minX = position.x + rect.x - bounds.x;
|
|
228
|
+
const maxX = position.x + rect.x + rect.width - (bounds.x + bounds.width);
|
|
229
|
+
const minY = position.y + rect.y - bounds.y;
|
|
230
|
+
const maxY = position.y + rect.y + rect.height - (bounds.y + bounds.height);
|
|
231
|
+
const bounded = {
|
|
232
|
+
x: Math.min(Math.max(minX, maxX), Math.max(Math.min(minX, maxX), position.x)),
|
|
233
|
+
y: Math.min(Math.max(minY, maxY), Math.max(Math.min(minY, maxY), position.y)),
|
|
234
|
+
};
|
|
235
|
+
return roundPoint(bounded, policy.coordinatePrecision);
|
|
236
|
+
}
|
|
237
|
+
function rotatePoint(point, degrees) {
|
|
238
|
+
const radians = degrees * Math.PI / 180;
|
|
239
|
+
const cos = Math.cos(radians);
|
|
240
|
+
const sin = Math.sin(radians);
|
|
241
|
+
return {
|
|
242
|
+
x: point.x * cos - point.y * sin,
|
|
243
|
+
y: point.x * sin + point.y * cos,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
export function mapPointToItemLocal(item, point, policy) {
|
|
247
|
+
const transform = normalizeSpatialTransform(item.transform, policy);
|
|
248
|
+
const translated = {
|
|
249
|
+
x: point.x - item.position.x,
|
|
250
|
+
y: point.y - item.position.y,
|
|
251
|
+
};
|
|
252
|
+
const unrotated = rotatePoint(translated, -(item.rotation ?? 0));
|
|
253
|
+
return {
|
|
254
|
+
x: unrotated.x / transform.scaleX,
|
|
255
|
+
y: unrotated.y / transform.scaleY,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
export function itemLocalPointToMap(item, point, transform) {
|
|
259
|
+
const scaled = { x: point.x * transform.scaleX, y: point.y * transform.scaleY };
|
|
260
|
+
const rotated = rotatePoint(scaled, item.rotation ?? 0);
|
|
261
|
+
return {
|
|
262
|
+
x: item.position.x + rotated.x,
|
|
263
|
+
y: item.position.y + rotated.y,
|
|
264
|
+
};
|
|
265
|
+
}
|
|
266
|
+
export function mapPointToZoneLocal(zone, point, policy) {
|
|
267
|
+
const position = getSpatialZonePosition(zone);
|
|
268
|
+
const transform = normalizeSpatialTransform(zone.transform, policy);
|
|
269
|
+
const translated = {
|
|
270
|
+
x: point.x - position.x,
|
|
271
|
+
y: point.y - position.y,
|
|
272
|
+
};
|
|
273
|
+
const unrotated = rotatePoint(translated, -(zone.rotation ?? 0));
|
|
274
|
+
return {
|
|
275
|
+
x: unrotated.x / transform.scaleX,
|
|
276
|
+
y: unrotated.y / transform.scaleY,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
export function zoneLocalPointToMap(zone, point, transform) {
|
|
280
|
+
const position = getSpatialZonePosition(zone);
|
|
281
|
+
const scaled = { x: point.x * transform.scaleX, y: point.y * transform.scaleY };
|
|
282
|
+
const rotated = rotatePoint(scaled, zone.rotation ?? 0);
|
|
283
|
+
return {
|
|
284
|
+
x: position.x + rotated.x,
|
|
285
|
+
y: position.y + rotated.y,
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
function itemLocalDirectionToMap(item, vector) {
|
|
289
|
+
return rotatePoint(vector, item.rotation ?? 0);
|
|
290
|
+
}
|
|
291
|
+
function zoneLocalDirectionToMap(zone, vector) {
|
|
292
|
+
return rotatePoint(vector, zone.rotation ?? 0);
|
|
293
|
+
}
|
|
294
|
+
function handleLocalPoint(bounds, handle) {
|
|
295
|
+
const minX = bounds.x;
|
|
296
|
+
const maxX = bounds.x + bounds.width;
|
|
297
|
+
const midX = bounds.x + bounds.width / 2;
|
|
298
|
+
const minY = bounds.y;
|
|
299
|
+
const maxY = bounds.y + bounds.height;
|
|
300
|
+
const midY = bounds.y + bounds.height / 2;
|
|
301
|
+
return {
|
|
302
|
+
n: { x: midX, y: minY },
|
|
303
|
+
ne: { x: maxX, y: minY },
|
|
304
|
+
e: { x: maxX, y: midY },
|
|
305
|
+
se: { x: maxX, y: maxY },
|
|
306
|
+
s: { x: midX, y: maxY },
|
|
307
|
+
sw: { x: minX, y: maxY },
|
|
308
|
+
w: { x: minX, y: midY },
|
|
309
|
+
nw: { x: minX, y: minY },
|
|
310
|
+
}[handle];
|
|
311
|
+
}
|
|
312
|
+
function oppositeHandle(handle) {
|
|
313
|
+
return {
|
|
314
|
+
n: 's',
|
|
315
|
+
ne: 'sw',
|
|
316
|
+
e: 'w',
|
|
317
|
+
se: 'nw',
|
|
318
|
+
s: 'n',
|
|
319
|
+
sw: 'ne',
|
|
320
|
+
w: 'e',
|
|
321
|
+
nw: 'se',
|
|
322
|
+
}[handle];
|
|
323
|
+
}
|
|
324
|
+
export function getSpatialItemWorldHandlePoints(item, policy) {
|
|
325
|
+
const bounds = getSpatialItemLocalBounds(item);
|
|
326
|
+
if (!bounds)
|
|
327
|
+
return null;
|
|
328
|
+
const transform = normalizeSpatialTransform(item.transform, policy);
|
|
329
|
+
const points = {
|
|
330
|
+
n: itemLocalPointToMap(item, handleLocalPoint(bounds, 'n'), transform),
|
|
331
|
+
ne: itemLocalPointToMap(item, handleLocalPoint(bounds, 'ne'), transform),
|
|
332
|
+
e: itemLocalPointToMap(item, handleLocalPoint(bounds, 'e'), transform),
|
|
333
|
+
se: itemLocalPointToMap(item, handleLocalPoint(bounds, 'se'), transform),
|
|
334
|
+
s: itemLocalPointToMap(item, handleLocalPoint(bounds, 's'), transform),
|
|
335
|
+
sw: itemLocalPointToMap(item, handleLocalPoint(bounds, 'sw'), transform),
|
|
336
|
+
w: itemLocalPointToMap(item, handleLocalPoint(bounds, 'w'), transform),
|
|
337
|
+
nw: itemLocalPointToMap(item, handleLocalPoint(bounds, 'nw'), transform),
|
|
338
|
+
rotate: { x: 0, y: 0 },
|
|
339
|
+
};
|
|
340
|
+
const top = points.n;
|
|
341
|
+
const outward = itemLocalDirectionToMap(item, { x: 0, y: -1 });
|
|
342
|
+
const rotateGap = Math.max(28, Math.min(56, bounds.height * 0.35));
|
|
343
|
+
points.rotate = {
|
|
344
|
+
x: top.x + outward.x * rotateGap,
|
|
345
|
+
y: top.y + outward.y * rotateGap,
|
|
346
|
+
};
|
|
347
|
+
return points;
|
|
348
|
+
}
|
|
349
|
+
export function getSpatialZoneWorldHandlePoints(zone, policy) {
|
|
350
|
+
const bounds = getSpatialZoneMapBounds(zone);
|
|
351
|
+
if (!bounds)
|
|
352
|
+
return null;
|
|
353
|
+
const transform = normalizeSpatialTransform(zone.transform, policy);
|
|
354
|
+
const points = {
|
|
355
|
+
n: zoneLocalPointToMap(zone, handleLocalPoint(bounds, 'n'), transform),
|
|
356
|
+
ne: zoneLocalPointToMap(zone, handleLocalPoint(bounds, 'ne'), transform),
|
|
357
|
+
e: zoneLocalPointToMap(zone, handleLocalPoint(bounds, 'e'), transform),
|
|
358
|
+
se: zoneLocalPointToMap(zone, handleLocalPoint(bounds, 'se'), transform),
|
|
359
|
+
s: zoneLocalPointToMap(zone, handleLocalPoint(bounds, 's'), transform),
|
|
360
|
+
sw: zoneLocalPointToMap(zone, handleLocalPoint(bounds, 'sw'), transform),
|
|
361
|
+
w: zoneLocalPointToMap(zone, handleLocalPoint(bounds, 'w'), transform),
|
|
362
|
+
nw: zoneLocalPointToMap(zone, handleLocalPoint(bounds, 'nw'), transform),
|
|
363
|
+
rotate: { x: 0, y: 0 },
|
|
364
|
+
};
|
|
365
|
+
const top = points.n;
|
|
366
|
+
const outward = zoneLocalDirectionToMap(zone, { x: 0, y: -1 });
|
|
367
|
+
const rotateGap = Math.max(28, Math.min(56, bounds.height * 0.35));
|
|
368
|
+
points.rotate = {
|
|
369
|
+
x: top.x + outward.x * rotateGap,
|
|
370
|
+
y: top.y + outward.y * rotateGap,
|
|
371
|
+
};
|
|
372
|
+
return points;
|
|
373
|
+
}
|
|
374
|
+
export function getSpatialZonePolygonPoints(zone) {
|
|
375
|
+
if (zone.shape.type !== 'polygon')
|
|
376
|
+
return null;
|
|
377
|
+
const points = parsePolygonPoints(zone.shape.points);
|
|
378
|
+
return points.length >= 3 ? points : null;
|
|
379
|
+
}
|
|
380
|
+
export function getSpatialZonePolygonHandles(zone, policy) {
|
|
381
|
+
const points = getSpatialZonePolygonPoints(zone);
|
|
382
|
+
if (!points)
|
|
383
|
+
return null;
|
|
384
|
+
const transform = normalizeSpatialTransform(zone.transform, policy);
|
|
385
|
+
const vertices = points.map((point, index) => ({
|
|
386
|
+
index,
|
|
387
|
+
point: zoneLocalPointToMap(zone, point, transform),
|
|
388
|
+
}));
|
|
389
|
+
const segments = points.map((point, index) => {
|
|
390
|
+
const next = points[(index + 1) % points.length];
|
|
391
|
+
return {
|
|
392
|
+
index,
|
|
393
|
+
point: zoneLocalPointToMap(zone, {
|
|
394
|
+
x: (point.x + next.x) / 2,
|
|
395
|
+
y: (point.y + next.y) / 2,
|
|
396
|
+
}, transform),
|
|
397
|
+
};
|
|
398
|
+
});
|
|
399
|
+
return { vertices, segments };
|
|
400
|
+
}
|
|
401
|
+
function normalizePolygonWorldPoint(zone, point, rect, policy) {
|
|
402
|
+
const bounded = policy.bounds === 'viewBox' ? clampPointToRect(point, rect) : point;
|
|
403
|
+
return roundPoint(mapPointToZoneLocal(zone, bounded, policy), policy.coordinatePrecision);
|
|
404
|
+
}
|
|
405
|
+
export function moveSpatialZonePolygonVertex(zone, args) {
|
|
406
|
+
const points = getSpatialZonePolygonPoints(zone);
|
|
407
|
+
if (!points || args.vertexIndex < 0 || args.vertexIndex >= points.length)
|
|
408
|
+
return zone;
|
|
409
|
+
const nextPoints = points.map((point, index) => (index === args.vertexIndex
|
|
410
|
+
? normalizePolygonWorldPoint(zone, args.worldPoint, args.rect, args.policy)
|
|
411
|
+
: point));
|
|
412
|
+
return {
|
|
413
|
+
...zone,
|
|
414
|
+
shape: {
|
|
415
|
+
type: 'polygon',
|
|
416
|
+
points: nextPoints,
|
|
417
|
+
},
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
export function insertSpatialZonePolygonVertex(zone, args) {
|
|
421
|
+
const points = getSpatialZonePolygonPoints(zone);
|
|
422
|
+
if (!points || args.segmentIndex < 0 || args.segmentIndex >= points.length)
|
|
423
|
+
return zone;
|
|
424
|
+
const nextPoint = normalizePolygonWorldPoint(zone, args.worldPoint, args.rect, args.policy);
|
|
425
|
+
const nextPoints = [
|
|
426
|
+
...points.slice(0, args.segmentIndex + 1),
|
|
427
|
+
nextPoint,
|
|
428
|
+
...points.slice(args.segmentIndex + 1),
|
|
429
|
+
];
|
|
430
|
+
return {
|
|
431
|
+
...zone,
|
|
432
|
+
shape: {
|
|
433
|
+
type: 'polygon',
|
|
434
|
+
points: nextPoints,
|
|
435
|
+
},
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
export function deleteSpatialZonePolygonVertex(zone, args) {
|
|
439
|
+
const points = getSpatialZonePolygonPoints(zone);
|
|
440
|
+
if (!points || points.length <= 3 || args.vertexIndex < 0 || args.vertexIndex >= points.length)
|
|
441
|
+
return zone;
|
|
442
|
+
return {
|
|
443
|
+
...zone,
|
|
444
|
+
shape: {
|
|
445
|
+
type: 'polygon',
|
|
446
|
+
points: points.filter((_, index) => index !== args.vertexIndex),
|
|
447
|
+
},
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
export function getSpatialZoneTransformedBounds(zone, policy) {
|
|
451
|
+
const bounds = getSpatialZoneMapBounds(zone);
|
|
452
|
+
if (!bounds)
|
|
453
|
+
return null;
|
|
454
|
+
const transform = normalizeSpatialTransform(zone.transform, policy);
|
|
455
|
+
const corners = [
|
|
456
|
+
handleLocalPoint(bounds, 'nw'),
|
|
457
|
+
handleLocalPoint(bounds, 'ne'),
|
|
458
|
+
handleLocalPoint(bounds, 'se'),
|
|
459
|
+
handleLocalPoint(bounds, 'sw'),
|
|
460
|
+
].map((point) => zoneLocalPointToMap(zone, point, transform));
|
|
461
|
+
const xs = corners.map((point) => point.x);
|
|
462
|
+
const ys = corners.map((point) => point.y);
|
|
463
|
+
const minX = Math.min(...xs);
|
|
464
|
+
const maxX = Math.max(...xs);
|
|
465
|
+
const minY = Math.min(...ys);
|
|
466
|
+
const maxY = Math.max(...ys);
|
|
467
|
+
return {
|
|
468
|
+
x: minX,
|
|
469
|
+
y: minY,
|
|
470
|
+
width: maxX - minX,
|
|
471
|
+
height: maxY - minY,
|
|
472
|
+
};
|
|
473
|
+
}
|
|
474
|
+
export function resizeSpatialItem(item, args) {
|
|
475
|
+
const bounds = getSpatialItemLocalBounds(item);
|
|
476
|
+
if (!bounds)
|
|
477
|
+
return item;
|
|
478
|
+
const current = normalizeSpatialTransform(item.transform, args.policy);
|
|
479
|
+
const anchor = handleLocalPoint(bounds, oppositeHandle(args.handle));
|
|
480
|
+
const active = handleLocalPoint(bounds, args.handle);
|
|
481
|
+
const widthDenominator = active.x - anchor.x;
|
|
482
|
+
const heightDenominator = active.y - anchor.y;
|
|
483
|
+
const scaledLocalPoint = {
|
|
484
|
+
x: args.localPoint.x * current.scaleX,
|
|
485
|
+
y: args.localPoint.y * current.scaleY,
|
|
486
|
+
};
|
|
487
|
+
const scaledAnchor = {
|
|
488
|
+
x: anchor.x * current.scaleX,
|
|
489
|
+
y: anchor.y * current.scaleY,
|
|
490
|
+
};
|
|
491
|
+
let nextScaleX = current.scaleX;
|
|
492
|
+
let nextScaleY = current.scaleY;
|
|
493
|
+
if (widthDenominator !== 0) {
|
|
494
|
+
nextScaleX = Math.abs((scaledLocalPoint.x - scaledAnchor.x) / widthDenominator);
|
|
495
|
+
}
|
|
496
|
+
if (heightDenominator !== 0) {
|
|
497
|
+
nextScaleY = Math.abs((scaledLocalPoint.y - scaledAnchor.y) / heightDenominator);
|
|
498
|
+
}
|
|
499
|
+
if (args.preserveAspectRatio) {
|
|
500
|
+
const dominant = Math.abs(nextScaleX - current.scaleX) >= Math.abs(nextScaleY - current.scaleY)
|
|
501
|
+
? nextScaleX
|
|
502
|
+
: nextScaleY;
|
|
503
|
+
if (widthDenominator !== 0)
|
|
504
|
+
nextScaleX = dominant;
|
|
505
|
+
if (heightDenominator !== 0)
|
|
506
|
+
nextScaleY = dominant;
|
|
507
|
+
}
|
|
508
|
+
const transform = normalizeSpatialTransform({ scaleX: nextScaleX, scaleY: nextScaleY }, args.policy);
|
|
509
|
+
const anchorBefore = itemLocalPointToMap(item, anchor, current);
|
|
510
|
+
const rotatedAnchorAfter = rotatePoint({ x: anchor.x * transform.scaleX, y: anchor.y * transform.scaleY }, item.rotation ?? 0);
|
|
511
|
+
return {
|
|
512
|
+
...item,
|
|
513
|
+
position: {
|
|
514
|
+
x: anchorBefore.x - rotatedAnchorAfter.x,
|
|
515
|
+
y: anchorBefore.y - rotatedAnchorAfter.y,
|
|
516
|
+
},
|
|
517
|
+
transform,
|
|
518
|
+
};
|
|
519
|
+
}
|
|
520
|
+
export function resizeSpatialZone(zone, args) {
|
|
521
|
+
const bounds = getSpatialZoneMapBounds(zone);
|
|
522
|
+
if (!bounds)
|
|
523
|
+
return zone;
|
|
524
|
+
const current = normalizeSpatialTransform(zone.transform, args.policy);
|
|
525
|
+
const anchor = handleLocalPoint(bounds, oppositeHandle(args.handle));
|
|
526
|
+
const active = handleLocalPoint(bounds, args.handle);
|
|
527
|
+
const widthDenominator = active.x - anchor.x;
|
|
528
|
+
const heightDenominator = active.y - anchor.y;
|
|
529
|
+
const scaledLocalPoint = {
|
|
530
|
+
x: args.localPoint.x * current.scaleX,
|
|
531
|
+
y: args.localPoint.y * current.scaleY,
|
|
532
|
+
};
|
|
533
|
+
const scaledAnchor = {
|
|
534
|
+
x: anchor.x * current.scaleX,
|
|
535
|
+
y: anchor.y * current.scaleY,
|
|
536
|
+
};
|
|
537
|
+
let nextScaleX = current.scaleX;
|
|
538
|
+
let nextScaleY = current.scaleY;
|
|
539
|
+
if (widthDenominator !== 0) {
|
|
540
|
+
nextScaleX = Math.abs((scaledLocalPoint.x - scaledAnchor.x) / widthDenominator);
|
|
541
|
+
}
|
|
542
|
+
if (heightDenominator !== 0) {
|
|
543
|
+
nextScaleY = Math.abs((scaledLocalPoint.y - scaledAnchor.y) / heightDenominator);
|
|
544
|
+
}
|
|
545
|
+
if (args.preserveAspectRatio) {
|
|
546
|
+
const dominant = Math.abs(nextScaleX - current.scaleX) >= Math.abs(nextScaleY - current.scaleY)
|
|
547
|
+
? nextScaleX
|
|
548
|
+
: nextScaleY;
|
|
549
|
+
if (widthDenominator !== 0)
|
|
550
|
+
nextScaleX = dominant;
|
|
551
|
+
if (heightDenominator !== 0)
|
|
552
|
+
nextScaleY = dominant;
|
|
553
|
+
}
|
|
554
|
+
const transform = normalizeSpatialTransform({ scaleX: nextScaleX, scaleY: nextScaleY }, args.policy);
|
|
555
|
+
const anchorBefore = zoneLocalPointToMap(zone, anchor, current);
|
|
556
|
+
const rotatedAnchorAfter = rotatePoint({ x: anchor.x * transform.scaleX, y: anchor.y * transform.scaleY }, zone.rotation ?? 0);
|
|
557
|
+
return {
|
|
558
|
+
...zone,
|
|
559
|
+
position: {
|
|
560
|
+
x: anchorBefore.x - rotatedAnchorAfter.x,
|
|
561
|
+
y: anchorBefore.y - rotatedAnchorAfter.y,
|
|
562
|
+
},
|
|
563
|
+
transform,
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
export function normalizeSpatialRotation(rotation) {
|
|
567
|
+
if (!Number.isFinite(rotation))
|
|
568
|
+
return 0;
|
|
569
|
+
let normalized = rotation % 360;
|
|
570
|
+
if (normalized >= 180)
|
|
571
|
+
normalized -= 360;
|
|
572
|
+
if (normalized < -180)
|
|
573
|
+
normalized += 360;
|
|
574
|
+
return normalized;
|
|
575
|
+
}
|
|
576
|
+
export function rotateSpatialItem(item, rotation, policy) {
|
|
577
|
+
const step = policy.rotationStep;
|
|
578
|
+
const snapped = step > 0 ? Math.round(rotation / step) * step : rotation;
|
|
579
|
+
return {
|
|
580
|
+
...item,
|
|
581
|
+
rotation: normalizeSpatialRotation(snapped),
|
|
582
|
+
};
|
|
583
|
+
}
|
|
584
|
+
function sameSpatialTransform(a, b, policy) {
|
|
585
|
+
const left = normalizeSpatialTransform(a, policy);
|
|
586
|
+
const right = normalizeSpatialTransform(b, policy);
|
|
587
|
+
return left.scaleX === right.scaleX && left.scaleY === right.scaleY;
|
|
588
|
+
}
|
|
589
|
+
function nearestGridValue(value, size, offset) {
|
|
590
|
+
return offset + Math.round((value - offset) / size) * size;
|
|
591
|
+
}
|
|
592
|
+
function keyboardGridValue(origin, delta, size, offset) {
|
|
593
|
+
const direction = delta < 0 ? -1 : 1;
|
|
594
|
+
const steps = Math.max(1, Math.round(Math.abs(delta) / size));
|
|
595
|
+
const originRatio = (origin - offset) / size;
|
|
596
|
+
const originStop = direction > 0
|
|
597
|
+
? Math.floor(originRatio)
|
|
598
|
+
: Math.ceil(originRatio);
|
|
599
|
+
return offset + (originStop + direction * steps) * size;
|
|
600
|
+
}
|
|
601
|
+
function uniqueSources(guides) {
|
|
602
|
+
const seen = new Set();
|
|
603
|
+
const sources = [];
|
|
604
|
+
for (const guide of guides) {
|
|
605
|
+
if (!seen.has(guide.source)) {
|
|
606
|
+
seen.add(guide.source);
|
|
607
|
+
sources.push(guide.source);
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
return sources;
|
|
611
|
+
}
|
|
612
|
+
function candidateWins(candidate, current) {
|
|
613
|
+
if (!current)
|
|
614
|
+
return true;
|
|
615
|
+
if (candidate.distance < current.distance)
|
|
616
|
+
return true;
|
|
617
|
+
return candidate.distance === current.distance && candidate.source === 'item-center';
|
|
618
|
+
}
|
|
619
|
+
function axisValue(point, axis) {
|
|
620
|
+
return axis === 'x' ? point.x : point.y;
|
|
621
|
+
}
|
|
622
|
+
function axisPoint(point, axis, value) {
|
|
623
|
+
return axis === 'x'
|
|
624
|
+
? { x: value, y: point.y }
|
|
625
|
+
: { x: point.x, y: value };
|
|
626
|
+
}
|
|
627
|
+
export function resolveSpatialEditPoint(args) {
|
|
628
|
+
const boundedRaw = args.policy.bounds === 'viewBox'
|
|
629
|
+
? clampPointToRect(args.rawPoint, args.rect)
|
|
630
|
+
: args.rawPoint;
|
|
631
|
+
let point = boundedRaw;
|
|
632
|
+
const guidesByAxis = {};
|
|
633
|
+
if (args.policy.snap.enabled) {
|
|
634
|
+
if (args.policy.snap.grid.enabled) {
|
|
635
|
+
for (const axis of ['x', 'y']) {
|
|
636
|
+
const value = axisValue(point, axis);
|
|
637
|
+
const size = args.policy.snap.grid.size[axis];
|
|
638
|
+
const offset = args.policy.snap.grid.offset[axis];
|
|
639
|
+
const keyboardIntent = args.intent?.kind === 'keyboard' ? args.intent : undefined;
|
|
640
|
+
const intentDelta = keyboardIntent ? keyboardIntent.delta[axis] : 0;
|
|
641
|
+
let usesKeyboardGridStep = false;
|
|
642
|
+
let position = nearestGridValue(value, size, offset);
|
|
643
|
+
if (keyboardIntent && intentDelta !== 0) {
|
|
644
|
+
usesKeyboardGridStep = true;
|
|
645
|
+
position = keyboardGridValue(keyboardIntent.origin[axis], intentDelta, size, offset);
|
|
646
|
+
}
|
|
647
|
+
const distance = usesKeyboardGridStep ? 0 : Math.abs(position - value);
|
|
648
|
+
if (usesKeyboardGridStep || distance <= args.policy.snap.grid.threshold) {
|
|
649
|
+
guidesByAxis[axis] = {
|
|
650
|
+
axis,
|
|
651
|
+
source: 'grid',
|
|
652
|
+
value: position,
|
|
653
|
+
distance,
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
if (args.policy.snap.itemCenters.enabled) {
|
|
659
|
+
for (const candidateItem of args.items ?? []) {
|
|
660
|
+
if (args.currentItemId !== undefined
|
|
661
|
+
&& String(candidateItem.id) === String(args.currentItemId)) {
|
|
662
|
+
continue;
|
|
663
|
+
}
|
|
664
|
+
if (!Number.isFinite(candidateItem.position?.x) || !Number.isFinite(candidateItem.position?.y)) {
|
|
665
|
+
continue;
|
|
666
|
+
}
|
|
667
|
+
for (const axis of ['x', 'y']) {
|
|
668
|
+
const position = candidateItem.position[axis];
|
|
669
|
+
const keyboardIntent = args.intent?.kind === 'keyboard' ? args.intent : undefined;
|
|
670
|
+
const intentDelta = keyboardIntent ? keyboardIntent.delta[axis] : 0;
|
|
671
|
+
const gridGuide = intentDelta !== 0 && guidesByAxis[axis]?.source === 'grid'
|
|
672
|
+
? guidesByAxis[axis]
|
|
673
|
+
: undefined;
|
|
674
|
+
if (keyboardIntent && intentDelta !== 0 && !gridGuide) {
|
|
675
|
+
const origin = keyboardIntent.origin[axis];
|
|
676
|
+
if ((intentDelta > 0 && position <= origin)
|
|
677
|
+
|| (intentDelta < 0 && position >= origin)) {
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
const comparisonValue = gridGuide ? gridGuide.value : axisValue(point, axis);
|
|
682
|
+
const distance = Math.abs(position - comparisonValue);
|
|
683
|
+
if (distance > args.policy.snap.itemCenters.threshold)
|
|
684
|
+
continue;
|
|
685
|
+
const candidate = {
|
|
686
|
+
axis,
|
|
687
|
+
source: 'item-center',
|
|
688
|
+
value: position,
|
|
689
|
+
distance,
|
|
690
|
+
targetId: candidateItem.id,
|
|
691
|
+
};
|
|
692
|
+
if (candidateWins(candidate, guidesByAxis[axis])) {
|
|
693
|
+
guidesByAxis[axis] = candidate;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
const axisGuides = ['x', 'y']
|
|
699
|
+
.map((axis) => guidesByAxis[axis])
|
|
700
|
+
.filter((guide) => Boolean(guide));
|
|
701
|
+
for (const guide of axisGuides) {
|
|
702
|
+
point = axisPoint(point, guide.axis, guide.value);
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
const snapGuides = ['x', 'y']
|
|
706
|
+
.map((axis) => guidesByAxis[axis])
|
|
707
|
+
.filter((guide) => Boolean(guide))
|
|
708
|
+
.map(({ distance: _distance, ...guide }) => guide);
|
|
709
|
+
const snapped = snapGuides.length > 0;
|
|
710
|
+
const boundedSnapped = args.policy.bounds === 'viewBox'
|
|
711
|
+
? clampPointToRect(point, args.rect)
|
|
712
|
+
: point;
|
|
713
|
+
const roundedPoint = roundPoint(boundedSnapped, args.policy.coordinatePrecision);
|
|
714
|
+
return {
|
|
715
|
+
point: roundedPoint,
|
|
716
|
+
rawPoint: args.rawPoint,
|
|
717
|
+
snapped,
|
|
718
|
+
guides: snapGuides,
|
|
719
|
+
sources: uniqueSources(snapGuides),
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
export function isMovableSpatialItem(item) {
|
|
723
|
+
if (item.disabled)
|
|
724
|
+
return false;
|
|
725
|
+
return Number.isFinite(item.position?.x) && Number.isFinite(item.position?.y);
|
|
726
|
+
}
|
|
727
|
+
export function moveSpatialItem(item, position) {
|
|
728
|
+
return {
|
|
729
|
+
...item,
|
|
730
|
+
position: { x: position.x, y: position.y },
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
export function moveSpatialZone(zone, position, rect, policy) {
|
|
734
|
+
return {
|
|
735
|
+
...zone,
|
|
736
|
+
position: normalizeZonePosition(zone, position, rect, policy),
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
export function samePoint(a, b) {
|
|
740
|
+
return a.x === b.x && a.y === b.y;
|
|
741
|
+
}
|
|
742
|
+
export function hasSpatialItemChangedByType(args) {
|
|
743
|
+
const previousPosition = args.rect
|
|
744
|
+
? normalizeEditPosition(args.previousItem.position, args.rect, args.policy)
|
|
745
|
+
: args.previousItem.position;
|
|
746
|
+
const nextPosition = args.rect
|
|
747
|
+
? normalizeEditPosition(args.nextItem.position, args.rect, args.policy)
|
|
748
|
+
: args.nextItem.position;
|
|
749
|
+
if (args.changeType === 'move') {
|
|
750
|
+
return !samePoint(previousPosition, nextPosition);
|
|
751
|
+
}
|
|
752
|
+
if (args.changeType === 'resize') {
|
|
753
|
+
return !sameSpatialTransform(args.previousItem.transform, args.nextItem.transform, args.policy)
|
|
754
|
+
|| !samePoint(previousPosition, nextPosition);
|
|
755
|
+
}
|
|
756
|
+
if (args.changeType === 'rotate') {
|
|
757
|
+
return normalizeSpatialRotation(args.previousItem.rotation ?? 0) !== normalizeSpatialRotation(args.nextItem.rotation ?? 0);
|
|
758
|
+
}
|
|
759
|
+
return args.previousItem !== args.nextItem;
|
|
760
|
+
}
|
|
761
|
+
export function hasSpatialZoneChangedByType(args) {
|
|
762
|
+
const previousPosition = getSpatialZonePosition(args.previousZone);
|
|
763
|
+
const nextPosition = getSpatialZonePosition(args.nextZone);
|
|
764
|
+
if (args.changeType === 'move') {
|
|
765
|
+
return !samePoint(previousPosition, nextPosition);
|
|
766
|
+
}
|
|
767
|
+
if (args.changeType === 'resize') {
|
|
768
|
+
return !sameSpatialTransform(args.previousZone.transform, args.nextZone.transform, args.policy)
|
|
769
|
+
|| !samePoint(previousPosition, nextPosition);
|
|
770
|
+
}
|
|
771
|
+
if (args.changeType === 'rotate') {
|
|
772
|
+
return normalizeSpatialRotation(args.previousZone.rotation ?? 0) !== normalizeSpatialRotation(args.nextZone.rotation ?? 0);
|
|
773
|
+
}
|
|
774
|
+
if (args.changeType === 'shape') {
|
|
775
|
+
const previousPoints = getSpatialZonePolygonPoints(args.previousZone);
|
|
776
|
+
const nextPoints = getSpatialZonePolygonPoints(args.nextZone);
|
|
777
|
+
if (!previousPoints || !nextPoints) {
|
|
778
|
+
return previousPoints !== nextPoints;
|
|
779
|
+
}
|
|
780
|
+
if (previousPoints.length !== nextPoints.length) {
|
|
781
|
+
return true;
|
|
782
|
+
}
|
|
783
|
+
return previousPoints.some((point, index) => !samePoint(point, nextPoints[index]));
|
|
784
|
+
}
|
|
785
|
+
return args.previousZone !== args.nextZone;
|
|
786
|
+
}
|
|
787
|
+
export function buildSpatialItemChangeContext(args) {
|
|
788
|
+
const zone = args.nextItem.zoneId
|
|
789
|
+
? args.zones.find((candidate) => candidate.id === args.nextItem.zoneId)
|
|
790
|
+
: undefined;
|
|
791
|
+
const previousPosition = args.previousItem?.position;
|
|
792
|
+
const position = args.nextItem.position;
|
|
793
|
+
const previousTransform = args.previousItem
|
|
794
|
+
? normalizeSpatialTransform(args.previousItem.transform, args.policy)
|
|
795
|
+
: undefined;
|
|
796
|
+
const transform = normalizeSpatialTransform(args.nextItem.transform, args.policy);
|
|
797
|
+
const previousRotation = args.previousItem?.rotation ?? 0;
|
|
798
|
+
const rotation = args.nextItem.rotation ?? 0;
|
|
799
|
+
const previousLocalBounds = args.previousItem
|
|
800
|
+
? getSpatialItemLocalBounds(args.previousItem) ?? undefined
|
|
801
|
+
: undefined;
|
|
802
|
+
const localBounds = getSpatialItemLocalBounds(args.nextItem) ?? undefined;
|
|
803
|
+
return {
|
|
804
|
+
kind: 'item-change',
|
|
805
|
+
changeType: args.changeType,
|
|
806
|
+
mode: args.mode,
|
|
807
|
+
itemId: args.nextItem.id,
|
|
808
|
+
zoneId: args.nextItem.zoneId,
|
|
809
|
+
previousItem: args.previousItem,
|
|
810
|
+
nextItem: args.nextItem,
|
|
811
|
+
item: args.nextItem,
|
|
812
|
+
...(previousPosition ? { previousPosition } : {}),
|
|
813
|
+
position,
|
|
814
|
+
...(previousPosition ? {
|
|
815
|
+
delta: {
|
|
816
|
+
x: position.x - previousPosition.x,
|
|
817
|
+
y: position.y - previousPosition.y,
|
|
818
|
+
},
|
|
819
|
+
} : {}),
|
|
820
|
+
previousTransform,
|
|
821
|
+
transform,
|
|
822
|
+
previousRotation,
|
|
823
|
+
rotation,
|
|
824
|
+
previousLocalBounds,
|
|
825
|
+
localBounds,
|
|
826
|
+
zone,
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
export function buildSpatialZoneChangeContext(args) {
|
|
830
|
+
const previousPosition = args.previousZone ? getSpatialZonePosition(args.previousZone) : undefined;
|
|
831
|
+
const position = getSpatialZonePosition(args.nextZone);
|
|
832
|
+
const previousTransform = args.previousZone
|
|
833
|
+
? normalizeSpatialTransform(args.previousZone.transform, args.policy)
|
|
834
|
+
: undefined;
|
|
835
|
+
const transform = normalizeSpatialTransform(args.nextZone.transform, args.policy);
|
|
836
|
+
const previousRotation = args.previousZone?.rotation ?? 0;
|
|
837
|
+
const rotation = args.nextZone.rotation ?? 0;
|
|
838
|
+
const previousLocalBounds = args.previousZone
|
|
839
|
+
? getSpatialZoneMapBounds(args.previousZone) ?? undefined
|
|
840
|
+
: undefined;
|
|
841
|
+
const localBounds = getSpatialZoneMapBounds(args.nextZone) ?? undefined;
|
|
842
|
+
const previousPoints = args.previousZone
|
|
843
|
+
? getSpatialZonePolygonPoints(args.previousZone) ?? undefined
|
|
844
|
+
: undefined;
|
|
845
|
+
const points = getSpatialZonePolygonPoints(args.nextZone) ?? undefined;
|
|
846
|
+
return {
|
|
847
|
+
kind: 'zone-change',
|
|
848
|
+
changeType: args.changeType,
|
|
849
|
+
mode: args.mode,
|
|
850
|
+
zoneId: args.nextZone.id,
|
|
851
|
+
previousZone: args.previousZone,
|
|
852
|
+
nextZone: args.nextZone,
|
|
853
|
+
zone: args.nextZone,
|
|
854
|
+
...(previousPosition ? { previousPosition } : {}),
|
|
855
|
+
position,
|
|
856
|
+
...(previousPosition ? {
|
|
857
|
+
delta: {
|
|
858
|
+
x: position.x - previousPosition.x,
|
|
859
|
+
y: position.y - previousPosition.y,
|
|
860
|
+
},
|
|
861
|
+
} : {}),
|
|
862
|
+
previousTransform,
|
|
863
|
+
transform,
|
|
864
|
+
previousRotation,
|
|
865
|
+
rotation,
|
|
866
|
+
previousLocalBounds,
|
|
867
|
+
localBounds,
|
|
868
|
+
...(args.changeType === 'shape' ? {
|
|
869
|
+
...(args.previousZone ? { previousShape: args.previousZone.shape } : {}),
|
|
870
|
+
shape: args.nextZone.shape,
|
|
871
|
+
...(previousPoints ? { previousPoints } : {}),
|
|
872
|
+
...(points ? { points } : {}),
|
|
873
|
+
...(typeof args.vertexIndex === 'number' ? { vertexIndex: args.vertexIndex } : {}),
|
|
874
|
+
...(typeof args.segmentIndex === 'number' ? { segmentIndex: args.segmentIndex } : {}),
|
|
875
|
+
...(args.shapeAction ? { shapeAction: args.shapeAction } : {}),
|
|
876
|
+
} : {}),
|
|
877
|
+
};
|
|
878
|
+
}
|
|
879
|
+
export function buildItemChangeContext(args) {
|
|
880
|
+
return buildSpatialItemChangeContext({
|
|
881
|
+
...args,
|
|
882
|
+
changeType: 'move',
|
|
883
|
+
policy: args.policy ?? getEditPolicy(args.mode),
|
|
884
|
+
});
|
|
885
|
+
}
|
|
886
|
+
//# sourceMappingURL=spatial-map-editing.js.map
|