@xom11/whiteboard 0.29.0 → 0.31.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 (93) hide show
  1. package/dist/ai.d.mts +259 -33
  2. package/dist/ai.d.ts +259 -33
  3. package/dist/ai.js +5424 -470
  4. package/dist/ai.js.map +1 -1
  5. package/dist/ai.mjs +4971 -351
  6. package/dist/ai.mjs.map +1 -1
  7. package/dist/catalog.json +5 -5
  8. package/dist/{chunk-V3YJ6JFL.mjs → chunk-44JY2AKC.mjs} +3 -3
  9. package/dist/{chunk-V3YJ6JFL.mjs.map → chunk-44JY2AKC.mjs.map} +1 -1
  10. package/dist/{chunk-GEC2D2EQ.mjs → chunk-BMYC2ILT.mjs} +4 -4
  11. package/dist/{chunk-GEC2D2EQ.mjs.map → chunk-BMYC2ILT.mjs.map} +1 -1
  12. package/dist/{chunk-PPKHCRRE.mjs → chunk-C76SOFXF.mjs} +3 -3
  13. package/dist/{chunk-PPKHCRRE.mjs.map → chunk-C76SOFXF.mjs.map} +1 -1
  14. package/dist/{chunk-IHUFOV7L.mjs → chunk-CH6SFONH.mjs} +15 -3
  15. package/dist/chunk-CH6SFONH.mjs.map +1 -0
  16. package/dist/{chunk-E6EDOPGT.mjs → chunk-DWIEVCGK.mjs} +254 -16
  17. package/dist/chunk-DWIEVCGK.mjs.map +1 -0
  18. package/dist/{chunk-SZDAS7LK.mjs → chunk-IE2GGHNF.mjs} +131 -81
  19. package/dist/chunk-IE2GGHNF.mjs.map +1 -0
  20. package/dist/{chunk-ZTQBUKLJ.mjs → chunk-JJ4FPCBE.mjs} +142 -22
  21. package/dist/chunk-JJ4FPCBE.mjs.map +1 -0
  22. package/dist/{chunk-QRUAEXLR.mjs → chunk-K5BS2H56.mjs} +5 -5
  23. package/dist/{chunk-QRUAEXLR.mjs.map → chunk-K5BS2H56.mjs.map} +1 -1
  24. package/dist/{chunk-BNBOIDO5.mjs → chunk-K7VJU7LQ.mjs} +3 -3
  25. package/dist/{chunk-BNBOIDO5.mjs.map → chunk-K7VJU7LQ.mjs.map} +1 -1
  26. package/dist/{chunk-H22OZYTW.mjs → chunk-KOXOC2FI.mjs} +48 -39
  27. package/dist/chunk-KOXOC2FI.mjs.map +1 -0
  28. package/dist/{chunk-CXHNVYMD.mjs → chunk-KWDBVLST.mjs} +5 -5
  29. package/dist/{chunk-CXHNVYMD.mjs.map → chunk-KWDBVLST.mjs.map} +1 -1
  30. package/dist/{chunk-OQIQNKPQ.mjs → chunk-LTLLQUMN.mjs} +4 -4
  31. package/dist/{chunk-OQIQNKPQ.mjs.map → chunk-LTLLQUMN.mjs.map} +1 -1
  32. package/dist/chunk-QK6OVDLC.mjs +103 -0
  33. package/dist/chunk-QK6OVDLC.mjs.map +1 -0
  34. package/dist/{chunk-QGNU34T7.mjs → chunk-QLQ4MJNO.mjs} +10 -4
  35. package/dist/chunk-QLQ4MJNO.mjs.map +1 -0
  36. package/dist/{chunk-BU5KLO3P.mjs → chunk-T3N4BSJV.mjs} +4 -4
  37. package/dist/{chunk-BU5KLO3P.mjs.map → chunk-T3N4BSJV.mjs.map} +1 -1
  38. package/dist/{chunk-5JM35CXV.mjs → chunk-TMRFSOM7.mjs} +4 -4
  39. package/dist/{chunk-5JM35CXV.mjs.map → chunk-TMRFSOM7.mjs.map} +1 -1
  40. package/dist/geometry-2d.d.mts +1 -1
  41. package/dist/geometry-2d.d.ts +1 -1
  42. package/dist/geometry-2d.js +841 -204
  43. package/dist/geometry-2d.js.map +1 -1
  44. package/dist/geometry-2d.mjs +5 -5
  45. package/dist/geometry-3d.d.mts +1 -1
  46. package/dist/geometry-3d.d.ts +1 -1
  47. package/dist/geometry-3d.js +172 -22
  48. package/dist/geometry-3d.js.map +1 -1
  49. package/dist/geometry-3d.mjs +4 -4
  50. package/dist/graph-2d.d.mts +1 -1
  51. package/dist/graph-2d.d.ts +1 -1
  52. package/dist/graph-2d.js +307 -100
  53. package/dist/graph-2d.js.map +1 -1
  54. package/dist/graph-2d.mjs +7 -7
  55. package/dist/handleExtractProblem-BrDY9ifM.d.mts +58 -0
  56. package/dist/handleExtractProblem-BrDY9ifM.d.ts +58 -0
  57. package/dist/{host-HOSJHQ5H.mjs → host-4FIUNIDQ.mjs} +13 -12
  58. package/dist/host-4FIUNIDQ.mjs.map +1 -0
  59. package/dist/{host-2ISGVO7O.mjs → host-4ZB4XD4S.mjs} +9 -8
  60. package/dist/host-4ZB4XD4S.mjs.map +1 -0
  61. package/dist/{host-ZQCDAT6O.mjs → host-H2IGOKJU.mjs} +3 -3
  62. package/dist/{host-ZQCDAT6O.mjs.map → host-H2IGOKJU.mjs.map} +1 -1
  63. package/dist/{host-HKMZSCIT.mjs → host-KMWP7KBT.mjs} +286 -74
  64. package/dist/host-KMWP7KBT.mjs.map +1 -0
  65. package/dist/index.d.mts +3 -2
  66. package/dist/index.d.ts +3 -2
  67. package/dist/index.js +849 -206
  68. package/dist/index.js.map +1 -1
  69. package/dist/index.mjs +22 -21
  70. package/dist/index.mjs.map +1 -1
  71. package/dist/latex.d.mts +1 -1
  72. package/dist/latex.d.ts +1 -1
  73. package/dist/latex.js +8 -2
  74. package/dist/latex.js.map +1 -1
  75. package/dist/latex.mjs +1 -1
  76. package/dist/render-NMS7OAV6.mjs +10 -0
  77. package/dist/{render-ZX2O2IK7.mjs.map → render-NMS7OAV6.mjs.map} +1 -1
  78. package/dist/serialize-PGHQZEPV.mjs +9 -0
  79. package/dist/{serialize-N4G6RFBB.mjs.map → serialize-PGHQZEPV.mjs.map} +1 -1
  80. package/dist/{types-C3FjpoUi.d.ts → types-tePd94vW.d.mts} +8 -0
  81. package/dist/{types-C3FjpoUi.d.mts → types-tePd94vW.d.ts} +8 -0
  82. package/package.json +2 -1
  83. package/dist/chunk-E6EDOPGT.mjs.map +0 -1
  84. package/dist/chunk-H22OZYTW.mjs.map +0 -1
  85. package/dist/chunk-IHUFOV7L.mjs.map +0 -1
  86. package/dist/chunk-QGNU34T7.mjs.map +0 -1
  87. package/dist/chunk-SZDAS7LK.mjs.map +0 -1
  88. package/dist/chunk-ZTQBUKLJ.mjs.map +0 -1
  89. package/dist/host-2ISGVO7O.mjs.map +0 -1
  90. package/dist/host-HKMZSCIT.mjs.map +0 -1
  91. package/dist/host-HOSJHQ5H.mjs.map +0 -1
  92. package/dist/render-ZX2O2IK7.mjs +0 -10
  93. package/dist/serialize-N4G6RFBB.mjs +0 -9
