neogestify-ui-components 1.2.21 → 2.0.1

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.
Files changed (52) hide show
  1. package/README.md +393 -2
  2. package/dist/components/VenueMapEditor/index.d.mts +202 -0
  3. package/dist/components/VenueMapEditor/index.d.ts +202 -0
  4. package/dist/components/VenueMapEditor/index.js +2684 -0
  5. package/dist/components/VenueMapEditor/index.js.map +1 -0
  6. package/dist/components/VenueMapEditor/index.mjs +2676 -0
  7. package/dist/components/VenueMapEditor/index.mjs.map +1 -0
  8. package/dist/components/alerts/index.js.map +1 -1
  9. package/dist/components/alerts/index.mjs.map +1 -1
  10. package/dist/components/html/index.d.mts +2 -0
  11. package/dist/components/html/index.d.ts +2 -0
  12. package/dist/components/html/index.js +24 -58
  13. package/dist/components/html/index.js.map +1 -1
  14. package/dist/components/html/index.mjs +24 -58
  15. package/dist/components/html/index.mjs.map +1 -1
  16. package/dist/components/icons/index.d.mts +18 -2
  17. package/dist/components/icons/index.d.ts +18 -2
  18. package/dist/components/icons/index.js +97 -11
  19. package/dist/components/icons/index.js.map +1 -1
  20. package/dist/components/icons/index.mjs +82 -12
  21. package/dist/components/icons/index.mjs.map +1 -1
  22. package/dist/context/theme/index.js.map +1 -1
  23. package/dist/context/theme/index.mjs.map +1 -1
  24. package/dist/index.d.mts +2 -1
  25. package/dist/index.d.ts +2 -1
  26. package/dist/index.js +2734 -69
  27. package/dist/index.js.map +1 -1
  28. package/dist/index.mjs +2713 -71
  29. package/dist/index.mjs.map +1 -1
  30. package/package.json +9 -4
  31. package/src/components/VenueMapEditor/VenueMapEditor.tsx +851 -0
  32. package/src/components/VenueMapEditor/VenueMapViewer.tsx +13 -0
  33. package/src/components/VenueMapEditor/components/Artboard.tsx +405 -0
  34. package/src/components/VenueMapEditor/components/EditorCanvas.tsx +472 -0
  35. package/src/components/VenueMapEditor/components/ElementNode.tsx +357 -0
  36. package/src/components/VenueMapEditor/components/FloorTabs.tsx +137 -0
  37. package/src/components/VenueMapEditor/components/GridOverlay.tsx +67 -0
  38. package/src/components/VenueMapEditor/components/PropertiesPanel.tsx +198 -0
  39. package/src/components/VenueMapEditor/components/Toolbar.tsx +254 -0
  40. package/src/components/VenueMapEditor/components/WallLayer.tsx +117 -0
  41. package/src/components/VenueMapEditor/hooks/useDrag.ts +79 -0
  42. package/src/components/VenueMapEditor/hooks/useHistory.ts +74 -0
  43. package/src/components/VenueMapEditor/hooks/usePanZoom.ts +114 -0
  44. package/src/components/VenueMapEditor/hooks/useSelection.ts +42 -0
  45. package/src/components/VenueMapEditor/index.ts +34 -0
  46. package/src/components/VenueMapEditor/types.ts +173 -0
  47. package/src/components/VenueMapEditor/utils/idGen.ts +2 -0
  48. package/src/components/VenueMapEditor/utils/snapUtils.ts +38 -0
  49. package/src/components/VenueMapEditor/utils/wallGeometry.ts +83 -0
  50. package/src/components/html/Input.tsx +48 -80
  51. package/src/components/icons/icons.tsx +153 -14
  52. package/src/index.ts +1 -0
