@xom11/whiteboard 0.6.4 → 0.7.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.
Files changed (66) hide show
  1. package/README.md +36 -0
  2. package/dist/chunk-3SSQKRRO.mjs +58 -0
  3. package/dist/chunk-3SSQKRRO.mjs.map +1 -0
  4. package/dist/chunk-7P7SQFOW.mjs +39 -0
  5. package/dist/chunk-7P7SQFOW.mjs.map +1 -0
  6. package/dist/chunk-BJX4YNA5.mjs +137 -0
  7. package/dist/chunk-BJX4YNA5.mjs.map +1 -0
  8. package/dist/chunk-C6SCVOMC.mjs +111 -0
  9. package/dist/chunk-C6SCVOMC.mjs.map +1 -0
  10. package/dist/chunk-DJTBZEAR.mjs +25 -0
  11. package/dist/chunk-DJTBZEAR.mjs.map +1 -0
  12. package/dist/chunk-HM7RIXJE.mjs +331 -0
  13. package/dist/chunk-HM7RIXJE.mjs.map +1 -0
  14. package/dist/chunk-HTBLO5JO.mjs +41 -0
  15. package/dist/chunk-HTBLO5JO.mjs.map +1 -0
  16. package/dist/chunk-HYXFHEDJ.mjs +129 -0
  17. package/dist/chunk-HYXFHEDJ.mjs.map +1 -0
  18. package/dist/chunk-LPM4MM45.mjs +211 -0
  19. package/dist/chunk-LPM4MM45.mjs.map +1 -0
  20. package/dist/chunk-P2AOIF7S.mjs +40 -0
  21. package/dist/chunk-P2AOIF7S.mjs.map +1 -0
  22. package/dist/chunk-SHFOGORM.mjs +44 -0
  23. package/dist/chunk-SHFOGORM.mjs.map +1 -0
  24. package/dist/chunk-X5R72SSJ.mjs +52 -0
  25. package/dist/chunk-X5R72SSJ.mjs.map +1 -0
  26. package/dist/geometry-2d.d.mts +16 -0
  27. package/dist/geometry-2d.d.ts +16 -0
  28. package/dist/geometry-2d.js +3549 -0
  29. package/dist/geometry-2d.js.map +1 -0
  30. package/dist/geometry-2d.mjs +7 -0
  31. package/dist/geometry-2d.mjs.map +1 -0
  32. package/dist/geometry-3d.d.mts +16 -0
  33. package/dist/geometry-3d.d.ts +16 -0
  34. package/dist/geometry-3d.js +2030 -0
  35. package/dist/geometry-3d.js.map +1 -0
  36. package/dist/geometry-3d.mjs +6 -0
  37. package/dist/geometry-3d.mjs.map +1 -0
  38. package/dist/graph-2d.d.mts +16 -0
  39. package/dist/graph-2d.d.ts +16 -0
  40. package/dist/graph-2d.js +1725 -0
  41. package/dist/graph-2d.js.map +1 -0
  42. package/dist/graph-2d.mjs +6 -0
  43. package/dist/graph-2d.mjs.map +1 -0
  44. package/dist/host-2QGKMGCT.mjs +1066 -0
  45. package/dist/host-2QGKMGCT.mjs.map +1 -0
  46. package/dist/host-T2W6R6SO.mjs +2859 -0
  47. package/dist/host-T2W6R6SO.mjs.map +1 -0
  48. package/dist/host-XUFON6CQ.mjs +1422 -0
  49. package/dist/host-XUFON6CQ.mjs.map +1 -0
  50. package/dist/host-Z3TEJKZA.mjs +466 -0
  51. package/dist/host-Z3TEJKZA.mjs.map +1 -0
  52. package/dist/index.d.mts +27 -146
  53. package/dist/index.d.ts +27 -146
  54. package/dist/index.js +4694 -4482
  55. package/dist/index.js.map +1 -1
  56. package/dist/index.mjs +136 -7179
  57. package/dist/index.mjs.map +1 -1
  58. package/dist/latex.d.mts +15 -0
  59. package/dist/latex.d.ts +15 -0
  60. package/dist/latex.js +750 -0
  61. package/dist/latex.js.map +1 -0
  62. package/dist/latex.mjs +6 -0
  63. package/dist/latex.mjs.map +1 -0
  64. package/dist/types-CinstD7T.d.mts +110 -0
  65. package/dist/types-CinstD7T.d.ts +110 -0
  66. package/package.json +24 -2