package/dist/catalog.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "generatedAt": "2026-06-09T11:30:04.795Z",
2
+ "generatedAt": "2026-06-17T03:53:55.915Z",
3
3
  "entries": [
4
4
  {
5
5
  "id": "geometry",
@@ -10,7 +10,7 @@
10
10
  "jsxgraph"
11
11
  ],
12
12
  "bundleSize": {
13
- "js": 81.86,
13
+ "js": 89.68,
14
14
  "css": 0
15
15
  }
16
16
  },
@@ -23,7 +23,7 @@
23
23
  "katex"
24
24
  ],
25
25
  "bundleSize": {
26
- "js": 9.15,
26
+ "js": 9.27,
27
27
  "css": 0
28
28
  }
29
29
  },
@@ -36,7 +36,7 @@
36
36
  "jsxgraph"
37
37
  ],
38
38
  "bundleSize": {
39
- "js": 57.59,
39
+ "js": 59.47,
40
40
  "css": 0
41
41
  }
42
42
  },
@@ -49,7 +49,7 @@
49
49
  "jsxgraph"
50
50
  ],
51
51
  "bundleSize": {
52
- "js": 50.33,
52
+ "js": 52.92,
53
53
  "css": 0
54
54
  }
55
55
  }
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- import { serializeScene } from './chunk-ZTQBUKLJ.mjs';
2
+ import { serializeScene } from './chunk-JJ4FPCBE.mjs';
3
3
 
4
4
  // src/stamps/graph-2d/serialize.ts