@@ -0,0 +1,13 @@
1
+ import { VenueMapEditor } from './VenueMapEditor';
2
+ import type { VenueMapViewerProps } from './types';
3
+
4
+ export function VenueMapViewer({ elementStatus, onElementClick, ...rest }: VenueMapViewerProps) {
5
+ return (
6
+ <VenueMapEditor
7
+ {...rest}
8
+ fixed={true}
9
+ elementStatus={elementStatus}
10
+ onElementClick={onElementClick}
11
+ />
12
+ );
13
+ }
@@ -0,0 +1,405 @@
1
+ import { useRef, useCallback } from 'react';
2
+ import type { RefObject, MouseEvent as ReactMouseEvent } from 'react';
3
+ import type { FloorArea } from '../types';
4
+ import type { PanZoomState } from '../hooks/usePanZoom';
5
+ import { useDrag } from '../hooks/useDrag';
6
+
7
+ type PanZoomRef = { current: PanZoomState };
8
+
9
+ // ─── Types ────────────────────────────────────────────────────────────────────
10
+
11
+ type HandleType = 'nw' | 'n' | 'ne' | 'e' | 'se' | 's' | 'sw' | 'w';
12
+
13
+ const HANDLE_CURSORS: Record<HandleType, string> = {
14
+ nw: 'nwse-resize',
15
+ ne: 'nesw-resize',
16
+ se: 'nwse-resize',
17
+ sw: 'nesw-resize',
18
+ n: 'ns-resize',
19
+ s: 'ns-resize',
20
+ e: 'ew-resize',
21
+ w: 'ew-resize',
22
+ };
23
+
24
+ const MIN_SIZE = 50;
25
+ const HANDLE_PX = 8;
26
+
27
+ // ─── Props ────────────────────────────────────────────────────────────────────
28
+
29
+ interface ArtboardProps {
30
+ area: FloorArea;
31
+ onResize: (area: FloorArea) => void;
32
+ onMove?: (dx: number, dy: number) => void;
33
+ onResizeCommit?: (area: FloorArea) => void;
34
+ svgRef: RefObject<SVGSVGElement | null>;
35
+ panZoomRef: PanZoomRef;
36
+ zoom: number;
37
+ readOnly?: boolean;
38
+ }
39
+
40
+ // ─── Geometry helper ──────────────────────────────────────────────────────────
41
+
42
+ function applyHandleDelta(
43
+ area: FloorArea,
44
+ handle: HandleType,
45
+ dx: number,
46
+ dy: number,
47
+ ): FloorArea {
48
+ const ax = area.x ?? 0;
49
+ const ay = area.y ?? 0;
50
+ const aw = area.width ?? 400;
51
+ const ah = area.height ?? 300;
52
+
53
+ const right = ax + aw;
54
+ const bottom = ay + ah;
55
+
56
+ let nx = ax, ny = ay, nw = aw, nh = ah;
57
+
58
+ switch (handle) {
59
+ case 'nw':
60
+ nw = Math.max(MIN_SIZE, aw - dx);
61
+ nh = Math.max(MIN_SIZE, ah - dy);
62
+ nx = right - nw;
63
+ ny = bottom - nh;
64
+ break;
65
+ case 'n':
66
+ nh = Math.max(MIN_SIZE, ah - dy);
67
+ ny = bottom - nh;
68
+ break;
69
+ case 'ne':
70
+ nw = Math.max(MIN_SIZE, aw + dx);
71
+ nh = Math.max(MIN_SIZE, ah - dy);
72
+ ny = bottom - nh;
73
+ break;
74
+ case 'e':
75
+ nw = Math.max(MIN_SIZE, aw + dx);
76
+ break;
77
+ case 'se':
78
+ nw = Math.max(MIN_SIZE, aw + dx);
79
+ nh = Math.max(MIN_SIZE, ah + dy);
80
+ break;
81
+ case 's':
82
+ nh = Math.max(MIN_SIZE, ah + dy);
83
+ break;
84
+ case 'sw':
85
+ nw = Math.max(MIN_SIZE, aw - dx);
86
+ nh = Math.max(MIN_SIZE, ah + dy);
87
+ nx = right - nw;
88
+ break;
89
+ case 'w':
90
+ nw = Math.max(MIN_SIZE, aw - dx);
91
+ nx = right - nw;
92
+ break;
93
+ }
94
+
95
+ return { ...area, x: nx, y: ny, width: nw, height: nh };
96
+ }
97
+
98
+ // ─── PolygonArtboard ──────────────────────────────────────────────────────────
99
+
100
+ interface PolygonArtboardProps {
101
+ area: FloorArea;
102
+ onResize: (area: FloorArea) => void;
103
+ onMove?: (dx: number, dy: number) => void;
104
+ onResizeCommit?: (area: FloorArea) => void;
105
+ svgRef: RefObject<SVGSVGElement | null>;
106
+ panZoomRef: PanZoomRef;
107
+ zoom: number;
108
+ readOnly?: boolean;
109
+ }
110
+
111
+ function PolygonArtboard({
112
+ area,
113
+ onResize,
114
+ onMove,
115
+ onResizeCommit,
116
+ svgRef,
117
+ panZoomRef,
118
+ zoom,
119
+ readOnly = false,
120
+ }: PolygonArtboardProps) {
121
+ const pts = area.points ?? [];
122
+ const areaRef = useRef(area);
123
+ areaRef.current = area;
124
+
125
+ const activeVertex = useRef<number | null>(null);
126
+ const vertexStart = useRef({ vx: 0, vy: 0, mx: 0, my: 0 });
127
+
128
+ const { handleMouseDown: handleVertexDown } = useDrag(svgRef, panZoomRef, {
129
+ onDragStart: (mx, my) => {
130
+ const idx = activeVertex.current;
131
+ if (idx === null) return;
132
+ const currentPts = areaRef.current.points ?? [];
133
+ vertexStart.current = { vx: currentPts[idx][0], vy: currentPts[idx][1], mx, my };
134
+ },
135
+ onDragMove: (_dx, _dy, canvasX, canvasY) => {
136
+ const idx = activeVertex.current;
137
+ if (idx === null) return;
138
+ const { vx, vy, mx, my } = vertexStart.current;
139
+ const newX = vx + (canvasX - mx);
140
+ const newY = vy + (canvasY - my);
141
+ const currentPts = areaRef.current.points ?? [];
142
+ const newPts = currentPts.map((p, i): [number, number] =>
143
+ i === idx ? [newX, newY] : p,
144
+ );
145
+ const newArea: FloorArea = { ...areaRef.current, points: newPts };
146
+ onResize(newArea);
147
+ },
148
+ onDragEnd: () => {
149
+ onResizeCommit?.(areaRef.current);
150
+ activeVertex.current = null;
151
+ },
152
+ });
153
+
154
+ const startVertexDrag = useCallback(
155
+ (e: ReactMouseEvent, idx: number) => {
156
+ activeVertex.current = idx;
157
+ handleVertexDown(e);
158
+ },
159
+ [handleVertexDown],
160
+ );
161
+
162
+ const { handleMouseDown: handleBodyDown } = useDrag(svgRef, panZoomRef, {
163
+ onDragMove: (dx, dy) => {
164
+ onMove?.(dx, dy);
165
+ },
166
+ });
167
+
168
+ const handleDeleteVertex = useCallback(
169
+ (e: ReactMouseEvent, idx: number) => {
170
+ e.stopPropagation();
171
+ const currentPts = areaRef.current.points ?? [];
172
+ if (currentPts.length <= 3) return;
173
+ const newPts = currentPts.filter((_, i) => i !== idx);
174
+ const newArea: FloorArea = { ...areaRef.current, points: newPts };
175
+ onResize(newArea);
176
+ onResizeCommit?.(newArea);
177
+ },
178
+ [onResize, onResizeCommit],
179
+ );
180
+
181
+ const handleAddVertex = useCallback(
182
+ (e: ReactMouseEvent, insertAfterIdx: number) => {
183
+ e.stopPropagation();
184
+ const currentPts = areaRef.current.points ?? [];
185
+ const a = currentPts[insertAfterIdx];
186
+ const b = currentPts[(insertAfterIdx + 1) % currentPts.length];
187
+ const mid: [number, number] = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
188
+ const newPts = [
189
+ ...currentPts.slice(0, insertAfterIdx + 1),
190
+ mid,
191
+ ...currentPts.slice(insertAfterIdx + 1),
192
+ ];
193
+ const newArea: FloorArea = { ...areaRef.current, points: newPts };
194
+ onResize(newArea);
195
+ onResizeCommit?.(newArea);
196
+ },
197
+ [onResize, onResizeCommit],
198
+ );
199
+
200
+ if (pts.length < 3) return null;
201
+
202
+ const pointsStr = pts.map(([x, y]) => `${x},${y}`).join(' ');
203
+ const hs = HANDLE_PX / zoom;
204
+ const sw = 1.5 / zoom;
205
+ const dash = `${6 / zoom},${3 / zoom}`;
206
+
207
+ return (
208
+ <g>
209
+ <defs>
210
+ <filter id="vme-artboard-shadow" x="-4%" y="-4%" width="108%" height="108%">
211
+ <feDropShadow dx={0} dy={3 / zoom} stdDeviation={6 / zoom} floodOpacity={0.12} />
212
+ </filter>
213
+ </defs>
214
+
215
+ {/* Shadow polygon */}
216
+ <polygon
217
+ points={pointsStr}
218
+ fill="#fafaf9"
219
+ stroke="none"
220
+ filter="url(#vme-artboard-shadow)"
221
+ />
222
+
223
+ {/* Body drag target (dashed border) */}
224
+ <polygon
225
+ points={pointsStr}
226
+ fill="transparent"
227
+ stroke="#94a3b8"
228
+ strokeWidth={sw}
229
+ strokeDasharray={dash}
230
+ style={{ cursor: readOnly ? 'default' : 'move' }}
231
+ onMouseDown={readOnly ? undefined : handleBodyDown}
232
+ />
233
+
234
+ {!readOnly && (
235
+ <>
236
+ {/* Edge midpoint diamond handles — click to add vertex */}
237
+ {pts.map(([ax, ay], i) => {
238
+ const [bx, by] = pts[(i + 1) % pts.length];
239
+ const mx = (ax + bx) / 2;
240
+ const my = (ay + by) / 2;
241
+ return (
242
+ <rect
243
+ key={`mid-${i}`}
244
+ x={mx - hs * 0.75}
245
+ y={my - hs * 0.75}
246
+ width={hs * 1.5}
247
+ height={hs * 1.5}
248
+ fill="white"
249
+ stroke="#94a3b8"
250
+ strokeWidth={sw}
251
+ style={{ cursor: 'copy', transform: `rotate(45deg)`, transformOrigin: `${mx}px ${my}px` }}
252
+ onClick={e => handleAddVertex(e, i)}
253
+ />
254
+ );
255
+ })}
256
+
257
+ {/* Vertex square handles */}
258
+ {pts.map(([vx, vy], i) => (
259
+ <rect
260
+ key={`v-${i}`}
261
+ x={vx - hs}
262
+ y={vy - hs}
263
+ width={hs * 2}
264
+ height={hs * 2}
265
+ rx={1 / zoom}
266
+ fill="white"
267
+ stroke="#3b82f6"
268
+ strokeWidth={sw}
269
+ style={{ cursor: 'move' }}
270
+ onMouseDown={e => startVertexDrag(e, i)}
271
+ onDoubleClick={e => handleDeleteVertex(e, i)}
272
+ />
273
+ ))}
274
+ </>
275
+ )}
276
+ </g>
277
+ );
278
+ }
279
+
280
+ // ─── RectArtboard ─────────────────────────────────────────────────────────────
281
+ // Extracted so all hooks are at the top level of a single component (Rules of Hooks).
282
+
283
+ function RectArtboard({
284
+ area,
285
+ onResize,
286
+ onMove,
287
+ onResizeCommit,
288
+ svgRef,
289
+ panZoomRef,
290
+ zoom,
291
+ readOnly = false,
292
+ }: ArtboardProps) {
293
+ const activeHandle = useRef<HandleType | null>(null);
294
+ const areaRef = useRef(area);
295
+ areaRef.current = area;
296
+
297
+ const { handleMouseDown: handleHandleDown } = useDrag(svgRef, panZoomRef, {
298
+ onDragMove: (dx, dy) => {
299
+ if (!activeHandle.current) return;
300
+ onResize(applyHandleDelta(areaRef.current, activeHandle.current, dx, dy));
301
+ },
302
+ onDragEnd: () => {
303
+ onResizeCommit?.(areaRef.current);
304
+ activeHandle.current = null;
305
+ },
306
+ });
307
+
308
+ const startHandleDrag = useCallback(
309
+ (e: ReactMouseEvent, type: HandleType) => {
310
+ activeHandle.current = type;
311
+ handleHandleDown(e);
312
+ },
313
+ [handleHandleDown],
314
+ );
315
+
316
+ const { handleMouseDown: handleBodyDown } = useDrag(svgRef, panZoomRef, {
317
+ onDragMove: (dx, dy) => {
318
+ onMove?.(dx, dy);
319
+ },
320
+ });
321
+
322
+ const ax = area.x ?? 0;
323
+ const ay = area.y ?? 0;
324
+ const aw = area.width ?? 400;
325
+ const ah = area.height ?? 300;
326
+
327
+ const hs = HANDLE_PX / zoom;
328
+ const sw = 1.5 / zoom;
329
+ const dash = `${6 / zoom},${3 / zoom}`;
330
+
331
+ const handles: Array<{ type: HandleType; cx: number; cy: number }> = [
332
+ { type: 'nw', cx: ax, cy: ay },
333
+ { type: 'n', cx: ax + aw/2, cy: ay },
334
+ { type: 'ne', cx: ax + aw, cy: ay },
335
+ { type: 'e', cx: ax + aw, cy: ay + ah/2 },
336
+ { type: 'se', cx: ax + aw, cy: ay + ah },
337
+ { type: 's', cx: ax + aw/2, cy: ay + ah },
338
+ { type: 'sw', cx: ax, cy: ay + ah },
339
+ { type: 'w', cx: ax, cy: ay + ah/2 },
340
+ ];
341
+
342
+ return (
343
+ <g>
344
+ <defs>
345
+ <filter id="vme-artboard-shadow" x="-4%" y="-4%" width="108%" height="108%">
346
+ <feDropShadow
347
+ dx={0}
348
+ dy={3 / zoom}
349
+ stdDeviation={6 / zoom}
350
+ floodOpacity={0.12}
351
+ />
352
+ </filter>
353
+ </defs>
354
+
355
+ <rect
356
+ x={ax}
357
+ y={ay}
358
+ width={aw}
359
+ height={ah}
360
+ fill="#fafaf9"
361
+ stroke="none"
362
+ filter="url(#vme-artboard-shadow)"
363
+ />
364
+
365
+ <rect
366
+ x={ax}
367
+ y={ay}
368
+ width={aw}
369
+ height={ah}
370
+ fill="transparent"
371
+ stroke="#94a3b8"
372
+ strokeWidth={sw}
373
+ strokeDasharray={dash}
374
+ style={{ cursor: readOnly ? 'default' : 'move' }}
375
+ onMouseDown={readOnly ? undefined : handleBodyDown}
376
+ />
377
+
378
+ {!readOnly &&
379
+ handles.map(({ type, cx, cy }) => (
380
+ <rect
381
+ key={type}
382
+ x={cx - hs}
383
+ y={cy - hs}
384
+ width={hs * 2}
385
+ height={hs * 2}
386
+ rx={1 / zoom}
387
+ fill="white"
388
+ stroke="#3b82f6"
389
+ strokeWidth={sw}
390
+ style={{ cursor: HANDLE_CURSORS[type] }}
391
+ onMouseDown={e => startHandleDrag(e, type)}
392
+ />
393
+ ))}
394
+ </g>
395
+ );
396
+ }
397
+
398
+ // ─── Artboard (router) ────────────────────────────────────────────────────────
399
+
400
+ export function Artboard(props: ArtboardProps) {
401
+ if (props.area.shape === 'polygon') {
402
+ return <PolygonArtboard {...props} />;
403
+ }
404
+ return <RectArtboard {...props} />;
405
+ }