@@ -0,0 +1,1422 @@
1
+ "use client";
2
+ import { isGeometry3DCustomData, parseSerializedBoard3D } from './chunk-DJTBZEAR.mjs';
3
+ import { useChordShortcut, MobileToolDrawer } from './chunk-LPM4MM45.mjs';
4
+ import { paletteFor } from './chunk-HTBLO5JO.mjs';
5
+ import { useIsMobile } from './chunk-P2AOIF7S.mjs';
6
+ import { insertStampImage } from './chunk-C6SCVOMC.mjs';
7
+ import './chunk-BJTO5JO5.mjs';
8
+ import { forwardRef, useId, useRef, useState, useCallback, useEffect, useImperativeHandle, useMemo } from 'react';
9
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
10
+ import { createPortal } from 'react-dom';
11
+
12
+ // src/stamps/geometry-3d/editor/theme.ts
13
+ function paletteFor2(isDark) {
14
+ const base = paletteFor(isDark);
15
+ return {
16
+ ...base,
17
+ view3dBg: isDark ? "#1a1a1a" : "#ffffff",
18
+ axisX: "#d63b3b",
19
+ axisY: "#2d8a2d",
20
+ axisZ: "#2d6dd6"
21
+ };
22
+ }
23
+ var DEFAULT_VIEW3D = {
24
+ azimuth: 0.7,
25
+ elevation: 0.4,
26
+ bbox3D: [-3, -3, -3, 3, 3, 3]
27
+ };
28
+ var VIEW3D_ATTRS = (isDark) => {
29
+ const p = paletteFor2(isDark);
30
+ return {
31
+ az: { slider: { visible: false }, point2: { visible: false } },
32
+ el: { slider: { visible: false } },
33
+ projection: "central",
34
+ axesPosition: "border",
35
+ xAxis: { strokeColor: p.axisX, lastArrow: { type: 2 } },
36
+ yAxis: { strokeColor: p.axisY, lastArrow: { type: 2 } },
37
+ zAxis: { strokeColor: p.axisZ, lastArrow: { type: 2 } }
38
+ };
39
+ };
40
+
41
+ // src/stamps/geometry-3d/editor/handlers.ts
42
+ function createHandlerContext(deps) {
43
+ return {
44
+ ...deps,
45
+ pendingPoints: [],
46
+ pendingFlags: {},
47
+ pushedPointCoords: /* @__PURE__ */ new Map()
48
+ };
49
+ }
50
+ function refByPlaceholder(id) {
51
+ return `@id:${id}`;
52
+ }
53
+ function createPoint3D(ctx, x, y, z, label) {
54
+ const id = ctx.nextId();
55
+ const attrs = { id, size: 3 };
56
+ const ref = ctx.view.create("point3d", [x, y, z], attrs);
57
+ ctx.objMap.set(id, ref);
58
+ ctx.pushedPointCoords.set(id, [x, y, z]);
59
+ ctx.pushLog({
60
+ type: "point3d",
61
+ parents: [x, y, z],
62
+ attributes: attrs,
63
+ id,
64
+ label
65
+ });
66
+ return { id, ref, coords: [x, y, z] };
67
+ }
68
+ function resolvePoint(ctx, hit) {
69
+ if (hit.existingPointId && ctx.objMap.has(hit.existingPointId)) {
70
+ const stored = ctx.pushedPointCoords.get(hit.existingPointId);
71
+ return {
72
+ id: hit.existingPointId,
73
+ ref: ctx.objMap.get(hit.existingPointId),
74
+ coords: stored ?? [hit.x3, hit.y3, hit.z3]
75
+ };
76
+ }
77
+ return createPoint3D(ctx, hit.x3, hit.y3, hit.z3);
78
+ }
79
+ function finishPolygon(ctx, points, extraAttrs = {}) {
80
+ const id = ctx.nextId();
81
+ const refs = points.map((p) => p.ref);
82
+ const attrs = { id, ...extraAttrs };
83
+ const ref = ctx.view.create("polygon3d", [refs], attrs);
84
+ ctx.objMap.set(id, ref);
85
+ ctx.pushLog({
86
+ type: "polygon3d",
87
+ parents: [points.map((p) => refByPlaceholder(p.id))],
88
+ attributes: attrs,
89
+ id
90
+ });
91
+ }
92
+ function finishLineLike(ctx, elType, points, extraAttrs = {}) {
93
+ const id = ctx.nextId();
94
+ const refs = points.map((p) => p.ref);
95
+ const attrs = { id, ...extraAttrs };
96
+ const ref = ctx.view.create(elType, refs, attrs);
97
+ ctx.objMap.set(id, ref);
98
+ ctx.pushLog({
99
+ type: elType,
100
+ parents: points.map((p) => refByPlaceholder(p.id)),
101
+ attributes: attrs,
102
+ id
103
+ });
104
+ }
105
+ function handleToolStep(ctx, tool, hit) {
106
+ switch (tool) {
107
+ case "move":
108
+ return;
109
+ case "point": {
110
+ const coords = ctx.promptCoords("To\u1EA1 \u0111\u1ED9 \u0111i\u1EC3m (x, y, z)");
111
+ if (!coords) return;
112
+ createPoint3D(ctx, coords.x, coords.y, coords.z);
113
+ ctx.notify();
114
+ return;
115
+ }
116
+ case "segment":
117
+ case "line": {
118
+ const p = resolvePoint(ctx, hit);
119
+ ctx.pendingPoints.push(p);
120
+ if (ctx.pendingPoints.length === 2) {
121
+ const lineColor = ctx.isDark ? "#9ecbff" : "#0066cc";
122
+ const baseAttrs = {
123
+ strokeColor: lineColor,
124
+ strokeWidth: 2,
125
+ visible: true,
126
+ fixed: true
127
+ };
128
+ if (tool === "segment") {
129
+ baseAttrs.straightFirst = false;
130
+ baseAttrs.straightLast = false;
131
+ }
132
+ finishLineLike(ctx, "line3d", ctx.pendingPoints, baseAttrs);
133
+ ctx.pendingPoints = [];
134
+ }
135
+ ctx.notify();
136
+ return;
137
+ }
138
+ case "plane": {
139
+ const p = resolvePoint(ctx, hit);
140
+ ctx.pendingPoints.push(p);
141
+ if (ctx.pendingPoints.length === 3) {
142
+ finishLineLike(ctx, "plane3d", ctx.pendingPoints);
143
+ ctx.pendingPoints = [];
144
+ }
145
+ ctx.notify();
146
+ return;
147
+ }
148
+ case "triangle": {
149
+ const p = resolvePoint(ctx, hit);
150
+ ctx.pendingPoints.push(p);
151
+ if (ctx.pendingPoints.length === 3) {
152
+ finishPolygon(ctx, ctx.pendingPoints);
153
+ ctx.pendingPoints = [];
154
+ }
155
+ ctx.notify();
156
+ return;
157
+ }
158
+ case "polygon": {
159
+ if (ctx.pendingPoints.length >= 3 && hit.existingPointId === ctx.pendingPoints[0].id) {
160
+ finishPolygon(ctx, ctx.pendingPoints);
161
+ ctx.pendingPoints = [];
162
+ ctx.notify();
163
+ return;
164
+ }
165
+ const p = resolvePoint(ctx, hit);
166
+ ctx.pendingPoints.push(p);
167
+ ctx.notify();
168
+ return;
169
+ }
170
+ case "label": {
171
+ if (!hit.existingPointId) return;
172
+ const text = ctx.promptText("N\u1ED9i dung nh\xE3n");
173
+ if (!text) return;
174
+ const id = ctx.nextId();
175
+ const pointLog = ctx.pushedPointCoords.get(hit.existingPointId);
176
+ if (!pointLog) return;
177
+ const [x, y, z] = pointLog;
178
+ const attrs = {
179
+ id,
180
+ fontSize: 14,
181
+ strokeColor: ctx.isDark ? "#f5f5f5" : "#111111"
182
+ };
183
+ const ref = ctx.view.create("text3d", [x, y, z, text], attrs);
184
+ ctx.objMap.set(id, ref);
185
+ ctx.pushLog({
186
+ type: "text3d",
187
+ parents: [x, y, z, text],
188
+ attributes: attrs,
189
+ id,
190
+ label: text
191
+ });
192
+ ctx.notify();
193
+ return;
194
+ }
195
+ // Solids + curved handled in B8, B9
196
+ default:
197
+ handleSolidStep(ctx, tool, hit);
198
+ return;
199
+ }
200
+ }
201
+ function handleSolidStep(ctx, tool, hit) {
202
+ switch (tool) {
203
+ case "tetrahedron": {
204
+ const p = resolvePoint(ctx, hit);
205
+ ctx.pendingPoints.push(p);
206
+ if (ctx.pendingPoints.length === 4) {
207
+ const [a, b, c, d] = ctx.pendingPoints;
208
+ finishPolyhedron(ctx, [
209
+ [a, b, c],
210
+ [a, b, d],
211
+ [a, c, d],
212
+ [b, c, d]
213
+ ]);
214
+ ctx.pendingPoints = [];
215
+ }
216
+ ctx.notify();
217
+ return;
218
+ }
219
+ case "parallelepiped": {
220
+ const origin = resolvePoint(ctx, hit);
221
+ const v1 = ctx.promptCoords("Vector c\u1EA1nh 1 (dx, dy, dz)");
222
+ const v2 = ctx.promptCoords("Vector c\u1EA1nh 2 (dx, dy, dz)");
223
+ const v3 = ctx.promptCoords("Vector c\u1EA1nh 3 (dx, dy, dz)");
224
+ if (!v1 || !v2 || !v3) return;
225
+ const [ox, oy, oz] = origin.coords;
226
+ const c1 = createPoint3D(ctx, ox + v1.x, oy + v1.y, oz + v1.z);
227
+ const c2 = createPoint3D(ctx, ox + v2.x, oy + v2.y, oz + v2.z);
228
+ const c3 = createPoint3D(ctx, ox + v3.x, oy + v3.y, oz + v3.z);
229
+ const c12 = createPoint3D(
230
+ ctx,
231
+ ox + v1.x + v2.x,
232
+ oy + v1.y + v2.y,
233
+ oz + v1.z + v2.z
234
+ );
235
+ const c13 = createPoint3D(
236
+ ctx,
237
+ ox + v1.x + v3.x,
238
+ oy + v1.y + v3.y,
239
+ oz + v1.z + v3.z
240
+ );
241
+ const c23 = createPoint3D(
242
+ ctx,
243
+ ox + v2.x + v3.x,
244
+ oy + v2.y + v3.y,
245
+ oz + v2.z + v3.z
246
+ );
247
+ const c123 = createPoint3D(
248
+ ctx,
249
+ ox + v1.x + v2.x + v3.x,
250
+ oy + v1.y + v2.y + v3.y,
251
+ oz + v1.z + v2.z + v3.z
252
+ );
253
+ finishPolyhedron(ctx, [
254
+ [origin, c1, c12, c2],
255
+ [origin, c1, c13, c3],
256
+ [origin, c2, c23, c3],
257
+ [c123, c12, c1, c13],
258
+ [c123, c12, c2, c23],
259
+ [c123, c13, c3, c23]
260
+ ]);
261
+ ctx.pendingPoints = [];
262
+ ctx.notify();
263
+ return;
264
+ }
265
+ case "prism": {
266
+ if (ctx.pendingPoints.length >= 3 && hit.existingPointId === ctx.pendingPoints[0].id) {
267
+ const base = ctx.pendingPoints;
268
+ const height = ctx.promptNumber("Chi\u1EC1u cao (theo tr\u1EE5c z)");
269
+ if (!height) return;
270
+ const top = base.map(
271
+ (bp) => createPoint3D(ctx, bp.coords[0], bp.coords[1], bp.coords[2] + height)
272
+ );
273
+ const faces = [base, top];
274
+ for (let i = 0; i < base.length; i++) {
275
+ const next = (i + 1) % base.length;
276
+ faces.push([base[i], base[next], top[next], top[i]]);
277
+ }
278
+ finishPolyhedron(ctx, faces);
279
+ ctx.pendingPoints = [];
280
+ ctx.notify();
281
+ return;
282
+ }
283
+ const p = resolvePoint(ctx, hit);
284
+ ctx.pendingPoints.push(p);
285
+ ctx.notify();
286
+ return;
287
+ }
288
+ case "pyramid": {
289
+ const baseDone = ctx.pendingFlags.pyramidBaseDone === true;
290
+ if (!baseDone && ctx.pendingPoints.length >= 3 && hit.existingPointId === ctx.pendingPoints[0].id) {
291
+ ctx.pendingFlags.pyramidBaseDone = true;
292
+ ctx.notify();
293
+ return;
294
+ }
295
+ if (baseDone) {
296
+ const base = ctx.pendingPoints;
297
+ const apex = createPoint3D(ctx, hit.x3, hit.y3, hit.z3);
298
+ const faces = [base];
299
+ for (let i = 0; i < base.length; i++) {
300
+ const next = (i + 1) % base.length;
301
+ faces.push([base[i], base[next], apex]);
302
+ }
303
+ finishPolyhedron(ctx, faces);
304
+ ctx.pendingPoints = [];
305
+ ctx.pendingFlags.pyramidBaseDone = false;
306
+ ctx.notify();
307
+ return;
308
+ }
309
+ const p = resolvePoint(ctx, hit);
310
+ ctx.pendingPoints.push(p);
311
+ ctx.notify();
312
+ return;
313
+ }
314
+ // Curved → B9
315
+ default:
316
+ handleCurvedStep(ctx, tool, hit);
317
+ return;
318
+ }
319
+ }
320
+ function finishPolyhedron(ctx, faces) {
321
+ const faceColor = ctx.isDark ? "rgba(150, 180, 220, 0.35)" : "rgba(60, 120, 200, 0.25)";
322
+ const edgeColor = ctx.isDark ? "#9ecbff" : "#0066cc";
323
+ for (const face of faces) {
324
+ finishPolygon(ctx, face, {
325
+ fillColor: faceColor,
326
+ fillOpacity: 1,
327
+ strokeColor: edgeColor,
328
+ strokeWidth: 1.5,
329
+ visible: true
330
+ });
331
+ }
332
+ }
333
+ var CURVED_SEGMENTS = 16;
334
+ function handleCurvedStep(ctx, tool, hit) {
335
+ switch (tool) {
336
+ case "sphere": {
337
+ const radius = ctx.promptNumber("B\xE1n k\xEDnh m\u1EB7t c\u1EA7u");
338
+ if (radius == null) return;
339
+ const center = resolvePoint(ctx, hit);
340
+ const id = ctx.nextId();
341
+ const ref = ctx.view.create("sphere3d", [center.ref, radius], { id });
342
+ ctx.objMap.set(id, ref);
343
+ ctx.pushLog({
344
+ type: "sphere3d",
345
+ parents: [refByPlaceholder(center.id), radius],
346
+ attributes: { id },
347
+ id
348
+ });
349
+ ctx.notify();
350
+ return;
351
+ }
352
+ case "cone": {
353
+ const baseDone = ctx.pendingFlags.coneBaseDone === true;
354
+ if (!baseDone) {
355
+ const radius2 = ctx.promptNumber("B\xE1n k\xEDnh \u0111\xE1y");
356
+ if (radius2 == null) return;
357
+ const center2 = resolvePoint(ctx, hit);
358
+ ctx.pendingFlags.coneCenter = center2;
359
+ ctx.pendingFlags.coneRadius = radius2;
360
+ ctx.pendingFlags.coneBaseDone = true;
361
+ ctx.notify();
362
+ return;
363
+ }
364
+ const center = ctx.pendingFlags.coneCenter;
365
+ const radius = ctx.pendingFlags.coneRadius;
366
+ const apex = createPoint3D(ctx, hit.x3, hit.y3, hit.z3);
367
+ const [cx, cy, cz] = center.coords;
368
+ const basePoints = [];
369
+ for (let i = 0; i < CURVED_SEGMENTS; i++) {
370
+ const theta = i / CURVED_SEGMENTS * Math.PI * 2;
371
+ basePoints.push(
372
+ createPoint3D(
373
+ ctx,
374
+ cx + radius * Math.cos(theta),
375
+ cy + radius * Math.sin(theta),
376
+ cz
377
+ )
378
+ );
379
+ }
380
+ const faces = [basePoints];
381
+ for (let i = 0; i < CURVED_SEGMENTS; i++) {
382
+ faces.push([basePoints[i], basePoints[(i + 1) % CURVED_SEGMENTS], apex]);
383
+ }
384
+ finishPolyhedron(ctx, faces);
385
+ ctx.pendingFlags.coneBaseDone = false;
386
+ ctx.pendingFlags.coneCenter = void 0;
387
+ ctx.pendingFlags.coneRadius = void 0;
388
+ ctx.notify();
389
+ return;
390
+ }
391
+ case "cylinder": {
392
+ const radius = ctx.promptNumber("B\xE1n k\xEDnh \u0111\xE1y");
393
+ if (radius == null) return;
394
+ const height = ctx.promptNumber("Chi\u1EC1u cao (theo tr\u1EE5c z)");
395
+ if (height == null) return;
396
+ const center = resolvePoint(ctx, hit);
397
+ const [cx, cy, cz] = center.coords;
398
+ const basePoints = [];
399
+ const topPoints = [];
400
+ for (let i = 0; i < CURVED_SEGMENTS; i++) {
401
+ const theta = i / CURVED_SEGMENTS * Math.PI * 2;
402
+ basePoints.push(
403
+ createPoint3D(
404
+ ctx,
405
+ cx + radius * Math.cos(theta),
406
+ cy + radius * Math.sin(theta),
407
+ cz
408
+ )
409
+ );
410
+ topPoints.push(
411
+ createPoint3D(
412
+ ctx,
413
+ cx + radius * Math.cos(theta),
414
+ cy + radius * Math.sin(theta),
415
+ cz + height
416
+ )
417
+ );
418
+ }
419
+ const faces = [basePoints, topPoints];
420
+ for (let i = 0; i < CURVED_SEGMENTS; i++) {
421
+ const next = (i + 1) % CURVED_SEGMENTS;
422
+ faces.push([basePoints[i], basePoints[next], topPoints[next], topPoints[i]]);
423
+ }
424
+ finishPolyhedron(ctx, faces);
425
+ ctx.notify();
426
+ return;
427
+ }
428
+ // 'solidofrevolution' removed in 0.6.1 — `solidofrevolution3d` is not a valid
429
+ // JSXGraph 1.12.2 element. See Bug #8.
430
+ default:
431
+ return;
432
+ }
433
+ }
434
+ var MiniBoard3D = forwardRef(function MiniBoard3D2({ isDark, initialState }, ref) {
435
+ const reactId = useId();
436
+ const containerId = `geom3d_${reactId.replace(/[^a-zA-Z0-9_]/g, "_")}`;
437
+ const containerRef = useRef(null);
438
+ const boardRef = useRef(null);
439
+ const viewRef = useRef(null);
440
+ const toolRef = useRef("move");
441
+ const logRef = useRef([]);
442
+ const objMapRef = useRef(/* @__PURE__ */ new Map());
443
+ const subsRef = useRef(/* @__PURE__ */ new Set());
444
+ const initialBbox3D = useRef(
445
+ initialState?.view.bbox3D ?? DEFAULT_VIEW3D.bbox3D
446
+ );
447
+ const ctxRef = useRef(null);
448
+ const pointerHandlerRef = useRef(null);
449
+ const [showAxes, setShowAxes] = useState(initialState?.showAxes ?? true);
450
+ const [showMesh, setShowMesh] = useState(initialState?.showMesh ?? false);
451
+ const notify = useCallback(() => {
452
+ for (const cb of subsRef.current) cb();
453
+ }, []);
454
+ useEffect(() => {
455
+ const div = containerRef.current;
456
+ if (!div) return;
457
+ let cancelled = false;
458
+ let JXG = null;
459
+ let board = null;
460
+ void (async () => {
461
+ JXG = (await import('jsxgraph')).default;
462
+ if (cancelled || !containerRef.current) return;
463
+ JXG.Options.text.display = "internal";
464
+ board = JXG.JSXGraph.initBoard(div, {
465
+ boundingbox: [-6, 6, 6, -6],
466
+ axis: false,
467
+ showCopyright: false,
468
+ showNavigation: false,
469
+ renderer: "svg"
470
+ });
471
+ boardRef.current = board;
472
+ const initView = initialState?.view ?? DEFAULT_VIEW3D;
473
+ const baseAttrs = VIEW3D_ATTRS(isDark);
474
+ const view = board.create(
475
+ "view3d",
476
+ [
477
+ [-5, -5],
478
+ [10, 10],
479
+ [
480
+ [initView.bbox3D[0], initView.bbox3D[3]],
481
+ [initView.bbox3D[1], initView.bbox3D[4]],
482
+ [initView.bbox3D[2], initView.bbox3D[5]]
483
+ ]
484
+ ],
485
+ {
486
+ ...baseAttrs,
487
+ az: { ...baseAttrs.az, value: initView.azimuth },
488
+ el: { ...baseAttrs.el, value: initView.elevation }
489
+ }
490
+ );
491
+ viewRef.current = view;
492
+ let idCounter = 1;
493
+ const ctx = createHandlerContext({
494
+ view,
495
+ pushLog: (e) => {
496
+ logRef.current.push(e);
497
+ notify();
498
+ },
499
+ objMap: objMapRef.current,
500
+ nextId: () => `obj_${Date.now().toString(36)}_${(idCounter++).toString(36)}`,
501
+ isDark,
502
+ promptCoords: (label) => {
503
+ const raw = window.prompt(`${label}
504
+ (\u0111\u1ECBnh d\u1EA1ng "x,y,z")`, "0,0,0");
505
+ if (!raw) return null;
506
+ const parts = raw.split(",").map((s) => Number(s.trim()));
507
+ if (parts.length !== 3 || parts.some((n) => !isFinite(n))) return null;
508
+ return { x: parts[0], y: parts[1], z: parts[2] };
509
+ },
510
+ promptNumber: (label) => {
511
+ const raw = window.prompt(label, "1");
512
+ if (raw == null) return null;
513
+ const n = Number(raw);
514
+ return isFinite(n) ? n : null;
515
+ },
516
+ promptText: (label) => {
517
+ const raw = window.prompt(label, "");
518
+ return raw == null ? null : raw;
519
+ },
520
+ notify
521
+ });
522
+ ctxRef.current = ctx;
523
+ function findExistingPointAt(clientX, clientY) {
524
+ const containerRect = div.getBoundingClientRect();
525
+ const localX = clientX - containerRect.left;
526
+ const localY = clientY - containerRect.top;
527
+ const PICK = 18;
528
+ const svg = div.querySelector("svg");
529
+ if (!svg) return void 0;
530
+ for (const [id, obj] of objMapRef.current) {
531
+ const entry = obj;
532
+ if (entry?.elType !== "point3d") continue;
533
+ const sc = entry.element2D?.coords?.scrCoords;
534
+ if (!sc || sc.length < 3) continue;
535
+ const dx = sc[1] - localX;
536
+ const dy = sc[2] - localY;
537
+ if (dx * dx + dy * dy <= PICK * PICK) return id;
538
+ }
539
+ return void 0;
540
+ }
541
+ const handlePointerDown = (e) => {
542
+ const tool = toolRef.current;
543
+ if (tool === "move") return;
544
+ const existingPointId = findExistingPointAt(e.clientX, e.clientY);
545
+ let x3 = 0;
546
+ let y3 = 0;
547
+ const z3 = 0;
548
+ try {
549
+ const board2d = boardRef.current;
550
+ if (board2d?.getUsrCoordsOfMouse) {
551
+ const uc = board2d.getUsrCoordsOfMouse(e);
552
+ if (Array.isArray(uc) && uc.length >= 2) {
553
+ x3 = uc[0];
554
+ y3 = uc[1];
555
+ }
556
+ }
557
+ } catch {
558
+ }
559
+ const hit = { x3, y3, z3, existingPointId };
560
+ handleToolStep(ctx, tool, hit);
561
+ };
562
+ const svgEl = div.querySelector("svg");
563
+ const targetEl = svgEl ?? div;
564
+ const handlePointerDownEv = (e) => handlePointerDown(e);
565
+ targetEl.addEventListener("pointerdown", handlePointerDownEv);
566
+ pointerHandlerRef.current = { el: targetEl, fn: handlePointerDownEv };
567
+ if (initialState?.elements?.length) {
568
+ const map = objMapRef.current;
569
+ for (const el of initialState.elements) {
570
+ const parents = el.parents.map(
571
+ (p2) => typeof p2 === "string" && p2.startsWith("@id:") ? map.get(p2.slice(4)) : p2
572
+ );
573
+ const obj = view.create(el.type, parents, {
574
+ ...el.attributes,
575
+ id: el.id,
576
+ name: el.label
577
+ });
578
+ map.set(el.id, obj);
579
+ logRef.current.push(el);
580
+ }
581
+ }
582
+ })();
583
+ return () => {
584
+ cancelled = true;
585
+ if (pointerHandlerRef.current) {
586
+ pointerHandlerRef.current.el.removeEventListener(
587
+ "pointerdown",
588
+ pointerHandlerRef.current.fn
589
+ );
590
+ pointerHandlerRef.current = null;
591
+ }
592
+ try {
593
+ if (board && JXG) JXG.JSXGraph.freeBoard(board);
594
+ } catch {
595
+ }
596
+ boardRef.current = null;
597
+ viewRef.current = null;
598
+ ctxRef.current = null;
599
+ objMapRef.current.clear();
600
+ };
601
+ }, []);
602
+ const handleRef = useRef(null);
603
+ handleRef.current = {
604
+ getContainer: () => containerRef.current,
605
+ getTool: () => toolRef.current,
606
+ setTool: (t) => {
607
+ toolRef.current = t;
608
+ notify();
609
+ },
610
+ // Sync toạ độ live của free point3d về log trước khi trả ra. JSXGraph
611
+ // cho phép drag point3d (parents=[x,y,z] không có ref), việc drag chỉ
612
+ // cập nhật obj.X()/Y()/Z() chứ không đụng log → re-edit + Chèn sẽ
613
+ // serialize toạ độ cũ → SVG không đổi → fileId trùng → user thấy
614
+ // "k thay đổi". Line/plane/polygon/sphere tham chiếu point qua @id nên
615
+ // auto-update theo.
616
+ getCreationLog: () => logRef.current.map((e) => {
617
+ if (e.type !== "point3d") return { ...e };
618
+ const parents = e.parents;
619
+ if (!Array.isArray(parents) || parents.length !== 3) return { ...e };
620
+ if (typeof parents[0] !== "number" || typeof parents[1] !== "number" || typeof parents[2] !== "number") return { ...e };
621
+ const obj = objMapRef.current.get(e.id);
622
+ if (!obj || typeof obj.X !== "function" || typeof obj.Y !== "function" || typeof obj.Z !== "function") return { ...e };
623
+ const x = obj.X();
624
+ const y = obj.Y();
625
+ const z = obj.Z();
626
+ if (!Number.isFinite(x) || !Number.isFinite(y) || !Number.isFinite(z)) return { ...e };
627
+ return { ...e, parents: [x, y, z] };
628
+ }),
629
+ pushLog: (e) => {
630
+ logRef.current.push(e);
631
+ notify();
632
+ },
633
+ getViewState: () => {
634
+ const v = viewRef.current;
635
+ return {
636
+ azimuth: v?.az?.Value?.() ?? DEFAULT_VIEW3D.azimuth,
637
+ elevation: v?.el?.Value?.() ?? DEFAULT_VIEW3D.elevation,
638
+ bbox3D: initialBbox3D.current
639
+ };
640
+ },
641
+ getBbox: () => [-6, 6, 6, -6],
642
+ getShowAxes: () => showAxes,
643
+ getShowMesh: () => showMesh,
644
+ setShowAxes: (b) => {
645
+ setShowAxes(b);
646
+ notify();
647
+ },
648
+ setShowMesh: (b) => {
649
+ setShowMesh(b);
650
+ notify();
651
+ },
652
+ resetView: () => {
653
+ notify();
654
+ },
655
+ undo: () => {
656
+ logRef.current.pop();
657
+ notify();
658
+ },
659
+ canUndo: () => logRef.current.length > 0,
660
+ snapshotSVG: () => {
661
+ const div = containerRef.current;
662
+ if (!div) return { svgString: "", width: 0, height: 0 };
663
+ const svg = div.querySelector("svg");
664
+ if (!svg) return { svgString: "", width: 0, height: 0 };
665
+ const clone = svg.cloneNode(true);
666
+ const rect = svg.getBoundingClientRect();
667
+ const width = rect.width || 600;
668
+ const height = rect.height || 600;
669
+ clone.setAttribute("width", String(width));
670
+ clone.setAttribute("height", String(height));
671
+ return {
672
+ svgString: new XMLSerializer().serializeToString(clone),
673
+ width,
674
+ height
675
+ };
676
+ },
677
+ subscribe: (cb) => {
678
+ subsRef.current.add(cb);
679
+ return () => {
680
+ subsRef.current.delete(cb);
681
+ };
682
+ }
683
+ };
684
+ useImperativeHandle(ref, () => handleRef.current, []);
685
+ const p = paletteFor2(isDark);
686
+ return /* @__PURE__ */ jsx(
687
+ "div",
688
+ {
689
+ ref: containerRef,
690
+ id: containerId,
691
+ style: {
692
+ width: "100%",
693
+ height: "100%",
694
+ background: p.view3dBg,
695
+ position: "relative",
696
+ // Clip JSXGraph mesh3d/bounding-box paths that project outside the
697
+ // board container (Bug #4) — without this they overlap LeftPanel and
698
+ // block pointer events.
699
+ overflow: "hidden"
700
+ }
701
+ }
702
+ );
703
+ });
704
+ var EditorPanel = forwardRef(function EditorPanel2({ isDark, initial, onInsert, onClose, isMobile = false, withLeftPanel = false, onBoardReady, onOpenDrawer }, ref) {
705
+ const boardRef = useRef(null);
706
+ const [ready, setReady] = useState(false);
707
+ const onBoardReadyRef = useRef(onBoardReady);
708
+ onBoardReadyRef.current = onBoardReady;
709
+ const setBoard = useCallback((h) => {
710
+ boardRef.current = h;
711
+ setReady(!!h);
712
+ onBoardReadyRef.current?.(h);
713
+ }, []);
714
+ const performInsert = useCallback(() => {
715
+ const board = boardRef.current;
716
+ if (!board) return false;
717
+ const log = board.getCreationLog();
718
+ if (log.length === 0) return false;
719
+ const view = board.getViewState();
720
+ const state = {
721
+ version: 1,
722
+ bbox: board.getBbox(),
723
+ view,
724
+ showAxes: board.getShowAxes(),
725
+ showMesh: board.getShowMesh(),
726
+ elements: log
727
+ };
728
+ const snap = board.snapshotSVG();
729
+ onInsert(JSON.stringify(state), snap.svgString, snap.width, snap.height);
730
+ return true;
731
+ }, [onInsert]);
732
+ useImperativeHandle(
733
+ ref,
734
+ () => ({
735
+ tryInsert: performInsert,
736
+ hasContent: () => (boardRef.current?.getCreationLog().length ?? 0) > 0
737
+ }),
738
+ [performInsert]
739
+ );
740
+ const handleInsert = useCallback(() => {
741
+ performInsert();
742
+ }, [performInsert]);
743
+ const wrapperStyle = isMobile ? { position: "fixed", inset: 0, zIndex: 40 } : {
744
+ position: "absolute",
745
+ top: "50%",
746
+ left: withLeftPanel ? "calc(50% + 120px)" : "50%",
747
+ transform: "translate(-50%, -50%)",
748
+ zIndex: 40
749
+ };
750
+ return /* @__PURE__ */ jsxs(
751
+ "div",
752
+ {
753
+ role: "dialog",
754
+ "aria-label": "D\u1EF1ng h\xECnh h\u1ECDc 3D",
755
+ "data-testid": "geom3d-editor-panel",
756
+ "data-stamp-area": "true",
757
+ "data-mobile-editor": isMobile ? "true" : void 0,
758
+ style: wrapperStyle,
759
+ className: [
760
+ isDark ? "theme--dark " : "",
761
+ "flex flex-col overflow-hidden bg-white",
762
+ isMobile ? "h-full w-full" : "h-[600px] max-h-[85vh] w-[760px] max-w-[calc(100vw-280px)] rounded-lg border border-slate-300 shadow-2xl ring-1 ring-black/5"
763
+ ].join(" "),
764
+ children: [
765
+ /* @__PURE__ */ jsxs("header", { className: "flex items-center gap-2 border-b border-slate-200 bg-gradient-to-r from-blue-600 to-cyan-600 px-3 py-2 text-white", children: [
766
+ isMobile && /* @__PURE__ */ jsx(
767
+ "button",
768
+ {
769
+ type: "button",
770
+ onClick: onOpenDrawer,
771
+ "aria-label": "M\u1EDF ng\u0103n c\xF4ng c\u1EE5",
772
+ className: "-ml-1 inline-flex h-10 w-10 items-center justify-center rounded transition hover:bg-white/15",
773
+ children: /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
774
+ /* @__PURE__ */ jsx("line", { x1: "4", y1: "6", x2: "20", y2: "6" }),
775
+ /* @__PURE__ */ jsx("line", { x1: "4", y1: "12", x2: "20", y2: "12" }),
776
+ /* @__PURE__ */ jsx("line", { x1: "4", y1: "18", x2: "20", y2: "18" })
777
+ ] })
778
+ }
779
+ ),
780
+ /* @__PURE__ */ jsxs("h3", { className: "flex flex-1 items-center gap-2 text-sm font-semibold", children: [
781
+ /* @__PURE__ */ jsx("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M4 7 L14 4 L20 7 L14 10 Z M4 7 L4 17 L14 20 L14 10 M14 20 L20 17 L20 7" }) }),
782
+ "H\xECnh h\u1ECDc kh\xF4ng gian (3D)"
783
+ ] }),
784
+ isMobile && /* @__PURE__ */ jsx(
785
+ "button",
786
+ {
787
+ type: "button",
788
+ onClick: handleInsert,
789
+ disabled: !ready,
790
+ "data-testid": "geom3d-insert-btn-mobile",
791
+ className: "rounded bg-white/15 px-3 py-1.5 text-xs font-semibold transition hover:bg-white/25 disabled:opacity-50",
792
+ children: "Ch\xE8n"
793
+ }
794
+ ),
795
+ /* @__PURE__ */ jsx(
796
+ "button",
797
+ {
798
+ onClick: onClose,
799
+ "aria-label": "\u0110\xF3ng",
800
+ className: "inline-flex h-9 w-9 items-center justify-center rounded transition hover:bg-white/15",
801
+ children: /* @__PURE__ */ jsxs("svg", { width: "18", height: "18", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
802
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
803
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" })
804
+ ] })
805
+ }
806
+ )
807
+ ] }),
808
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1", children: /* @__PURE__ */ jsx(MiniBoard3D, { ref: setBoard, isDark, initialState: initial }) }),
809
+ !isMobile && /* @__PURE__ */ jsxs("footer", { className: "flex items-center justify-between border-t border-slate-200 bg-slate-50 px-3 py-2", children: [
810
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-slate-500", children: "Ch\u1ECDn c\xF4ng c\u1EE5 b\xEAn tr\xE1i, click tr\xEAn b\u1EA3ng \u0111\u1EC3 d\u1EF1ng h\xECnh." }),
811
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
812
+ /* @__PURE__ */ jsx(
813
+ "button",
814
+ {
815
+ onClick: onClose,
816
+ className: "rounded border border-slate-300 bg-white px-3 py-1 text-xs font-medium text-slate-700 transition hover:bg-slate-100",
817
+ children: "Hu\u1EF7"
818
+ }
819
+ ),
820
+ /* @__PURE__ */ jsx(
821
+ "button",
822
+ {
823
+ onClick: handleInsert,
824
+ disabled: !ready,
825
+ "data-testid": "geom3d-insert-btn",
826
+ className: "rounded bg-blue-600 px-3 py-1 text-xs font-medium text-white transition hover:bg-blue-700 disabled:opacity-50",
827
+ children: "Ch\xE8n"
828
+ }
829
+ )
830
+ ] })
831
+ ] })
832
+ ]
833
+ }
834
+ );
835
+ });
836
+
837
+ // src/stamps/geometry-3d/editor/tools.ts
838
+ var GROUP_LABELS_3D = {
839
+ view: "Xem",
840
+ primitive: "C\u01A1 b\u1EA3n",
841
+ solid: "Kh\u1ED1i \u0111a di\u1EC7n",
842
+ curved: "Kh\u1ED1i cong",
843
+ meta: "Kh\xE1c"
844
+ };
845
+ var GROUP_ORDER_3D = [
846
+ "view",
847
+ "primitive",
848
+ "solid",
849
+ "curved",
850
+ "meta"
851
+ ];
852
+ var A_CODE_3D = "A".charCodeAt(0);
853
+ function letterForGroup3D(g) {
854
+ const idx = GROUP_ORDER_3D.indexOf(g);
855
+ return idx >= 0 ? String.fromCharCode(A_CODE_3D + idx) : "";
856
+ }
857
+ var TOOLS_3D = [
858
+ { key: "move", label: "Di chuy\u1EC3n", group: "view", stepsRequired: 0 },
859
+ { key: "point", label: "\u0110i\u1EC3m", group: "primitive", stepsRequired: 1, hint: "Nh\u1EADp (x, y, z)" },
860
+ { key: "segment", label: "\u0110o\u1EA1n th\u1EB3ng", group: "primitive", stepsRequired: 2 },
861
+ { key: "line", label: "\u0110\u01B0\u1EDDng th\u1EB3ng", group: "primitive", stepsRequired: 2 },
862
+ { key: "plane", label: "M\u1EB7t ph\u1EB3ng", group: "primitive", stepsRequired: 3 },
863
+ { key: "triangle", label: "Tam gi\xE1c", group: "primitive", stepsRequired: 3 },
864
+ {
865
+ key: "polygon",
866
+ label: "\u0110a gi\xE1c",
867
+ group: "primitive",
868
+ stepsRequired: 3,
869
+ hint: "Click tr\u1EDF l\u1EA1i \u0111i\u1EC3m \u0111\u1EA7u \u0111\u1EC3 \u0111\xF3ng"
870
+ },
871
+ { key: "tetrahedron", label: "T\u1EE9 di\u1EC7n", group: "solid", stepsRequired: 4 },
872
+ {
873
+ key: "parallelepiped",
874
+ label: "H\xECnh h\u1ED9p",
875
+ group: "solid",
876
+ stepsRequired: 1,
877
+ hint: "1 \u0111\u1EC9nh + 3 vector"
878
+ },
879
+ {
880
+ key: "prism",
881
+ label: "L\u0103ng tr\u1EE5",
882
+ group: "solid",
883
+ stepsRequired: 3,
884
+ hint: "\u0110a gi\xE1c \u0111\xE1y + chi\u1EC1u cao"
885
+ },
886
+ {
887
+ key: "pyramid",
888
+ label: "Ch\xF3p",
889
+ group: "solid",
890
+ stepsRequired: 4,
891
+ hint: "\u0110a gi\xE1c \u0111\xE1y + \u0111\u1EC9nh"
892
+ },
893
+ { key: "sphere", label: "M\u1EB7t c\u1EA7u", group: "curved", stepsRequired: 1, hint: "T\xE2m + b\xE1n k\xEDnh" },
894
+ {
895
+ key: "cone",
896
+ label: "H\xECnh n\xF3n",
897
+ group: "curved",
898
+ stepsRequired: 2,
899
+ hint: "T\xE2m \u0111\xE1y + b\xE1n k\xEDnh + \u0111\u1EC9nh"
900
+ },
901
+ {
902
+ key: "cylinder",
903
+ label: "H\xECnh tr\u1EE5",
904
+ group: "curved",
905
+ stepsRequired: 1,
906
+ hint: "T\xE2m \u0111\xE1y + b\xE1n k\xEDnh + chi\u1EC1u cao"
907
+ },
908
+ { key: "label", label: "Nh\xE3n", group: "meta", stepsRequired: 1, hint: "G\u1EAFn v\xE0o \u0111i\u1EC3m" }
909
+ ];
910
+ function ToolButton({ toolKey, label, hint, active, onClick, icon, badge }) {
911
+ return /* @__PURE__ */ jsxs(
912
+ "button",
913
+ {
914
+ type: "button",
915
+ title: hint ? `${label} \u2014 ${hint}` : label,
916
+ "aria-label": label,
917
+ "aria-pressed": active,
918
+ onClick,
919
+ "data-active": active || void 0,
920
+ "data-tool": toolKey,
921
+ className: [
922
+ "relative flex h-8 items-center justify-center rounded-md transition",
923
+ active ? "bg-blue-600 text-white shadow-sm" : "text-slate-700 hover:bg-slate-100 hover:text-slate-900"
924
+ ].join(" "),
925
+ children: [
926
+ icon,
927
+ badge
928
+ ]
929
+ }
930
+ );
931
+ }
932
+ var stroke = {
933
+ fill: "none",
934
+ stroke: "currentColor",
935
+ strokeWidth: 1.5,
936
+ strokeLinecap: "round",
937
+ strokeLinejoin: "round"
938
+ };
939
+ var ICONS_3D = {
940
+ move: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", ...stroke, children: /* @__PURE__ */ jsx("path", { d: "M5 9l-3 3 3 3M19 9l3 3-3 3M9 5l3-3 3 3M9 19l3 3 3-3" }) }),
941
+ point: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "3", fill: "currentColor" }) }),
942
+ segment: /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", ...stroke, children: [
943
+ /* @__PURE__ */ jsx("line", { x1: "4", y1: "20", x2: "20", y2: "4" }),
944
+ /* @__PURE__ */ jsx("circle", { cx: "4", cy: "20", r: "1.5", fill: "currentColor", stroke: "none" }),
945
+ /* @__PURE__ */ jsx("circle", { cx: "20", cy: "4", r: "1.5", fill: "currentColor", stroke: "none" })
946
+ ] }),
947
+ line: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", ...stroke, children: /* @__PURE__ */ jsx("line", { x1: "2", y1: "22", x2: "22", y2: "2" }) }),
948
+ plane: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", ...stroke, children: /* @__PURE__ */ jsx("path", { d: "M3 18 L8 8 L21 6 L16 18 Z" }) }),
949
+ triangle: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", ...stroke, children: /* @__PURE__ */ jsx("path", { d: "M12 4 L21 20 L3 20 Z" }) }),
950
+ polygon: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", ...stroke, children: /* @__PURE__ */ jsx("path", { d: "M12 3 L20 9 L17 19 L7 19 L4 9 Z" }) }),
951
+ tetrahedron: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", ...stroke, children: /* @__PURE__ */ jsx("path", { d: "M12 3 L20 20 L4 20 Z M12 3 L12 20" }) }),
952
+ parallelepiped: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", ...stroke, children: /* @__PURE__ */ jsx("path", { d: "M4 7 L14 4 L20 7 L14 10 Z M4 7 L4 17 L14 20 L14 10 M14 20 L20 17 L20 7" }) }),
953
+ prism: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", ...stroke, children: /* @__PURE__ */ jsx("path", { d: "M12 4 L18 8 L18 20 L12 16 Z M12 4 L6 8 L6 20 L12 16 M6 8 L12 12 L18 8 M6 20 L18 20" }) }),
954
+ pyramid: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", ...stroke, children: /* @__PURE__ */ jsx("path", { d: "M12 3 L4 20 L20 20 Z M12 3 L12 20" }) }),
955
+ sphere: /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", ...stroke, children: [
956
+ /* @__PURE__ */ jsx("circle", { cx: "12", cy: "12", r: "8" }),
957
+ /* @__PURE__ */ jsx("ellipse", { cx: "12", cy: "12", rx: "8", ry: "3" })
958
+ ] }),
959
+ cone: /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", ...stroke, children: [
960
+ /* @__PURE__ */ jsx("path", { d: "M12 3 L4 20 L20 20 Z" }),
961
+ /* @__PURE__ */ jsx("ellipse", { cx: "12", cy: "20", rx: "8", ry: "2" })
962
+ ] }),
963
+ cylinder: /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", ...stroke, children: [
964
+ /* @__PURE__ */ jsx("ellipse", { cx: "12", cy: "5", rx: "6", ry: "2" }),
965
+ /* @__PURE__ */ jsx("ellipse", { cx: "12", cy: "19", rx: "6", ry: "2" }),
966
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "5", x2: "6", y2: "19" }),
967
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "5", x2: "18", y2: "19" })
968
+ ] }),
969
+ label: /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", ...stroke, children: /* @__PURE__ */ jsx("path", { d: "M4 4 H 16 L 20 8 L 16 12 H 4 Z" }) })
970
+ };
971
+ var TOOLTIP_DELAY_MS = 400;
972
+ function Shell({ title, icon, onClose, children, isDark }) {
973
+ return /* @__PURE__ */ jsxs(
974
+ "aside",
975
+ {
976
+ role: "complementary",
977
+ "aria-label": title,
978
+ "data-testid": "geom3d-left-panel",
979
+ "data-stamp-area": "true",
980
+ className: [
981
+ isDark ? "theme--dark " : "",
982
+ "absolute left-0 top-0 z-30 flex h-full w-60 flex-col border-r border-slate-200 bg-white shadow-md animate-in slide-in-from-left duration-200"
983
+ ].join(""),
984
+ children: [
985
+ /* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between border-b border-slate-200 bg-gradient-to-r from-slate-50 to-white px-3 py-2", children: [
986
+ /* @__PURE__ */ jsxs("h3", { className: "flex items-center gap-2 text-sm font-semibold text-slate-800", children: [
987
+ /* @__PURE__ */ jsx("span", { className: "text-base leading-none", children: icon }),
988
+ title
989
+ ] }),
990
+ /* @__PURE__ */ jsx(
991
+ "button",
992
+ {
993
+ onClick: onClose,
994
+ "aria-label": "\u0110\xF3ng",
995
+ className: "rounded p-1 text-slate-500 transition hover:bg-slate-100 hover:text-slate-800",
996
+ children: /* @__PURE__ */ jsx(CloseIcon, {})
997
+ }
998
+ )
999
+ ] }),
1000
+ /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 overflow-y-auto p-3 space-y-4", children })
1001
+ ]
1002
+ }
1003
+ );
1004
+ }
1005
+ function Section({ label, children }) {
1006
+ return /* @__PURE__ */ jsxs("section", { children: [
1007
+ /* @__PURE__ */ jsx("h4", { className: "mb-1.5 text-[10px] font-semibold uppercase tracking-wider text-slate-500", children: label }),
1008
+ children
1009
+ ] });
1010
+ }
1011
+ var Geom3DIconHeader = /* @__PURE__ */ jsx("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: /* @__PURE__ */ jsx("path", { d: "M4 7 L14 4 L20 7 L14 10 Z M4 7 L4 17 L14 20 L14 10 M14 20 L20 17 L20 7" }) });
1012
+ function CloseIcon() {
1013
+ return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1014
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
1015
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" })
1016
+ ] });
1017
+ }
1018
+ function UndoIcon() {
1019
+ return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1020
+ /* @__PURE__ */ jsx("polyline", { points: "3 7 3 13 9 13" }),
1021
+ /* @__PURE__ */ jsx("path", { d: "M3.51 13a9 9 0 1 0 2.13-9.36L3 7" })
1022
+ ] });
1023
+ }
1024
+ function ResetViewIcon() {
1025
+ return /* @__PURE__ */ jsxs("svg", { width: "16", height: "16", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
1026
+ /* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }),
1027
+ /* @__PURE__ */ jsx("path", { d: "M3 3v5h5" })
1028
+ ] });
1029
+ }
1030
+ function AxisIcon3D() {
1031
+ return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", children: [
1032
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "20", x2: "12", y2: "4" }),
1033
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "12", x2: "22", y2: "6" }),
1034
+ /* @__PURE__ */ jsx("line", { x1: "12", y1: "12", x2: "2", y2: "18" })
1035
+ ] });
1036
+ }
1037
+ function MeshIcon() {
1038
+ return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", children: [
1039
+ /* @__PURE__ */ jsx("path", { d: "M4 8 L12 4 L20 8 L12 12 Z" }),
1040
+ /* @__PURE__ */ jsx("path", { d: "M4 8 L4 16 L12 20 L12 12" }),
1041
+ /* @__PURE__ */ jsx("path", { d: "M12 20 L20 16 L20 8" })
1042
+ ] });
1043
+ }
1044
+ function useToolHoverTooltip() {
1045
+ const [hover, setHover] = useState(null);
1046
+ const [portalReady, setPortalReady] = useState(false);
1047
+ const hoverTimerRef = useRef(null);
1048
+ useEffect(() => {
1049
+ setPortalReady(true);
1050
+ return () => {
1051
+ if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current);
1052
+ };
1053
+ }, []);
1054
+ const showHover = useCallback((el, t) => {
1055
+ if (hoverTimerRef.current) clearTimeout(hoverTimerRef.current);
1056
+ hoverTimerRef.current = setTimeout(() => {
1057
+ const r = el.getBoundingClientRect();
1058
+ setHover({ label: t.label, hint: t.hint, x: r.right, y: r.top + r.height / 2 });
1059
+ }, TOOLTIP_DELAY_MS);
1060
+ }, []);
1061
+ const hideHover = useCallback(() => {
1062
+ if (hoverTimerRef.current) {
1063
+ clearTimeout(hoverTimerRef.current);
1064
+ hoverTimerRef.current = null;
1065
+ }
1066
+ setHover(null);
1067
+ }, []);
1068
+ return { hover, portalReady, showHover, hideHover };
1069
+ }
1070
+ function useHandleState(handle) {
1071
+ const [tool, setTool] = useState("move");
1072
+ const [showAxes, setShowAxes] = useState(true);
1073
+ const [showMesh, setShowMesh] = useState(false);
1074
+ const [canUndo, setCanUndo] = useState(false);
1075
+ useEffect(() => {
1076
+ if (!handle) return;
1077
+ const sync = () => {
1078
+ setTool(handle.getTool());
1079
+ setShowAxes(handle.getShowAxes());
1080
+ setShowMesh(handle.getShowMesh());
1081
+ setCanUndo(handle.canUndo());
1082
+ };
1083
+ sync();
1084
+ return handle.subscribe(sync);
1085
+ }, [handle]);
1086
+ return { tool, showAxes, showMesh, canUndo };
1087
+ }
1088
+ function DesktopPanel(props) {
1089
+ const { handle, onResetView, onClose, isDark, chordGroup } = props;
1090
+ const { tool, showAxes, showMesh, canUndo } = useHandleState(handle);
1091
+ const { hover, portalReady, showHover, hideHover } = useToolHoverTooltip();
1092
+ const grouped = useMemo(() => {
1093
+ return TOOLS_3D.reduce(
1094
+ (acc, t) => {
1095
+ var _a;
1096
+ (acc[_a = t.group] ?? (acc[_a] = [])).push(t);
1097
+ return acc;
1098
+ },
1099
+ {}
1100
+ );
1101
+ }, []);
1102
+ const orderedGroups = useMemo(
1103
+ () => GROUP_ORDER_3D.filter((g) => grouped[g]),
1104
+ [grouped]
1105
+ );
1106
+ const activeGroupTools = chordGroup ? grouped[chordGroup] ?? null : null;
1107
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1108
+ /* @__PURE__ */ jsxs(Shell, { title: "H\xECnh h\u1ECDc 3D", icon: Geom3DIconHeader, onClose, isDark, children: [
1109
+ /* @__PURE__ */ jsx(Section, { label: "B\u1ED1 c\u1EE5c", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 flex-wrap text-[11px] text-slate-700", children: [
1110
+ /* @__PURE__ */ jsxs("label", { className: "inline-flex select-none items-center gap-1.5", children: [
1111
+ /* @__PURE__ */ jsx(
1112
+ "input",
1113
+ {
1114
+ type: "checkbox",
1115
+ checked: showAxes,
1116
+ onChange: (e) => handle?.setShowAxes(e.target.checked),
1117
+ "data-testid": "toggle-axes"
1118
+ }
1119
+ ),
1120
+ "Tr\u1EE5c"
1121
+ ] }),
1122
+ /* @__PURE__ */ jsxs("label", { className: "inline-flex select-none items-center gap-1.5", children: [
1123
+ /* @__PURE__ */ jsx(
1124
+ "input",
1125
+ {
1126
+ type: "checkbox",
1127
+ checked: showMesh,
1128
+ onChange: (e) => handle?.setShowMesh(e.target.checked),
1129
+ "data-testid": "toggle-mesh"
1130
+ }
1131
+ ),
1132
+ "L\u01B0\u1EDBi"
1133
+ ] }),
1134
+ /* @__PURE__ */ jsx(
1135
+ "button",
1136
+ {
1137
+ type: "button",
1138
+ onClick: onResetView,
1139
+ title: "Reset g\xF3c nh\xECn",
1140
+ "aria-label": "Reset view",
1141
+ className: "ml-auto inline-flex items-center justify-center rounded p-1 text-slate-600 transition hover:bg-slate-100 hover:text-slate-900",
1142
+ children: /* @__PURE__ */ jsx(ResetViewIcon, {})
1143
+ }
1144
+ ),
1145
+ /* @__PURE__ */ jsx(
1146
+ "button",
1147
+ {
1148
+ type: "button",
1149
+ onClick: () => handle?.undo(),
1150
+ disabled: !canUndo,
1151
+ title: "Ho\xE0n t\xE1c (Ctrl/Cmd+Z)",
1152
+ "aria-label": "Ho\xE0n t\xE1c",
1153
+ className: "inline-flex items-center justify-center rounded p-1 text-slate-600 transition hover:bg-slate-100 hover:text-slate-900 disabled:cursor-not-allowed disabled:text-slate-300 disabled:hover:bg-transparent",
1154
+ children: /* @__PURE__ */ jsx(UndoIcon, {})
1155
+ }
1156
+ )
1157
+ ] }) }),
1158
+ orderedGroups.map((group) => {
1159
+ const tools = grouped[group];
1160
+ const isChordActive = chordGroup === group;
1161
+ const dimmed = chordGroup !== null && !isChordActive;
1162
+ return /* @__PURE__ */ jsxs(
1163
+ "section",
1164
+ {
1165
+ "data-chord-group": group,
1166
+ "data-chord-active": isChordActive ? "true" : "false",
1167
+ className: [
1168
+ "rounded-md transition",
1169
+ isChordActive ? "bg-blue-50 ring-1 ring-blue-400 p-1" : "p-0",
1170
+ dimmed ? "opacity-55" : "opacity-100"
1171
+ ].join(" "),
1172
+ children: [
1173
+ /* @__PURE__ */ jsxs("h4", { className: "mb-1.5 flex items-center justify-between text-[10px] font-semibold uppercase tracking-wider text-slate-500", children: [
1174
+ /* @__PURE__ */ jsx("span", { children: GROUP_LABELS_3D[group] }),
1175
+ /* @__PURE__ */ jsx(
1176
+ "span",
1177
+ {
1178
+ "data-testid": `chord-letter-${group}`,
1179
+ className: [
1180
+ "font-mono text-[10px] leading-none transition",
1181
+ isChordActive ? "text-blue-700 font-bold" : "text-slate-400"
1182
+ ].join(" "),
1183
+ children: letterForGroup3D(group)
1184
+ }
1185
+ )
1186
+ ] }),
1187
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-4 gap-1", children: tools.map((t, i) => {
1188
+ const isActive = tool === t.key;
1189
+ return /* @__PURE__ */ jsx(
1190
+ ToolButton,
1191
+ {
1192
+ toolKey: t.key,
1193
+ label: t.label,
1194
+ hint: t.hint,
1195
+ active: isActive,
1196
+ onClick: () => handle?.setTool(t.key),
1197
+ icon: /* @__PURE__ */ jsx(
1198
+ "span",
1199
+ {
1200
+ onMouseEnter: (e) => showHover(e.currentTarget.closest("button"), t),
1201
+ onMouseLeave: hideHover,
1202
+ onFocus: (e) => showHover(e.currentTarget.closest("button"), t),
1203
+ onBlur: hideHover,
1204
+ children: ICONS_3D[t.key]
1205
+ }
1206
+ ),
1207
+ badge: /* @__PURE__ */ jsx(
1208
+ "span",
1209
+ {
1210
+ "data-testid": `chord-num-${t.key}`,
1211
+ className: [
1212
+ "pointer-events-none absolute bottom-0 right-0.5 font-mono text-[9px] leading-none transition",
1213
+ isActive ? "text-white/70" : isChordActive ? "text-blue-700 font-bold" : "text-slate-400"
1214
+ ].join(" "),
1215
+ children: i + 1
1216
+ }
1217
+ )
1218
+ },
1219
+ t.key
1220
+ );
1221
+ }) })
1222
+ ]
1223
+ },
1224
+ group
1225
+ );
1226
+ }),
1227
+ chordGroup && activeGroupTools && /* @__PURE__ */ jsxs(
1228
+ "div",
1229
+ {
1230
+ "data-testid": "chord-hint",
1231
+ className: "mt-1 rounded border border-blue-200 bg-blue-50/60 px-2 py-1 text-[11px] leading-snug text-slate-600",
1232
+ children: [
1233
+ /* @__PURE__ */ jsx("span", { className: "font-mono font-semibold text-blue-700", children: letterForGroup3D(chordGroup) }),
1234
+ /* @__PURE__ */ jsx("span", { className: "mx-1 text-slate-400", children: "\u2192" }),
1235
+ activeGroupTools.map((t, i) => /* @__PURE__ */ jsxs("span", { className: "mr-2 inline-block", children: [
1236
+ /* @__PURE__ */ jsx("span", { className: "font-mono font-semibold text-blue-700", children: i + 1 }),
1237
+ /* @__PURE__ */ jsx("span", { className: "ml-1", children: t.label })
1238
+ ] }, t.key)),
1239
+ /* @__PURE__ */ jsx("span", { className: "text-slate-400", children: "Esc hu\u1EF7" })
1240
+ ]
1241
+ }
1242
+ )
1243
+ ] }),
1244
+ portalReady && hover && typeof document !== "undefined" ? createPortal(
1245
+ /* @__PURE__ */ jsxs(
1246
+ "div",
1247
+ {
1248
+ role: "tooltip",
1249
+ className: "pointer-events-none fixed w-max max-w-[220px] rounded-md bg-slate-900 px-2 py-1 text-left text-[11px] leading-tight text-white shadow-lg",
1250
+ style: {
1251
+ left: hover.x + 8,
1252
+ top: hover.y,
1253
+ transform: "translate(0, -50%)",
1254
+ zIndex: 2147483600
1255
+ },
1256
+ children: [
1257
+ /* @__PURE__ */ jsx("span", { className: "block font-medium", children: hover.label }),
1258
+ hover.hint && /* @__PURE__ */ jsx("span", { className: "mt-0.5 block text-slate-300", children: hover.hint })
1259
+ ]
1260
+ }
1261
+ ),
1262
+ document.body
1263
+ ) : null
1264
+ ] });
1265
+ }
1266
+ function MobilePanel(props) {
1267
+ const { handle, onResetView, isDark, drawerOpen, onDrawerClose } = props;
1268
+ const { tool, showAxes, showMesh, canUndo } = useHandleState(handle);
1269
+ const groups = useMemo(() => {
1270
+ const acc = /* @__PURE__ */ new Map();
1271
+ for (const t of TOOLS_3D) {
1272
+ if (!acc.has(t.group)) acc.set(t.group, []);
1273
+ acc.get(t.group).push(t);
1274
+ }
1275
+ return Array.from(acc.entries()).map(([group, tools]) => ({
1276
+ group,
1277
+ groupLabel: GROUP_LABELS_3D[group],
1278
+ tools: tools.map((t) => ({ key: t.key, label: t.label, icon: ICONS_3D[t.key] }))
1279
+ }));
1280
+ }, []);
1281
+ return /* @__PURE__ */ jsx(
1282
+ MobileToolDrawer,
1283
+ {
1284
+ title: "H\xECnh h\u1ECDc 3D",
1285
+ headerIcon: Geom3DIconHeader,
1286
+ testId: "geom3d-left-panel",
1287
+ isDark,
1288
+ drawerOpen: !!drawerOpen,
1289
+ onDrawerClose: () => onDrawerClose?.(),
1290
+ chips: [
1291
+ {
1292
+ label: "Tr\u1EE5c",
1293
+ icon: /* @__PURE__ */ jsx(AxisIcon3D, {}),
1294
+ pressed: showAxes,
1295
+ onToggle: (b) => handle?.setShowAxes(b),
1296
+ testId: "toggle-axes"
1297
+ },
1298
+ {
1299
+ label: "L\u01B0\u1EDBi",
1300
+ icon: /* @__PURE__ */ jsx(MeshIcon, {}),
1301
+ pressed: showMesh,
1302
+ onToggle: (b) => handle?.setShowMesh(b),
1303
+ testId: "toggle-mesh"
1304
+ }
1305
+ ],
1306
+ actions: [
1307
+ {
1308
+ label: "Reset view",
1309
+ title: "Reset g\xF3c nh\xECn",
1310
+ icon: /* @__PURE__ */ jsx(ResetViewIcon, {}),
1311
+ onClick: onResetView
1312
+ },
1313
+ {
1314
+ label: "Ho\xE0n t\xE1c",
1315
+ title: "Ho\xE0n t\xE1c (Ctrl/Cmd+Z)",
1316
+ icon: /* @__PURE__ */ jsx(UndoIcon, {}),
1317
+ onClick: () => handle?.undo(),
1318
+ disabled: !canUndo
1319
+ }
1320
+ ],
1321
+ groups,
1322
+ activeTool: tool,
1323
+ onToolSelect: (k) => handle?.setTool(k)
1324
+ }
1325
+ );
1326
+ }
1327
+ function LeftPanel(props) {
1328
+ if (props.isMobile) return /* @__PURE__ */ jsx(MobilePanel, { ...props });
1329
+ return /* @__PURE__ */ jsx(DesktopPanel, { ...props });
1330
+ }
1331
+ function parseInitial(editingElement) {
1332
+ if (!editingElement) return null;
1333
+ if (!isGeometry3DCustomData(editingElement.customData)) return null;
1334
+ try {
1335
+ return parseSerializedBoard3D(editingElement.customData.jsonState);
1336
+ } catch {
1337
+ return null;
1338
+ }
1339
+ }
1340
+ var Geometry3DStampHost = forwardRef(
1341
+ function Geometry3DStampHost2({ api, editingElement, onClose, isDark }, ref) {
1342
+ const editorRef = useRef(null);
1343
+ const { isMobile } = useIsMobile();
1344
+ const [drawerOpen, setDrawerOpen] = useState(false);
1345
+ const [boardHandle, setBoardHandle] = useState(null);
1346
+ const initial = useMemo(
1347
+ () => parseInitial(editingElement),
1348
+ [editingElement]
1349
+ );
1350
+ const handleBoardReady = useCallback((h) => {
1351
+ setBoardHandle((prev) => prev === h ? prev : h);
1352
+ }, []);
1353
+ const { chordGroup } = useChordShortcut({
1354
+ groupOrder: GROUP_ORDER_3D,
1355
+ tools: TOOLS_3D,
1356
+ onSelect: (key) => boardHandle?.setTool(key),
1357
+ enabled: !isMobile
1358
+ });
1359
+ const handleResetView = useCallback(() => {
1360
+ boardHandle?.resetView();
1361
+ }, [boardHandle]);
1362
+ const handleInsert = useCallback(
1363
+ async (jsonState, svgString, width, height) => {
1364
+ if (!api) return;
1365
+ await insertStampImage(api, {
1366
+ svgString,
1367
+ makeCustomData: () => ({
1368
+ kind: "geometry3d",
1369
+ version: 1,
1370
+ jsonState,
1371
+ svgWidth: width,
1372
+ svgHeight: height
1373
+ }),
1374
+ editingElementId: editingElement?.id ?? null
1375
+ });
1376
+ onClose();
1377
+ },
1378
+ [api, editingElement, onClose]
1379
+ );
1380
+ useImperativeHandle(
1381
+ ref,
1382
+ () => ({
1383
+ tryInsert: () => editorRef.current?.tryInsert() ?? false,
1384
+ hasContent: () => editorRef.current?.hasContent() ?? false
1385
+ }),
1386
+ []
1387
+ );
1388
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1389
+ /* @__PURE__ */ jsx(
1390
+ LeftPanel,
1391
+ {
1392
+ handle: boardHandle,
1393
+ onResetView: handleResetView,
1394
+ onClose,
1395
+ isDark,
1396
+ isMobile,
1397
+ drawerOpen,
1398
+ onDrawerClose: () => setDrawerOpen(false),
1399
+ chordGroup
1400
+ }
1401
+ ),
1402
+ /* @__PURE__ */ jsx(
1403
+ EditorPanel,
1404
+ {
1405
+ ref: editorRef,
1406
+ isDark,
1407
+ initial,
1408
+ onInsert: handleInsert,
1409
+ onClose,
1410
+ isMobile,
1411
+ withLeftPanel: !isMobile,
1412
+ onBoardReady: handleBoardReady,
1413
+ onOpenDrawer: () => setDrawerOpen(true)
1414
+ }
1415
+ )
1416
+ ] });
1417
+ }
1418
+ );
1419
+
1420
+ export { Geometry3DStampHost };
1421
+ //# sourceMappingURL=host-XUFON6CQ.mjs.map
1422
+ //# sourceMappingURL=host-XUFON6CQ.mjs.map