5
5
  function stringifySceneState(state) {
@@ -24,5 +24,5 @@ function parseSceneState(json) {
24
24
  }
25
25
 
26
26
  export { parseSceneState, stringifySceneState };
27
- //# sourceMappingURL=chunk-V3YJ6JFL.mjs.map
28
- //# sourceMappingURL=chunk-V3YJ6JFL.mjs.map
27
+ //# sourceMappingURL=chunk-44JY2AKC.mjs.map
28
+ //# sourceMappingURL=chunk-44JY2AKC.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/stamps/graph-2d/serialize.ts"],"names":[],"mappings":";;;AASO,SAAS,oBAAoB,KAAA,EAAsB;AACxD,EAAA,OAAO,eAAe,KAAK,CAAA;AAC7B;AAEO,SAAS,gBAAgB,IAAA,EAA4B;AAC1D,EAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EACvB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,IAAI,CAAC,GAAA,IAAO,OAAO,GAAA,KAAQ,UAAU,OAAO,IAAA;AAC5C,EAAA,MAAM,CAAA,GAAI,GAAA;AACV,EAAA,IAAI,CAAA,CAAE,IAAA,EAAM,MAAA,KAAW,SAAA,EAAW,OAAO,IAAA;AACzC,EAAA,IAAI,CAAC,EAAE,IAAA,EAAM,IAAA,IAAQ,OAAO,CAAA,CAAE,IAAA,CAAK,IAAA,KAAS,QAAA,EAAU,OAAO,IAAA;AAC7D,EAAA,IAAI,OAAO,CAAA,CAAE,OAAA,KAAY,QAAA,EAAU,OAAO,IAAA;AAC1C,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,KAAK,GAAG,OAAO,IAAA;AACpC,EAAA,IAAI,CAAC,CAAA,CAAE,OAAA,IAAW,OAAO,CAAA,CAAE,OAAA,KAAY,UAAU,OAAO,IAAA;AACxD,EAAA,OAAO,GAAA;AACT","file":"chunk-V3YJ6JFL.mjs","sourcesContent":["// src/stamps/graph-2d/serialize.ts\n//\n// graph-2d đã dùng plain State (không envelope) ngay từ đầu. Sau Tier D PR 3,\n// thin wrapper qua shared helper cho serialize. parseSceneState giữ behavior\n// null-on-invalid để host/index.tsx có thể discriminate \"customData hỏng\".\n\nimport { serializeScene } from '../shared/serializeScene';\nimport type { State } from '../../core/scene/types';\n\nexport function stringifySceneState(state: State): string {\n return serializeScene(state);\n}\n\nexport function parseSceneState(json: string): State | null {\n if (!json) return null;\n let raw: unknown;\n try {\n raw = JSON.parse(json);\n } catch {\n return null;\n }\n if (!raw || typeof raw !== 'object') return null;\n const v = raw as Partial<State>;\n if (v.meta?.domain !== 'graph2d') return null;\n if (!v.meta?.view || typeof v.meta.view !== 'object') return null;\n if (typeof v.counter !== 'number') return null;\n if (!Array.isArray(v.order)) return null;\n if (!v.objects || typeof v.objects !== 'object') return null;\n return raw as State;\n}\n"]}
1
+ {"version":3,"sources":["../src/stamps/graph-2d/serialize.ts"],"names":[],"mappings":";;;AASO,SAAS,oBAAoB,KAAA,EAAsB;AACxD,EAAA,OAAO,eAAe,KAAK,CAAA;AAC7B;AAEO,SAAS,gBAAgB,IAAA,EAA4B;AAC1D,EAAA,IAAI,CAAC,MAAM,OAAO,IAAA;AAClB,EAAA,IAAI,GAAA;AACJ,EAAA,IAAI;AACF,IAAA,GAAA,GAAM,IAAA,CAAK,MAAM,IAAI,CAAA;AAAA,EACvB,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,IAAA;AAAA,EACT;AACA,EAAA,IAAI,CAAC,GAAA,IAAO,OAAO,GAAA,KAAQ,UAAU,OAAO,IAAA;AAC5C,EAAA,MAAM,CAAA,GAAI,GAAA;AACV,EAAA,IAAI,CAAA,CAAE,IAAA,EAAM,MAAA,KAAW,SAAA,EAAW,OAAO,IAAA;AACzC,EAAA,IAAI,CAAC,EAAE,IAAA,EAAM,IAAA,IAAQ,OAAO,CAAA,CAAE,IAAA,CAAK,IAAA,KAAS,QAAA,EAAU,OAAO,IAAA;AAC7D,EAAA,IAAI,OAAO,CAAA,CAAE,OAAA,KAAY,QAAA,EAAU,OAAO,IAAA;AAC1C,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,KAAK,GAAG,OAAO,IAAA;AACpC,EAAA,IAAI,CAAC,CAAA,CAAE,OAAA,IAAW,OAAO,CAAA,CAAE,OAAA,KAAY,UAAU,OAAO,IAAA;AACxD,EAAA,OAAO,GAAA;AACT","file":"chunk-44JY2AKC.mjs","sourcesContent":["// src/stamps/graph-2d/serialize.ts\n//\n// graph-2d đã dùng plain State (không envelope) ngay từ đầu. Sau Tier D PR 3,\n// thin wrapper qua shared helper cho serialize. parseSceneState giữ behavior\n// null-on-invalid để host/index.tsx có thể discriminate \"customData hỏng\".\n\nimport { serializeScene } from '../shared/serializeScene';\nimport type { State } from '../../core/scene/types';\n\nexport function stringifySceneState(state: State): string {\n return serializeScene(state);\n}\n\nexport function parseSceneState(json: string): State | null {\n if (!json) return null;\n let raw: unknown;\n try {\n raw = JSON.parse(json);\n } catch {\n return null;\n }\n if (!raw || typeof raw !== 'object') return null;\n const v = raw as Partial<State>;\n if (v.meta?.domain !== 'graph2d') return null;\n if (!v.meta?.view || typeof v.meta.view !== 'object') return null;\n if (typeof v.counter !== 'number') return null;\n if (!Array.isArray(v.order)) return null;\n if (!v.objects || typeof v.objects !== 'object') return null;\n return raw as State;\n}\n"]}
@@ -1,11 +1,11 @@
1
1
  "use client";
2
- import { isGeometryCustomData, renderGeometrySvgFromState } from './chunk-H22OZYTW.mjs';
2
+ import { isGeometryCustomData, renderGeometrySvgFromState } from './chunk-KOXOC2FI.mjs';
3
3
  import { svgToStampFile } from './chunk-5UTGXHLJ.mjs';
4
4
  import { lazy } from 'react';
5
5
  import { jsxs, jsx } from 'react/jsx-runtime';
6
6
 
7
7
  var GeometryStampHost = lazy(
8
- () => import('./host-HKMZSCIT.mjs').then((m) => ({ default: m.GeometryStampHost }))
8
+ () => import('./host-KMWP7KBT.mjs').then((m) => ({ default: m.GeometryStampHost }))
9
9
  );
10
10
  var GeometryIcon = /* @__PURE__ */ jsxs("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
11
11
  /* @__PURE__ */ jsx("polygon", { points: "4,20 20,20 12,5" }),
@@ -38,5 +38,5 @@ var geometryStamp = {
38
38
  };
39
39
 
40
40
  export { geometryStamp };
41
- //# sourceMappingURL=chunk-GEC2D2EQ.mjs.map
42
- //# sourceMappingURL=chunk-GEC2D2EQ.mjs.map
41
+ //# sourceMappingURL=chunk-BMYC2ILT.mjs.map
42
+ //# sourceMappingURL=chunk-BMYC2ILT.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/stamps/geometry-2d/index.tsx"],"names":[],"mappings":";;;;;AAgBA,IAAM,iBAAA,GAAoB,IAAA;AAAA,EAAK,MAC7B,OAAO,qBAAQ,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,MAAO,EAAE,OAAA,EAAS,CAAA,CAAE,iBAAA,EAAkB,CAAE;AACjE,CAAA;AAEA,IAAM,YAAA,wBACH,KAAA,EAAA,EAAI,KAAA,EAAM,MAAK,MAAA,EAAO,IAAA,EAAK,SAAQ,WAAA,EAAY,IAAA,EAAK,QAAO,MAAA,EAAO,cAAA,EAAe,aAAY,KAAA,EAAM,aAAA,EAAc,SAAQ,cAAA,EAAe,OAAA,EAAQ,eAAY,MAAA,EAC3J,QAAA,EAAA;AAAA,kBAAA,GAAA,CAAC,SAAA,EAAA,EAAQ,QAAO,iBAAA,EAAkB,CAAA;AAAA,kBAClC,GAAA,CAAC,QAAA,EAAA,EAAO,EAAA,EAAG,GAAA,EAAI,EAAA,EAAG,IAAA,EAAK,CAAA,EAAE,KAAA,EAAM,IAAA,EAAK,cAAA,EAAe,MAAA,EAAO,MAAA,EAAO,CAAA;AAAA,kBACjE,GAAA,CAAC,QAAA,EAAA,EAAO,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,IAAA,EAAK,CAAA,EAAE,KAAA,EAAM,IAAA,EAAK,cAAA,EAAe,MAAA,EAAO,MAAA,EAAO,CAAA;AAAA,kBAClE,GAAA,CAAC,QAAA,EAAA,EAAO,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,GAAA,EAAI,CAAA,EAAE,KAAA,EAAM,IAAA,EAAK,cAAA,EAAe,MAAA,EAAO,MAAA,EAAO;AAAA,CAAA,EACnE,CAAA;AAGK,IAAM,aAAA,GAA+C;AAAA,EAC1D,IAAA,EAAM,UAAA;AAAA,EACN,WAAA,EAAa,GAAA;AAAA,EACb,YAAA,EAAc,GAAA;AAAA,EACd,YAAA,EAAc,8BAAA;AAAA,EACd,WAAA,EAAa,YAAA;AAAA,EACb,aAAA,EAAe,wBAAA;AAAA,EACf,iBAAA,EAAmB,oBAAA;AAAA,EACnB,MAAM,wBAAwB,IAAA,EAAM;AAClC,IAAA,IAAI,CAAC,oBAAA,CAAqB,IAAI,CAAA,EAAG;AAC/B,MAAA,MAAM,IAAI,MAAM,+EAAuE,CAAA;AAAA,IACzF;AACA,IAAA,OAAO,0BAAA,CAA2B,KAAK,SAAS,CAAA;AAAA,EAClD,CAAA;AAAA,EACA,MAAM,0BAA0B,OAAA,EAA4C;AAC1E,IAAA,MAAM,OAAO,OAAA,CAAQ,UAAA;AACrB,IAAA,MAAM,SAAU,OAAA,CAAuC,MAAA;AACvD,IAAA,IAAI,CAAC,QAAQ,CAAC,MAAA,IAAU,CAAC,oBAAA,CAAqB,IAAI,GAAG,OAAO,IAAA;AAC5D,IAAA,MAAM,SAAA,GAAY,MAAM,0BAAA,CAA2B,IAAA,CAAK,SAAS,CAAA;AACjE,IAAA,OAAO,cAAA,CAAe,WAAW,MAAM,CAAA;AAAA,EACzC,CAAA;AAAA,EACA,IAAA,EAAM;AACR","file":"chunk-GEC2D2EQ.mjs","sourcesContent":["'use client';\n\nimport { lazy, type ReactNode } from 'react';\nimport { renderGeometrySvgFromState } from './render';\nimport type {\n RestoredStampFile,\n StampType,\n} from '../shared/types';\nimport { svgToStampFile } from '../shared/svgToStampFile';\nimport {\n isGeometryCustomData,\n type GeometryCustomData,\n} from './types';\n\nexport type { GeometryCustomData };\n\nconst GeometryStampHost = lazy(() =>\n import('./host').then((m) => ({ default: m.GeometryStampHost })),\n);\n\nconst GeometryIcon: ReactNode = (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"4,20 20,20 12,5\" />\n <circle cx=\"4\" cy=\"20\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\" />\n <circle cx=\"20\" cy=\"20\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\" />\n <circle cx=\"12\" cy=\"5\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n);\n\nexport const geometryStamp: StampType<GeometryCustomData> = {\n kind: 'geometry',\n shortcutKey: 'g',\n toolbarLabel: 'G',\n toolbarTitle: 'Chèn hình học (G)',\n toolbarIcon: GeometryIcon,\n toolbarTestId: 'stamp-toolbar-geometry',\n matchesCustomData: isGeometryCustomData,\n async renderSvgFromCustomData(data) {\n if (!isGeometryCustomData(data)) {\n throw new Error('geometryStamp.renderSvgFromCustomData: customData không phải geometry');\n }\n return renderGeometrySvgFromState(data.jsonState);\n },\n async restoreFileFromCustomData(element): Promise<RestoredStampFile | null> {\n const data = element.customData as GeometryCustomData | undefined;\n const fileId = (element as { fileId?: string | null }).fileId;\n if (!data || !fileId || !isGeometryCustomData(data)) return null;\n const svgString = await renderGeometrySvgFromState(data.jsonState);\n return svgToStampFile(svgString, fileId);\n },\n Host: GeometryStampHost,\n};\n"]}
1
+ {"version":3,"sources":["../src/stamps/geometry-2d/index.tsx"],"names":[],"mappings":";;;;;AAgBA,IAAM,iBAAA,GAAoB,IAAA;AAAA,EAAK,MAC7B,OAAO,qBAAQ,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,MAAO,EAAE,OAAA,EAAS,CAAA,CAAE,iBAAA,EAAkB,CAAE;AACjE,CAAA;AAEA,IAAM,YAAA,wBACH,KAAA,EAAA,EAAI,KAAA,EAAM,MAAK,MAAA,EAAO,IAAA,EAAK,SAAQ,WAAA,EAAY,IAAA,EAAK,QAAO,MAAA,EAAO,cAAA,EAAe,aAAY,KAAA,EAAM,aAAA,EAAc,SAAQ,cAAA,EAAe,OAAA,EAAQ,eAAY,MAAA,EAC3J,QAAA,EAAA;AAAA,kBAAA,GAAA,CAAC,SAAA,EAAA,EAAQ,QAAO,iBAAA,EAAkB,CAAA;AAAA,kBAClC,GAAA,CAAC,QAAA,EAAA,EAAO,EAAA,EAAG,GAAA,EAAI,EAAA,EAAG,IAAA,EAAK,CAAA,EAAE,KAAA,EAAM,IAAA,EAAK,cAAA,EAAe,MAAA,EAAO,MAAA,EAAO,CAAA;AAAA,kBACjE,GAAA,CAAC,QAAA,EAAA,EAAO,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,IAAA,EAAK,CAAA,EAAE,KAAA,EAAM,IAAA,EAAK,cAAA,EAAe,MAAA,EAAO,MAAA,EAAO,CAAA;AAAA,kBAClE,GAAA,CAAC,QAAA,EAAA,EAAO,EAAA,EAAG,IAAA,EAAK,EAAA,EAAG,GAAA,EAAI,CAAA,EAAE,KAAA,EAAM,IAAA,EAAK,cAAA,EAAe,MAAA,EAAO,MAAA,EAAO;AAAA,CAAA,EACnE,CAAA;AAGK,IAAM,aAAA,GAA+C;AAAA,EAC1D,IAAA,EAAM,UAAA;AAAA,EACN,WAAA,EAAa,GAAA;AAAA,EACb,YAAA,EAAc,GAAA;AAAA,EACd,YAAA,EAAc,8BAAA;AAAA,EACd,WAAA,EAAa,YAAA;AAAA,EACb,aAAA,EAAe,wBAAA;AAAA,EACf,iBAAA,EAAmB,oBAAA;AAAA,EACnB,MAAM,wBAAwB,IAAA,EAAM;AAClC,IAAA,IAAI,CAAC,oBAAA,CAAqB,IAAI,CAAA,EAAG;AAC/B,MAAA,MAAM,IAAI,MAAM,+EAAuE,CAAA;AAAA,IACzF;AACA,IAAA,OAAO,0BAAA,CAA2B,KAAK,SAAS,CAAA;AAAA,EAClD,CAAA;AAAA,EACA,MAAM,0BAA0B,OAAA,EAA4C;AAC1E,IAAA,MAAM,OAAO,OAAA,CAAQ,UAAA;AACrB,IAAA,MAAM,SAAU,OAAA,CAAuC,MAAA;AACvD,IAAA,IAAI,CAAC,QAAQ,CAAC,MAAA,IAAU,CAAC,oBAAA,CAAqB,IAAI,GAAG,OAAO,IAAA;AAC5D,IAAA,MAAM,SAAA,GAAY,MAAM,0BAAA,CAA2B,IAAA,CAAK,SAAS,CAAA;AACjE,IAAA,OAAO,cAAA,CAAe,WAAW,MAAM,CAAA;AAAA,EACzC,CAAA;AAAA,EACA,IAAA,EAAM;AACR","file":"chunk-BMYC2ILT.mjs","sourcesContent":["'use client';\n\nimport { lazy, type ReactNode } from 'react';\nimport { renderGeometrySvgFromState } from './render';\nimport type {\n RestoredStampFile,\n StampType,\n} from '../shared/types';\nimport { svgToStampFile } from '../shared/svgToStampFile';\nimport {\n isGeometryCustomData,\n type GeometryCustomData,\n} from './types';\n\nexport type { GeometryCustomData };\n\nconst GeometryStampHost = lazy(() =>\n import('./host').then((m) => ({ default: m.GeometryStampHost })),\n);\n\nconst GeometryIcon: ReactNode = (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <polygon points=\"4,20 20,20 12,5\" />\n <circle cx=\"4\" cy=\"20\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\" />\n <circle cx=\"20\" cy=\"20\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\" />\n <circle cx=\"12\" cy=\"5\" r=\"1.4\" fill=\"currentColor\" stroke=\"none\" />\n </svg>\n);\n\nexport const geometryStamp: StampType<GeometryCustomData> = {\n kind: 'geometry',\n shortcutKey: 'g',\n toolbarLabel: 'G',\n toolbarTitle: 'Chèn hình học (G)',\n toolbarIcon: GeometryIcon,\n toolbarTestId: 'stamp-toolbar-geometry',\n matchesCustomData: isGeometryCustomData,\n async renderSvgFromCustomData(data) {\n if (!isGeometryCustomData(data)) {\n throw new Error('geometryStamp.renderSvgFromCustomData: customData không phải geometry');\n }\n return renderGeometrySvgFromState(data.jsonState);\n },\n async restoreFileFromCustomData(element): Promise<RestoredStampFile | null> {\n const data = element.customData as GeometryCustomData | undefined;\n const fileId = (element as { fileId?: string | null }).fileId;\n if (!data || !fileId || !isGeometryCustomData(data)) return null;\n const svgString = await renderGeometrySvgFromState(data.jsonState);\n return svgToStampFile(svgString, fileId);\n },\n Host: GeometryStampHost,\n};\n"]}
@@ -4,7 +4,7 @@ import { lazy } from 'react';
4
4
  import { jsx } from 'react/jsx-runtime';
5
5
 
6
6
  var LatexStampHost = lazy(
7
- () => import('./host-ZQCDAT6O.mjs').then((m) => ({ default: m.LatexStampHost }))
7
+ () => import('./host-H2IGOKJU.mjs').then((m) => ({ default: m.LatexStampHost }))
8
8
  );
9
9
  var LatexIcon = /* @__PURE__ */ jsx("svg", { width: "20", height: "20", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.6", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M17 5 H7 L13 12 L7 19 H17" }) });
10
10
  var latexStamp = {
@@ -35,5 +35,5 @@ var latexStamp = {
35
35
  };
36
36
 
37
37
  export { latexStamp };
38
- //# sourceMappingURL=chunk-PPKHCRRE.mjs.map
39
- //# sourceMappingURL=chunk-PPKHCRRE.mjs.map
38
+ //# sourceMappingURL=chunk-C76SOFXF.mjs.map
39
+ //# sourceMappingURL=chunk-C76SOFXF.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/stamps/latex/index.tsx"],"names":[],"mappings":";;;;AAYA,IAAM,cAAA,GAAiB,IAAA;AAAA,EAAK,MAC1B,OAAO,qBAAQ,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,MAAO,EAAE,OAAA,EAAS,CAAA,CAAE,cAAA,EAAe,CAAE;AAC9D,CAAA;AAEA,IAAM,SAAA,mBACJ,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAM,IAAA,EAAK,QAAO,IAAA,EAAK,OAAA,EAAQ,WAAA,EAAY,IAAA,EAAK,MAAA,EAAO,MAAA,EAAO,gBAAe,WAAA,EAAY,KAAA,EAAM,aAAA,EAAc,OAAA,EAAQ,cAAA,EAAe,OAAA,EAAQ,aAAA,EAAY,MAAA,EAC3J,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,2BAAA,EAA4B,CAAA,EACtC,CAAA;AAGK,IAAM,UAAA,GAAyC;AAAA,EACpD,IAAA,EAAM,OAAA;AAAA,EACN,WAAA,EAAa,GAAA;AAAA,EACb,YAAA,EAAc,GAAA;AAAA,EACd,YAAA,EAAc,qCAAA;AAAA,EACd,WAAA,EAAa,SAAA;AAAA,EACb,aAAA,EAAe,qBAAA;AAAA,EACf,iBAAA,EAAmB,iBAAA;AAAA,EACnB,MAAM,wBAAwB,IAAA,EAAM;AAClC,IAAA,IAAI,CAAC,iBAAA,CAAkB,IAAI,CAAA,EAAG;AAC5B,MAAA,MAAM,IAAI,MAAM,yEAAiE,CAAA;AAAA,IACnF;AACA,IAAA,OAAO,gBAAA,CAAiB,IAAA,CAAK,GAAA,EAAK,IAAA,CAAK,WAAW,CAAA;AAAA,EACpD,CAAA;AAAA,EACA,MAAM,0BAA0B,OAAA,EAA4C;AAC1E,IAAA,MAAM,OAAO,OAAA,CAAQ,UAAA;AACrB,IAAA,MAAM,SAAU,OAAA,CAAuC,MAAA;AACvD,IAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,MAAA,EAAQ,OAAO,IAAA;AAC7B,IAAA,IAAI,CAAC,iBAAA,CAAkB,IAAI,CAAA,EAAG,OAAO,IAAA;AACrC,IAAA,MAAM,YAAY,MAAM,gBAAA,CAAiB,IAAA,CAAK,GAAA,EAAK,KAAK,WAAW,CAAA;AACnE,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,kBAAA,CAAmB,SAAS,CAAC,CAAA;AACnD,IAAA,MAAM,OAAA,GAAU,4BAAA,IACd,OAAO,IAAA,KAAS,WAAA,GAAc,IAAA,CAAK,IAAI,CAAA,GAAI,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,CAAE,SAAS,QAAQ,CAAA,CAAA;AAEhF,IAAA,OAAO,EAAE,MAAA,EAAQ,OAAA,EAAS,QAAA,EAAU,eAAA,EAAgB;AAAA,EACtD,CAAA;AAAA,EACA,IAAA,EAAM;AACR","file":"chunk-PPKHCRRE.mjs","sourcesContent":["'use client';\n\nimport { lazy } from 'react';\nimport { renderLatexToSvg } from './render';\nimport type {\n RestoredStampFile,\n StampType,\n} from '../shared/types';\nimport { isLatexCustomData, type LatexCustomData } from './types';\n\nexport type { LatexCustomData };\n\nconst LatexStampHost = lazy(() =>\n import('./host').then((m) => ({ default: m.LatexStampHost })),\n);\n\nconst LatexIcon = (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M17 5 H7 L13 12 L7 19 H17\" />\n </svg>\n);\n\nexport const latexStamp: StampType<LatexCustomData> = {\n kind: 'latex',\n shortcutKey: 'l',\n toolbarLabel: 'L',\n toolbarTitle: 'Chèn công thức LaTeX (L)',\n toolbarIcon: LatexIcon,\n toolbarTestId: 'stamp-toolbar-latex',\n matchesCustomData: isLatexCustomData,\n async renderSvgFromCustomData(data) {\n if (!isLatexCustomData(data)) {\n throw new Error('latexStamp.renderSvgFromCustomData: customData không phải latex');\n }\n return renderLatexToSvg(data.src, data.displayMode);\n },\n async restoreFileFromCustomData(element): Promise<RestoredStampFile | null> {\n const data = element.customData as LatexCustomData | undefined;\n const fileId = (element as { fileId?: string | null }).fileId;\n if (!data || !fileId) return null;\n if (!isLatexCustomData(data)) return null;\n const svgString = await renderLatexToSvg(data.src, data.displayMode);\n const utf8 = unescape(encodeURIComponent(svgString));\n const dataURL = 'data:image/svg+xml;base64,' + (\n typeof btoa !== 'undefined' ? btoa(utf8) : Buffer.from(utf8).toString('base64')\n );\n return { fileId, dataURL, mimeType: 'image/svg+xml' };\n },\n Host: LatexStampHost,\n};\n"]}
1
+ {"version":3,"sources":["../src/stamps/latex/index.tsx"],"names":[],"mappings":";;;;AAYA,IAAM,cAAA,GAAiB,IAAA;AAAA,EAAK,MAC1B,OAAO,qBAAQ,CAAA,CAAE,IAAA,CAAK,CAAC,CAAA,MAAO,EAAE,OAAA,EAAS,CAAA,CAAE,cAAA,EAAe,CAAE;AAC9D,CAAA;AAEA,IAAM,SAAA,mBACJ,GAAA,CAAC,KAAA,EAAA,EAAI,KAAA,EAAM,IAAA,EAAK,QAAO,IAAA,EAAK,OAAA,EAAQ,WAAA,EAAY,IAAA,EAAK,MAAA,EAAO,MAAA,EAAO,gBAAe,WAAA,EAAY,KAAA,EAAM,aAAA,EAAc,OAAA,EAAQ,cAAA,EAAe,OAAA,EAAQ,aAAA,EAAY,MAAA,EAC3J,QAAA,kBAAA,GAAA,CAAC,MAAA,EAAA,EAAK,CAAA,EAAE,2BAAA,EAA4B,CAAA,EACtC,CAAA;AAGK,IAAM,UAAA,GAAyC;AAAA,EACpD,IAAA,EAAM,OAAA;AAAA,EACN,WAAA,EAAa,GAAA;AAAA,EACb,YAAA,EAAc,GAAA;AAAA,EACd,YAAA,EAAc,qCAAA;AAAA,EACd,WAAA,EAAa,SAAA;AAAA,EACb,aAAA,EAAe,qBAAA;AAAA,EACf,iBAAA,EAAmB,iBAAA;AAAA,EACnB,MAAM,wBAAwB,IAAA,EAAM;AAClC,IAAA,IAAI,CAAC,iBAAA,CAAkB,IAAI,CAAA,EAAG;AAC5B,MAAA,MAAM,IAAI,MAAM,yEAAiE,CAAA;AAAA,IACnF;AACA,IAAA,OAAO,gBAAA,CAAiB,IAAA,CAAK,GAAA,EAAK,IAAA,CAAK,WAAW,CAAA;AAAA,EACpD,CAAA;AAAA,EACA,MAAM,0BAA0B,OAAA,EAA4C;AAC1E,IAAA,MAAM,OAAO,OAAA,CAAQ,UAAA;AACrB,IAAA,MAAM,SAAU,OAAA,CAAuC,MAAA;AACvD,IAAA,IAAI,CAAC,IAAA,IAAQ,CAAC,MAAA,EAAQ,OAAO,IAAA;AAC7B,IAAA,IAAI,CAAC,iBAAA,CAAkB,IAAI,CAAA,EAAG,OAAO,IAAA;AACrC,IAAA,MAAM,YAAY,MAAM,gBAAA,CAAiB,IAAA,CAAK,GAAA,EAAK,KAAK,WAAW,CAAA;AACnE,IAAA,MAAM,IAAA,GAAO,QAAA,CAAS,kBAAA,CAAmB,SAAS,CAAC,CAAA;AACnD,IAAA,MAAM,OAAA,GAAU,4BAAA,IACd,OAAO,IAAA,KAAS,WAAA,GAAc,IAAA,CAAK,IAAI,CAAA,GAAI,MAAA,CAAO,IAAA,CAAK,IAAI,CAAA,CAAE,SAAS,QAAQ,CAAA,CAAA;AAEhF,IAAA,OAAO,EAAE,MAAA,EAAQ,OAAA,EAAS,QAAA,EAAU,eAAA,EAAgB;AAAA,EACtD,CAAA;AAAA,EACA,IAAA,EAAM;AACR","file":"chunk-C76SOFXF.mjs","sourcesContent":["'use client';\n\nimport { lazy } from 'react';\nimport { renderLatexToSvg } from './render';\nimport type {\n RestoredStampFile,\n StampType,\n} from '../shared/types';\nimport { isLatexCustomData, type LatexCustomData } from './types';\n\nexport type { LatexCustomData };\n\nconst LatexStampHost = lazy(() =>\n import('./host').then((m) => ({ default: m.LatexStampHost })),\n);\n\nconst LatexIcon = (\n <svg width=\"20\" height=\"20\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"1.6\" strokeLinecap=\"round\" strokeLinejoin=\"round\" aria-hidden=\"true\">\n <path d=\"M17 5 H7 L13 12 L7 19 H17\" />\n </svg>\n);\n\nexport const latexStamp: StampType<LatexCustomData> = {\n kind: 'latex',\n shortcutKey: 'l',\n toolbarLabel: 'L',\n toolbarTitle: 'Chèn công thức LaTeX (L)',\n toolbarIcon: LatexIcon,\n toolbarTestId: 'stamp-toolbar-latex',\n matchesCustomData: isLatexCustomData,\n async renderSvgFromCustomData(data) {\n if (!isLatexCustomData(data)) {\n throw new Error('latexStamp.renderSvgFromCustomData: customData không phải latex');\n }\n return renderLatexToSvg(data.src, data.displayMode);\n },\n async restoreFileFromCustomData(element): Promise<RestoredStampFile | null> {\n const data = element.customData as LatexCustomData | undefined;\n const fileId = (element as { fileId?: string | null }).fileId;\n if (!data || !fileId) return null;\n if (!isLatexCustomData(data)) return null;\n const svgString = await renderLatexToSvg(data.src, data.displayMode);\n const utf8 = unescape(encodeURIComponent(svgString));\n const dataURL = 'data:image/svg+xml;base64,' + (\n typeof btoa !== 'undefined' ? btoa(utf8) : Buffer.from(utf8).toString('base64')\n );\n return { fileId, dataURL, mimeType: 'image/svg+xml' };\n },\n Host: LatexStampHost,\n};\n"]}
@@ -191,6 +191,18 @@ function createStore(initial, options = {}) {
191
191
  };
192
192
  }
193
193
 
194
+ // src/core/scene/kinds/_label.ts
195
+ function labelOpts(labelOffset, dflt) {
196
+ const offset = labelOffset ?? dflt;
197
+ return { label: { fixed: false, ...offset ? { offset } : {} } };
198
+ }
199
+ function readLabelOffset(label) {
200
+ const off = label.evalVisProp?.("offset") ?? label.visProp?.offset;
201
+ const rel = label.relativeCoords?.scrCoords;
202
+ if (!off || !rel || off.length < 2 || rel.length < 3) return null;
203
+ return [Math.round(off[0] + rel[1]), Math.round(off[1] - rel[2])];
204
+ }
205
+
194
206
  // src/core/scene/expressions/parser.ts
195
207
  var ALLOWED_CONSTANTS = ["pi", "e"];
196
208
  var ALLOWED_FUNCTIONS = [
@@ -310,6 +322,6 @@ function collectFreeVars(expression) {
310
322
  return out.sort();
311
323
  }
312
324
 
313
- export { collectFreeVars, compile, createStore, validate };
314
- //# sourceMappingURL=chunk-IHUFOV7L.mjs.map
315
- //# sourceMappingURL=chunk-IHUFOV7L.mjs.map
325
+ export { collectFreeVars, compile, createStore, labelOpts, readLabelOffset, validate };
326
+ //# sourceMappingURL=chunk-CH6SFONH.mjs.map
327
+ //# sourceMappingURL=chunk-CH6SFONH.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/core/scene/reducer.ts","../src/core/scene/store.ts","../src/core/scene/kinds/_label.ts","../src/core/scene/expressions/parser.ts"],"names":[],"mappings":"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","file":"chunk-CH6SFONH.mjs","sourcesContent":["// src/core/scene/reducer.ts\nimport type { Draft } from 'immer';\nimport type { Action, State, SceneObject } from './types';\nimport { getKind } from './registry';\n\nfunction collectDependents(state: Draft<State> | State, rootId: string): Set<string> {\n const dependents = new Set<string>([rootId]);\n let grew = true;\n while (grew) {\n grew = false;\n for (const obj of Object.values(state.objects) as SceneObject[]) {\n if (dependents.has(obj.id)) continue;\n let kindDef;\n try { kindDef = getKind(obj.kind); } catch { continue; }\n const refs = kindDef.dependsOn(obj.attrs as never);\n if (refs.some(r => dependents.has(r))) {\n dependents.add(obj.id);\n grew = true;\n }\n }\n }\n return dependents;\n}\n\nexport function reduce(draft: Draft<State>, action: Action): void {\n switch (action.type) {\n case 'ADD': {\n const { obj } = action.payload;\n if (draft.objects[obj.id]) throw new Error(`[scene] id \"${obj.id}\" đã tồn tại`);\n const kindDef = getKind(obj.kind);\n kindDef.validate?.(obj.attrs as never);\n draft.objects[obj.id] = obj;\n draft.order.push(obj.id);\n draft.counter += 1;\n return;\n }\n case 'UPDATE': {\n const { id, patch } = action.payload;\n const obj = draft.objects[id];\n if (!obj) return;\n Object.assign(obj, patch);\n return;\n }\n case 'UPDATE_ATTRS': {\n const { id, patch } = action.payload;\n const obj = draft.objects[id];\n if (!obj) return;\n const kindDef = getKind(obj.kind);\n const nextAttrs = { ...(obj.attrs as object), ...patch };\n kindDef.validate?.(nextAttrs as never);\n obj.attrs = nextAttrs;\n return;\n }\n case 'DELETE': {\n const { id } = action.payload;\n if (!draft.objects[id]) return;\n const toDelete = collectDependents(draft, id);\n for (const delId of toDelete) {\n delete draft.objects[delId];\n }\n draft.order = draft.order.filter(x => !toDelete.has(x));\n return;\n }\n case 'RESET': {\n draft.objects = {};\n draft.order = [];\n draft.counter = 0;\n return;\n }\n case 'LOAD': {\n const { state } = action.payload;\n draft.objects = { ...state.objects };\n draft.order = [...state.order];\n draft.counter = state.counter;\n // Cast qua Draft<State['meta']> — immer chấp nhận structurally\n // identical shape, chỉ stricter readonly.\n draft.meta = { ...state.meta } as typeof draft.meta;\n return;\n }\n case 'TRANSACTION': {\n for (const sub of action.payload.actions) {\n reduce(draft, sub);\n }\n return;\n }\n case 'UPDATE_VIEW': {\n // Action payload typed cho graph-2d ViewSettings. Bỏ qua nếu domain\n // khác (view shape không tương thích — 2d dùng bbox, 3d dùng bbox3D).\n if (draft.meta.domain !== 'graph2d') return;\n Object.assign(draft.meta.view, action.payload.patch);\n return;\n }\n }\n}\n","// src/core/scene/store.ts\nimport { produce } from 'immer';\nimport { reduce } from './reducer';\nimport type { Action, State } from './types';\n\nexport type StoreListener = (next: State, prev: State, action: Action) => void;\n\nexport interface Store {\n getState(): State;\n dispatch(action: Action): void;\n subscribe(listener: StoreListener): () => void;\n undo(): void;\n redo(): void;\n canUndo(): boolean;\n canRedo(): boolean;\n transaction(fn: (dispatch: (a: Action) => void) => void): void;\n withoutHistory(fn: () => void): void;\n}\n\nexport type StoreOptions = { historyLimit?: number };\n\nconst HISTORY_DEFAULT = 100;\n\nconst UNDO_ACTION: Action = { type: 'TRANSACTION', payload: { actions: [] } };\nconst REDO_ACTION: Action = { type: 'TRANSACTION', payload: { actions: [] } };\n\nexport function createStore(initial: State, options: StoreOptions = {}): Store {\n const limit = options.historyLimit ?? HISTORY_DEFAULT;\n let state = initial;\n const past: State[] = [];\n const future: State[] = [];\n const listeners = new Set<StoreListener>();\n let dispatching = false;\n let suspendHistory = false;\n let transactionActions: Action[] | null = null;\n\n function notify(prev: State, action: Action): void {\n listeners.forEach(l => l(state, prev, action));\n }\n\n function pushHistory(prev: State): void {\n if (suspendHistory) return;\n past.push(prev);\n if (past.length > limit) past.shift();\n future.length = 0;\n }\n\n function applyAction(action: Action): void {\n const prev = state;\n state = produce(state, draft => { reduce(draft, action); });\n if (state !== prev) {\n pushHistory(prev);\n notify(prev, action);\n }\n }\n\n return {\n getState: () => state,\n\n dispatch(action: Action) {\n if (dispatching) throw new Error('[scene] không được dispatch trong subscriber');\n if (transactionActions) {\n transactionActions.push(action);\n return;\n }\n dispatching = true;\n try { applyAction(action); } finally { dispatching = false; }\n },\n\n subscribe(listener) {\n listeners.add(listener);\n return () => { listeners.delete(listener); };\n },\n\n undo() {\n const prev = past.pop();\n if (!prev) return;\n future.push(state);\n const old = state;\n state = prev;\n notify(old, UNDO_ACTION);\n },\n\n redo() {\n const next = future.pop();\n if (!next) return;\n past.push(state);\n if (past.length > limit) past.shift();\n const old = state;\n state = next;\n notify(old, REDO_ACTION);\n },\n\n canUndo: () => past.length > 0,\n canRedo: () => future.length > 0,\n\n transaction(fn) {\n if (transactionActions) throw new Error('[scene] transaction lồng nhau không hỗ trợ');\n transactionActions = [];\n try { fn((a) => { transactionActions!.push(a); }); }\n finally {\n const actions = transactionActions;\n transactionActions = null;\n if (actions.length > 0) {\n applyAction({ type: 'TRANSACTION', payload: { actions } });\n }\n }\n },\n\n withoutHistory(fn) {\n const prevSuspend = suspendHistory;\n suspendHistory = true;\n try { fn(); } finally { suspendHistory = prevSuspend; }\n },\n };\n}\n","// src/core/scene/kinds/_label.ts\n/** Offset nhãn (pixel) so với anchor. offset[1] dương = nhãn đi lên (quy ước JSXGraph). */\nexport type LabelOffset = [number, number];\n\n/**\n * Opts cho `label` của một JSXGraph element: luôn `fixed:false` (kéo được).\n * `labelOffset` (nếu có) hoặc `dflt` (nếu có) thành `offset`. Không có cả hai\n * → chỉ `{ fixed:false }` (giữ default JSXGraph, byte-identical với hiện tại\n * cho line/circle vốn không set offset).\n */\nexport function labelOpts(\n labelOffset?: LabelOffset,\n dflt?: LabelOffset,\n): { label: Record<string, unknown> } {\n const offset = labelOffset ?? dflt;\n return { label: { fixed: false, ...(offset ? { offset } : {}) } };\n}\n\n/**\n * Đọc offset-tổng (pixel) của một label JSXGraph sau khi user kéo, quy về dạng\n * thuần `offset` (để zero relativeCoords mà vị trí không đổi):\n * x = offset[0] + rel.scrCoords[1]\n * y = offset[1] - rel.scrCoords[2] (screen-y xuống, offset-y lên)\n * Trả null nếu thiếu dữ liệu.\n */\nexport function readLabelOffset(label: {\n evalVisProp?: (k: string) => unknown;\n visProp?: { offset?: number[] };\n relativeCoords?: { scrCoords?: number[] };\n}): LabelOffset | null {\n const off = (label.evalVisProp?.('offset') as number[] | undefined) ?? label.visProp?.offset;\n const rel = label.relativeCoords?.scrCoords;\n if (!off || !rel || off.length < 2 || rel.length < 3) return null;\n return [Math.round(off[0] + rel[1]), Math.round(off[1] - rel[2])];\n}\n","// src/core/scene/expressions/parser.ts\n// Pure expression parser cho function2d kind. Không import JSXGraph/React.\n\nexport const ALLOWED_CONSTANTS = ['pi', 'e'] as const;\nexport const ALLOWED_FUNCTIONS = [\n 'sin', 'cos', 'tan',\n 'asin', 'acos', 'atan', 'atan2',\n 'sinh', 'cosh', 'tanh',\n 'exp', 'log', 'log10', 'ln',\n 'sqrt', 'cbrt', 'abs',\n 'floor', 'ceil', 'round',\n 'min', 'max', 'pow',\n] as const;\n\nconst ID_RE = /[A-Za-z_][A-Za-z0-9_]*/g;\nconst UNSAFE_RE = /[=;{}]|\\beval\\b|\\bnew\\b|\\breturn\\b|\\bthis\\b|\\bwindow\\b|\\bdocument\\b|\\bglobal\\b|\\bprocess\\b/;\n\nconst NUMBER_RE = /^[+-]?(\\d+\\.?\\d*|\\.\\d+)([eE][+-]?\\d+)?$/;\n\nexport type ValidateResult = { ok: true } | { ok: false; error: string };\n\n// Detect identifiers used as function calls (followed by '(')\nconst FUNC_CALL_RE = /([A-Za-z_][A-Za-z0-9_]*)\\s*\\(/g;\n\n/** Kiểm tra expression có hợp lệ không. */\nexport function validate(expression: string): ValidateResult {\n const trimmed = expression.trim();\n if (!trimmed) return { ok: false, error: 'Biểu thức rỗng' };\n if (UNSAFE_RE.test(trimmed)) return { ok: false, error: 'Biểu thức chứa toán tử hoặc identifier không cho phép' };\n\n // Replace ^ với ** (JS không có ^)\n const jsExpr = trimmed.replace(/\\^/g, '**');\n\n // Check function call identifiers — must be in ALLOWED_FUNCTIONS\n let fm: RegExpExecArray | null;\n FUNC_CALL_RE.lastIndex = 0;\n while ((fm = FUNC_CALL_RE.exec(jsExpr)) !== null) {\n const fnName = fm[1];\n if (!(ALLOWED_FUNCTIONS as readonly string[]).includes(fnName)) {\n return { ok: false, error: `Hàm không hợp lệ: ${fnName}` };\n }\n }\n\n // Check identifiers\n const ids = new Set<string>();\n let m: RegExpExecArray | null;\n ID_RE.lastIndex = 0;\n while ((m = ID_RE.exec(jsExpr)) !== null) ids.add(m[0]);\n\n for (const id of ids) {\n if (id === 'x') continue;\n if ((ALLOWED_CONSTANTS as readonly string[]).includes(id)) continue;\n if ((ALLOWED_FUNCTIONS as readonly string[]).includes(id)) continue;\n // Remaining identifiers treat as parameter — OK at validate time.\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(id)) {\n return { ok: false, error: `Identifier không hợp lệ: ${id}` };\n }\n }\n\n // Try syntactic compile (with dummy params) to catch syntax errors.\n try {\n buildFunctionBody(jsExpr, Array.from(ids).filter(\n (id) => id !== 'x' && !(ALLOWED_CONSTANTS as readonly string[]).includes(id) && !(ALLOWED_FUNCTIONS as readonly string[]).includes(id),\n ));\n } catch (err) {\n return { ok: false, error: `Cú pháp lỗi: ${(err as Error).message}` };\n }\n\n return { ok: true };\n}\n\n/**\n * Compile expression thành function (x: number) => number.\n * - `params` map từ identifier → value để inline vào closure.\n * - Trả string error nếu invalid.\n */\nexport function compile(\n expression: string,\n params: Record<string, number>,\n): ((x: number) => number) | string {\n const v = validate(expression);\n if (!v.ok) return v.error;\n const jsExpr = expression.trim().replace(/\\^/g, '**');\n\n const ids = new Set<string>();\n let m: RegExpExecArray | null;\n ID_RE.lastIndex = 0;\n while ((m = ID_RE.exec(jsExpr)) !== null) ids.add(m[0]);\n\n const paramNames: string[] = [];\n for (const id of ids) {\n if (id === 'x') continue;\n if ((ALLOWED_CONSTANTS as readonly string[]).includes(id)) continue;\n if ((ALLOWED_FUNCTIONS as readonly string[]).includes(id)) continue;\n paramNames.push(id);\n }\n\n try {\n const body = buildFunctionBody(jsExpr, paramNames);\n \n const fn = new Function('x', ...paramNames, body) as (\n x: number,\n ...args: number[]\n ) => number;\n const args = paramNames.map((name) => params[name] ?? NaN);\n return (x: number) => fn(x, ...args);\n } catch (err) {\n return `Compile error: ${(err as Error).message}`;\n }\n}\n\nfunction buildFunctionBody(jsExpr: string, _paramNames: string[]): string {\n // Inline Math constants and functions.\n // sin → Math.sin, pi → Math.PI, e → Math.E, ln → Math.log.\n let body = jsExpr;\n body = body.replace(/\\bln\\b/g, 'Math.log');\n body = body.replace(/\\blog\\b/g, 'Math.log10');\n body = body.replace(/\\bpi\\b/g, '(Math.PI)');\n body = body.replace(/\\be\\b(?!\\w)/g, '(Math.E)');\n for (const fn of ALLOWED_FUNCTIONS) {\n if (fn === 'log' || fn === 'log10') continue; // already handled\n body = body.replace(new RegExp(`\\\\b${fn}\\\\b`, 'g'), `Math.${fn}`);\n }\n return `\"use strict\"; return (${body});`;\n}\n\n/** Liệt kê free identifiers (≠ x, ≠ allowed const/func). */\nexport function collectFreeVars(expression: string): string[] {\n const v = validate(expression);\n if (!v.ok) return [];\n const ids = new Set<string>();\n let m: RegExpExecArray | null;\n ID_RE.lastIndex = 0;\n while ((m = ID_RE.exec(expression)) !== null) ids.add(m[0]);\n const out: string[] = [];\n for (const id of ids) {\n if (id === 'x') continue;\n if ((ALLOWED_CONSTANTS as readonly string[]).includes(id)) continue;\n if ((ALLOWED_FUNCTIONS as readonly string[]).includes(id)) continue;\n if (NUMBER_RE.test(id)) continue;\n out.push(id);\n }\n return out.sort();\n}\n"]}