@xom11/whiteboard 0.11.0 → 0.24.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 (112) hide show
  1. package/README.md +67 -0
  2. package/dist/{ExcalidrawWithMenus-EAVPOPJZ.mjs → ExcalidrawWithMenus-KBLDWPM2.mjs} +2 -3
  3. package/dist/ExcalidrawWithMenus-KBLDWPM2.mjs.map +1 -0
  4. package/dist/catalog.json +57 -0
  5. package/dist/{chunk-PWIMZIB6.mjs → chunk-2SKXRBGS.mjs} +7 -8
  6. package/dist/chunk-2SKXRBGS.mjs.map +1 -0
  7. package/dist/chunk-33PEN2WC.mjs +57 -0
  8. package/dist/chunk-33PEN2WC.mjs.map +1 -0
  9. package/dist/chunk-3KBL77M6.mjs +127 -0
  10. package/dist/chunk-3KBL77M6.mjs.map +1 -0
  11. package/dist/chunk-5UTGXHLJ.mjs +57 -0
  12. package/dist/chunk-5UTGXHLJ.mjs.map +1 -0
  13. package/dist/chunk-6XUPIGVD.mjs +467 -0
  14. package/dist/chunk-6XUPIGVD.mjs.map +1 -0
  15. package/dist/chunk-7WG2KDRF.mjs +28 -0
  16. package/dist/chunk-7WG2KDRF.mjs.map +1 -0
  17. package/dist/chunk-FZY33J6Z.mjs +95 -0
  18. package/dist/chunk-FZY33J6Z.mjs.map +1 -0
  19. package/dist/chunk-HNQLZIEP.mjs +78 -0
  20. package/dist/chunk-HNQLZIEP.mjs.map +1 -0
  21. package/dist/chunk-NVJ7K3DK.mjs +29 -0
  22. package/dist/chunk-NVJ7K3DK.mjs.map +1 -0
  23. package/dist/chunk-O4WIZFRQ.mjs +11 -0
  24. package/dist/chunk-O4WIZFRQ.mjs.map +1 -0
  25. package/dist/{chunk-YVJP7NRG.mjs → chunk-O6QTYAKE.mjs} +7 -9
  26. package/dist/chunk-O6QTYAKE.mjs.map +1 -0
  27. package/dist/chunk-R5FL6S7L.mjs +22 -0
  28. package/dist/chunk-R5FL6S7L.mjs.map +1 -0
  29. package/dist/chunk-RBUILBX3.mjs +388 -0
  30. package/dist/chunk-RBUILBX3.mjs.map +1 -0
  31. package/dist/chunk-RD34F5PM.mjs +57 -0
  32. package/dist/chunk-RD34F5PM.mjs.map +1 -0
  33. package/dist/{chunk-7P7SQFOW.mjs → chunk-RXOFO64U.mjs} +3 -3
  34. package/dist/chunk-RXOFO64U.mjs.map +1 -0
  35. package/dist/chunk-TOOHCAWP.mjs +1167 -0
  36. package/dist/chunk-TOOHCAWP.mjs.map +1 -0
  37. package/dist/{chunk-C6SCVOMC.mjs → chunk-TQYQVXNW.mjs} +5 -41
  38. package/dist/chunk-TQYQVXNW.mjs.map +1 -0
  39. package/dist/chunk-VBJLUHCY.mjs +23 -0
  40. package/dist/chunk-VBJLUHCY.mjs.map +1 -0
  41. package/dist/chunk-VRWZILTG.mjs +205 -0
  42. package/dist/chunk-VRWZILTG.mjs.map +1 -0
  43. package/dist/chunk-XVSO7FBM.mjs +61 -0
  44. package/dist/chunk-XVSO7FBM.mjs.map +1 -0
  45. package/dist/geometry-2d.d.mts +3 -6
  46. package/dist/geometry-2d.d.ts +3 -6
  47. package/dist/geometry-2d.js +5069 -2651
  48. package/dist/geometry-2d.js.map +1 -1
  49. package/dist/geometry-2d.mjs +8 -4
  50. package/dist/geometry-3d.d.mts +4 -7
  51. package/dist/geometry-3d.d.ts +4 -7
  52. package/dist/geometry-3d.js +3053 -2150
  53. package/dist/geometry-3d.js.map +1 -1
  54. package/dist/geometry-3d.mjs +7 -4
  55. package/dist/graph-2d.d.mts +4 -7
  56. package/dist/graph-2d.d.ts +4 -7
  57. package/dist/graph-2d.js +3363 -1670
  58. package/dist/graph-2d.js.map +1 -1
  59. package/dist/graph-2d.mjs +10 -3
  60. package/dist/host-3N4E4KJH.mjs +1142 -0
  61. package/dist/host-3N4E4KJH.mjs.map +1 -0
  62. package/dist/{host-Z3TEJKZA.mjs → host-6SNSZ332.mjs} +4 -4
  63. package/dist/{host-Z3TEJKZA.mjs.map → host-6SNSZ332.mjs.map} +1 -1
  64. package/dist/host-EVJT3LIF.mjs +3198 -0
  65. package/dist/host-EVJT3LIF.mjs.map +1 -0
  66. package/dist/host-HN4X3TBC.mjs +2374 -0
  67. package/dist/host-HN4X3TBC.mjs.map +1 -0
  68. package/dist/index.css +4 -1
  69. package/dist/index.css.map +1 -1
  70. package/dist/index.d.mts +659 -19
  71. package/dist/index.d.ts +659 -19
  72. package/dist/index.js +11741 -9420
  73. package/dist/index.js.map +1 -1
  74. package/dist/index.mjs +1467 -336
  75. package/dist/index.mjs.map +1 -1
  76. package/dist/latex.d.mts +3 -4
  77. package/dist/latex.d.ts +3 -4
  78. package/dist/latex.js +33 -18
  79. package/dist/latex.js.map +1 -1
  80. package/dist/latex.mjs +2 -3
  81. package/dist/render-OCVGDKK6.mjs +8 -0
  82. package/dist/render-OCVGDKK6.mjs.map +1 -0
  83. package/dist/serialize-GKN6OVPM.mjs +6 -0
  84. package/dist/serialize-GKN6OVPM.mjs.map +1 -0
  85. package/dist/{types-CinstD7T.d.mts → types-rA4slL08.d.mts} +69 -4
  86. package/dist/{types-CinstD7T.d.ts → types-rA4slL08.d.ts} +69 -4
  87. package/package.json +24 -5
  88. package/dist/ExcalidrawWithMenus-EAVPOPJZ.mjs.map +0 -1
  89. package/dist/chunk-74VEEZBV.mjs +0 -619
  90. package/dist/chunk-74VEEZBV.mjs.map +0 -1
  91. package/dist/chunk-7P7SQFOW.mjs.map +0 -1
  92. package/dist/chunk-BJTO5JO5.mjs +0 -11
  93. package/dist/chunk-BJTO5JO5.mjs.map +0 -1
  94. package/dist/chunk-C6SCVOMC.mjs.map +0 -1
  95. package/dist/chunk-D257NCQW.mjs +0 -58
  96. package/dist/chunk-D257NCQW.mjs.map +0 -1
  97. package/dist/chunk-G7FR3AIV.mjs +0 -193
  98. package/dist/chunk-G7FR3AIV.mjs.map +0 -1
  99. package/dist/chunk-HTBLO5JO.mjs +0 -41
  100. package/dist/chunk-HTBLO5JO.mjs.map +0 -1
  101. package/dist/chunk-PWIMZIB6.mjs.map +0 -1
  102. package/dist/chunk-SBDMF4NQ.mjs +0 -212
  103. package/dist/chunk-SBDMF4NQ.mjs.map +0 -1
  104. package/dist/chunk-WQOABS6N.mjs +0 -197
  105. package/dist/chunk-WQOABS6N.mjs.map +0 -1
  106. package/dist/chunk-YVJP7NRG.mjs.map +0 -1
  107. package/dist/host-N6ACNJKI.mjs +0 -3226
  108. package/dist/host-N6ACNJKI.mjs.map +0 -1
  109. package/dist/host-NKGV6RF2.mjs +0 -1134
  110. package/dist/host-NKGV6RF2.mjs.map +0 -1
  111. package/dist/host-XVK7UCRE.mjs +0 -2908
  112. package/dist/host-XVK7UCRE.mjs.map +0 -1
@@ -1,212 +0,0 @@
1
- "use client";
2
- import { useState, useRef, useCallback, useEffect } from 'react';
3
- import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
4
-
5
- // src/stamps/shared/useChordShortcut.ts
6
- var A_CODE = "a".charCodeAt(0);
7
- function isFieldFocused() {
8
- const ae = typeof document !== "undefined" ? document.activeElement : null;
9
- return !!(ae && (ae.tagName === "INPUT" || ae.tagName === "TEXTAREA" || ae.isContentEditable));
10
- }
11
- function useChordShortcut(args) {
12
- const { groupOrder, tools, onSelect, enabled } = args;
13
- const [chordGroup, setChordGroup] = useState(null);
14
- const groupOrderRef = useRef(groupOrder);
15
- const toolsRef = useRef(tools);
16
- const onSelectRef = useRef(onSelect);
17
- const chordGroupRef = useRef(null);
18
- groupOrderRef.current = groupOrder;
19
- toolsRef.current = tools;
20
- onSelectRef.current = onSelect;
21
- const cancel = useCallback(() => {
22
- chordGroupRef.current = null;
23
- setChordGroup(null);
24
- }, []);
25
- useEffect(() => {
26
- if (!enabled) return;
27
- const setChord = (next) => {
28
- chordGroupRef.current = next;
29
- setChordGroup(next);
30
- };
31
- const onKey = (e) => {
32
- if (e.metaKey || e.ctrlKey || e.altKey) return;
33
- if (isFieldFocused()) return;
34
- const key = e.key;
35
- const lower = key.length === 1 ? key.toLowerCase() : key;
36
- if (key === "Escape") {
37
- if (chordGroupRef.current !== null) {
38
- e.preventDefault();
39
- e.stopPropagation();
40
- setChord(null);
41
- }
42
- return;
43
- }
44
- if (lower.length === 1 && lower >= "a" && lower <= "z") {
45
- const idx = lower.charCodeAt(0) - A_CODE;
46
- if (idx < groupOrderRef.current.length) {
47
- e.preventDefault();
48
- e.stopPropagation();
49
- setChord(groupOrderRef.current[idx]);
50
- }
51
- return;
52
- }
53
- if (key >= "1" && key <= "9") {
54
- const active = chordGroupRef.current;
55
- if (active === null) return;
56
- const n = key.charCodeAt(0) - "1".charCodeAt(0);
57
- const toolsInGroup = toolsRef.current.filter(
58
- (t) => t.group === active
59
- );
60
- e.preventDefault();
61
- e.stopPropagation();
62
- if (n < toolsInGroup.length) {
63
- onSelectRef.current(toolsInGroup[n].key);
64
- }
65
- setChord(null);
66
- return;
67
- }
68
- };
69
- window.addEventListener("keydown", onKey, { capture: true });
70
- return () => {
71
- window.removeEventListener("keydown", onKey, { capture: true });
72
- };
73
- }, [enabled]);
74
- return { chordGroup, cancel };
75
- }
76
- function MobileToolDrawer({
77
- title,
78
- headerIcon,
79
- chips,
80
- actions,
81
- groups,
82
- activeTool,
83
- onToolSelect,
84
- drawerOpen,
85
- onDrawerClose,
86
- isDark,
87
- testId
88
- }) {
89
- return /* @__PURE__ */ jsxs(Fragment, { children: [
90
- drawerOpen && /* @__PURE__ */ jsx(
91
- "div",
92
- {
93
- className: "stamp-drawer-backdrop",
94
- onPointerDown: onDrawerClose,
95
- "aria-hidden": "true"
96
- }
97
- ),
98
- /* @__PURE__ */ jsxs(
99
- "aside",
100
- {
101
- role: "complementary",
102
- "aria-label": title,
103
- "aria-hidden": !drawerOpen ? "true" : void 0,
104
- "data-testid": testId,
105
- "data-stamp-area": "true",
106
- "data-mobile-drawer": "true",
107
- "data-geo-mobile": "true",
108
- "data-drawer-state": drawerOpen ? "open" : "closed",
109
- className: [
110
- isDark ? "theme--dark " : "",
111
- "stamp-drawer-mobile flex flex-col border-r border-slate-200 bg-white shadow-md"
112
- ].join(""),
113
- children: [
114
- /* @__PURE__ */ jsxs("header", { className: "flex items-center justify-between border-b border-slate-200 bg-gradient-to-r from-slate-50 to-white px-4 py-3", children: [
115
- /* @__PURE__ */ jsxs("h3", { className: "flex items-center gap-2 text-base font-semibold text-slate-800", children: [
116
- /* @__PURE__ */ jsx("span", { className: "inline-flex h-7 w-7 items-center justify-center rounded-lg bg-emerald-50 text-emerald-700", children: headerIcon }),
117
- title
118
- ] }),
119
- /* @__PURE__ */ jsx(
120
- "button",
121
- {
122
- type: "button",
123
- onClick: onDrawerClose,
124
- "aria-label": "\u0110\xF3ng ng\u0103n c\xF4ng c\u1EE5",
125
- className: "inline-flex h-9 w-9 items-center justify-center rounded-full text-slate-500 transition hover:bg-slate-100 hover:text-slate-800",
126
- children: /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", children: [
127
- /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
128
- /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" })
129
- ] })
130
- }
131
- )
132
- ] }),
133
- /* @__PURE__ */ jsxs("div", { className: "sticky top-0 z-10 flex items-center gap-2 border-b border-slate-200 bg-white/95 px-3 py-2 backdrop-blur", children: [
134
- chips.map((c) => /* @__PURE__ */ jsxs(
135
- "button",
136
- {
137
- type: "button",
138
- role: "switch",
139
- "aria-pressed": c.pressed,
140
- "aria-label": c.label,
141
- "data-testid": c.testId,
142
- onClick: () => c.onToggle(!c.pressed),
143
- className: "geo-mobile-chip",
144
- children: [
145
- c.icon,
146
- c.label
147
- ]
148
- },
149
- c.label
150
- )),
151
- actions.length > 0 && /* @__PURE__ */ jsx("div", { className: "ml-auto flex items-center gap-1", children: actions.map((a) => /* @__PURE__ */ jsx(
152
- "button",
153
- {
154
- type: "button",
155
- onClick: a.onClick,
156
- disabled: a.disabled,
157
- "aria-label": a.label,
158
- title: a.title ?? a.label,
159
- "data-testid": a.testId,
160
- className: "inline-flex h-9 w-9 items-center justify-center rounded-full text-slate-600 transition hover:bg-slate-100 hover:text-slate-900 disabled:cursor-not-allowed disabled:text-slate-300 disabled:hover:bg-transparent",
161
- children: a.icon
162
- },
163
- a.label
164
- )) })
165
- ] }),
166
- /* @__PURE__ */ jsx(
167
- "div",
168
- {
169
- className: "min-h-0 flex-1 overflow-y-auto",
170
- style: { paddingBottom: "calc(0.75rem + env(safe-area-inset-bottom))" },
171
- children: groups.map((g) => /* @__PURE__ */ jsxs("section", { className: "px-3 pt-3 pb-1", children: [
172
- /* @__PURE__ */ jsxs("h4", { className: "mb-2 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-wider text-slate-500", children: [
173
- /* @__PURE__ */ jsx("span", { className: "h-1 w-1 rounded-full bg-emerald-500" }),
174
- g.groupLabel
175
- ] }),
176
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-3 gap-2", children: g.tools.map((t) => {
177
- const active = activeTool === t.key;
178
- return /* @__PURE__ */ jsxs(
179
- "button",
180
- {
181
- type: "button",
182
- "aria-label": t.label,
183
- "aria-pressed": active,
184
- "data-tool": t.key,
185
- onClick: () => {
186
- onToolSelect(t.key);
187
- onDrawerClose();
188
- },
189
- className: [
190
- "flex flex-col items-center justify-center gap-1.5 rounded-2xl px-2 py-3 transition active:scale-95",
191
- active ? "geo-mobile-tool-active" : "bg-slate-50 text-slate-700 hover:bg-slate-100"
192
- ].join(" "),
193
- children: [
194
- /* @__PURE__ */ jsx("span", { className: "flex h-6 w-6 items-center justify-center", children: t.icon }),
195
- /* @__PURE__ */ jsx("span", { className: "text-center text-[11px] font-medium leading-tight line-clamp-2", children: t.label })
196
- ]
197
- },
198
- t.key
199
- );
200
- }) })
201
- ] }, g.group))
202
- }
203
- )
204
- ]
205
- }
206
- )
207
- ] });
208
- }
209
-
210
- export { MobileToolDrawer, useChordShortcut };
211
- //# sourceMappingURL=chunk-SBDMF4NQ.mjs.map
212
- //# sourceMappingURL=chunk-SBDMF4NQ.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/stamps/shared/useChordShortcut.ts","../src/stamps/shared/MobileToolDrawer.tsx"],"names":[],"mappings":"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","file":"chunk-SBDMF4NQ.mjs","sourcesContent":["import { useCallback, useEffect, useRef, useState } from 'react';\n\ninterface UseChordShortcutArgs<G extends string> {\n groupOrder: readonly G[];\n tools: ReadonlyArray<{ key: string; group: G }>;\n onSelect: (toolKey: string) => void;\n enabled: boolean;\n}\n\ninterface UseChordShortcutResult<G extends string> {\n chordGroup: G | null;\n cancel: () => void;\n}\n\nconst A_CODE = 'a'.charCodeAt(0);\n\nfunction isFieldFocused(): boolean {\n const ae = (typeof document !== 'undefined'\n ? (document.activeElement as HTMLElement | null)\n : null);\n return !!(\n ae &&\n (ae.tagName === 'INPUT' ||\n ae.tagName === 'TEXTAREA' ||\n ae.isContentEditable)\n );\n}\n\nexport function useChordShortcut<G extends string>(\n args: UseChordShortcutArgs<G>,\n): UseChordShortcutResult<G> {\n const { groupOrder, tools, onSelect, enabled } = args;\n\n const [chordGroup, setChordGroup] = useState<G | null>(null);\n\n const groupOrderRef = useRef(groupOrder);\n const toolsRef = useRef(tools);\n const onSelectRef = useRef(onSelect);\n const chordGroupRef = useRef<G | null>(null);\n\n groupOrderRef.current = groupOrder;\n toolsRef.current = tools;\n onSelectRef.current = onSelect;\n // chordGroupRef được sync ngay trong handler (xem `setChord` dưới đây)\n // thay vì ghi từ render body — nếu ghi ở body sẽ bị React batch hoá khi\n // hai event xảy ra trong cùng một act() (event sau đọc giá trị cũ).\n\n const cancel = useCallback(() => {\n chordGroupRef.current = null;\n setChordGroup(null);\n }, []);\n\n useEffect(() => {\n if (!enabled) return;\n\n const setChord = (next: G | null) => {\n chordGroupRef.current = next;\n setChordGroup(next);\n };\n\n const onKey = (e: KeyboardEvent) => {\n if (e.metaKey || e.ctrlKey || e.altKey) return;\n if (isFieldFocused()) return;\n\n const key = e.key;\n const lower = key.length === 1 ? key.toLowerCase() : key;\n\n if (key === 'Escape') {\n if (chordGroupRef.current !== null) {\n e.preventDefault();\n e.stopPropagation();\n setChord(null);\n }\n return;\n }\n\n if (lower.length === 1 && lower >= 'a' && lower <= 'z') {\n const idx = lower.charCodeAt(0) - A_CODE;\n if (idx < groupOrderRef.current.length) {\n e.preventDefault();\n e.stopPropagation();\n setChord(groupOrderRef.current[idx]);\n }\n return;\n }\n\n if (key >= '1' && key <= '9') {\n const active = chordGroupRef.current;\n if (active === null) return;\n const n = key.charCodeAt(0) - '1'.charCodeAt(0); // 0-indexed\n const toolsInGroup = toolsRef.current.filter(\n (t) => t.group === active,\n );\n e.preventDefault();\n e.stopPropagation();\n if (n < toolsInGroup.length) {\n onSelectRef.current(toolsInGroup[n].key);\n }\n setChord(null);\n return;\n }\n };\n\n window.addEventListener('keydown', onKey, { capture: true });\n return () => {\n window.removeEventListener('keydown', onKey, { capture: true });\n };\n }, [enabled]);\n\n return { chordGroup, cancel };\n}\n","'use client';\n\nimport React from 'react';\n\n/**\n * Generic mobile tool drawer dùng chung cho geometry-2d + geometry-3d.\n *\n * Layout:\n * - Header: icon + title + close\n * - Sticky toolbar: chip switches (Trục/Lưới) + icon-actions (Reset, Undo)\n * - Body: section dọc, mỗi section là 1 nhóm tools, grid 3-col card có nhãn\n *\n * Style: soft-modern, emerald accent, khớp các class trong shared/stamp.css.\n */\n\nexport interface MobileChip {\n /** Label hiển thị + aria-label */\n label: string;\n icon: React.ReactNode;\n pressed: boolean;\n onToggle: (next: boolean) => void;\n /** data-testid optional (để test cũ chạy được) */\n testId?: string;\n}\n\nexport interface MobileActionButton {\n label: string;\n icon: React.ReactNode;\n onClick: () => void;\n disabled?: boolean;\n title?: string;\n /** data-testid optional (cho phép test target button cụ thể) */\n testId?: string;\n}\n\nexport interface MobileTool<TKey extends string> {\n key: TKey;\n label: string;\n icon: React.ReactNode;\n}\n\nexport interface MobileToolGroup<TKey extends string, TGroup extends string> {\n group: TGroup;\n groupLabel: string;\n tools: MobileTool<TKey>[];\n}\n\ninterface MobileToolDrawerProps<TKey extends string, TGroup extends string> {\n title: string;\n headerIcon: React.ReactNode;\n chips: MobileChip[];\n actions: MobileActionButton[];\n groups: MobileToolGroup<TKey, TGroup>[];\n activeTool: TKey;\n onToolSelect: (key: TKey) => void;\n drawerOpen: boolean;\n onDrawerClose: () => void;\n isDark?: boolean;\n /** data-testid trên <aside> — giữ để test cũ tìm được panel */\n testId?: string;\n}\n\nexport function MobileToolDrawer<TKey extends string, TGroup extends string>({\n title,\n headerIcon,\n chips,\n actions,\n groups,\n activeTool,\n onToolSelect,\n drawerOpen,\n onDrawerClose,\n isDark,\n testId,\n}: MobileToolDrawerProps<TKey, TGroup>) {\n return (\n <>\n {drawerOpen && (\n <div\n className=\"stamp-drawer-backdrop\"\n onPointerDown={onDrawerClose}\n aria-hidden=\"true\"\n />\n )}\n <aside\n role=\"complementary\"\n aria-label={title}\n aria-hidden={!drawerOpen ? 'true' : undefined}\n data-testid={testId}\n data-stamp-area=\"true\"\n data-mobile-drawer=\"true\"\n data-geo-mobile=\"true\"\n data-drawer-state={drawerOpen ? 'open' : 'closed'}\n className={[\n isDark ? 'theme--dark ' : '',\n 'stamp-drawer-mobile flex flex-col border-r border-slate-200 bg-white shadow-md',\n ].join('')}\n >\n {/* Header */}\n <header className=\"flex items-center justify-between border-b border-slate-200 bg-gradient-to-r from-slate-50 to-white px-4 py-3\">\n <h3 className=\"flex items-center gap-2 text-base font-semibold text-slate-800\">\n <span className=\"inline-flex h-7 w-7 items-center justify-center rounded-lg bg-emerald-50 text-emerald-700\">\n {headerIcon}\n </span>\n {title}\n </h3>\n <button\n type=\"button\"\n onClick={onDrawerClose}\n aria-label=\"Đóng ngăn công cụ\"\n className=\"inline-flex h-9 w-9 items-center justify-center rounded-full text-slate-500 transition hover:bg-slate-100 hover:text-slate-800\"\n >\n <svg width=\"14\" height=\"14\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"2\" strokeLinecap=\"round\" strokeLinejoin=\"round\">\n <line x1=\"6\" y1=\"6\" x2=\"18\" y2=\"18\" />\n <line x1=\"18\" y1=\"6\" x2=\"6\" y2=\"18\" />\n </svg>\n </button>\n </header>\n\n {/* Sticky toolbar: chips + actions */}\n <div className=\"sticky top-0 z-10 flex items-center gap-2 border-b border-slate-200 bg-white/95 px-3 py-2 backdrop-blur\">\n {chips.map((c) => (\n <button\n key={c.label}\n type=\"button\"\n role=\"switch\"\n aria-pressed={c.pressed}\n aria-label={c.label}\n data-testid={c.testId}\n onClick={() => c.onToggle(!c.pressed)}\n className=\"geo-mobile-chip\"\n >\n {c.icon}\n {c.label}\n </button>\n ))}\n {actions.length > 0 && <div className=\"ml-auto flex items-center gap-1\">\n {actions.map((a) => (\n <button\n key={a.label}\n type=\"button\"\n onClick={a.onClick}\n disabled={a.disabled}\n aria-label={a.label}\n title={a.title ?? a.label}\n data-testid={a.testId}\n className=\"inline-flex h-9 w-9 items-center justify-center rounded-full text-slate-600 transition hover:bg-slate-100 hover:text-slate-900 disabled:cursor-not-allowed disabled:text-slate-300 disabled:hover:bg-transparent\"\n >\n {a.icon}\n </button>\n ))}\n </div>}\n </div>\n\n {/* Body: groups xếp dọc */}\n <div\n className=\"min-h-0 flex-1 overflow-y-auto\"\n style={{ paddingBottom: 'calc(0.75rem + env(safe-area-inset-bottom))' }}\n >\n {groups.map((g) => (\n <section key={g.group} className=\"px-3 pt-3 pb-1\">\n <h4 className=\"mb-2 flex items-center gap-2 text-[11px] font-semibold uppercase tracking-wider text-slate-500\">\n <span className=\"h-1 w-1 rounded-full bg-emerald-500\" />\n {g.groupLabel}\n </h4>\n <div className=\"grid grid-cols-3 gap-2\">\n {g.tools.map((t) => {\n const active = activeTool === t.key;\n return (\n <button\n key={t.key}\n type=\"button\"\n aria-label={t.label}\n aria-pressed={active}\n data-tool={t.key}\n onClick={() => {\n onToolSelect(t.key);\n onDrawerClose();\n }}\n className={[\n 'flex flex-col items-center justify-center gap-1.5 rounded-2xl px-2 py-3 transition active:scale-95',\n active\n ? 'geo-mobile-tool-active'\n : 'bg-slate-50 text-slate-700 hover:bg-slate-100',\n ].join(' ')}\n >\n <span className=\"flex h-6 w-6 items-center justify-center\">{t.icon}</span>\n <span className=\"text-center text-[11px] font-medium leading-tight line-clamp-2\">\n {t.label}\n </span>\n </button>\n );\n })}\n </div>\n </section>\n ))}\n </div>\n </aside>\n </>\n );\n}\n"]}
@@ -1,197 +0,0 @@
1
- "use client";
2
- import { paletteFor } from './chunk-HTBLO5JO.mjs';
3
-
4
- // src/stamps/geometry-3d/serialize.ts
5
- function isGeometry3DCustomData(data) {
6
- if (!data || typeof data !== "object") return false;
7
- const d = data;
8
- return d.kind === "geometry3d" && (d.version === 1 || d.version === 2) && typeof d.jsonState === "string";
9
- }
10
- function serializeBoard3D(state) {
11
- return JSON.stringify(state);
12
- }
13
- function parseSerializedBoard3D(json) {
14
- const parsed = JSON.parse(json);
15
- if (!parsed || typeof parsed !== "object") {
16
- throw new Error("parseSerializedBoard3D: not an object");
17
- }
18
- const p = parsed;
19
- if (p.version !== 1 && p.version !== 2) {
20
- throw new Error(`parseSerializedBoard3D: unsupported version ${String(p.version)}`);
21
- }
22
- if (!Array.isArray(p.elements)) {
23
- throw new Error("parseSerializedBoard3D: elements missing");
24
- }
25
- return parsed;
26
- }
27
-
28
- // src/stamps/geometry-3d/editor/theme.ts
29
- function paletteFor2(isDark) {
30
- const base = paletteFor(isDark);
31
- return {
32
- ...base,
33
- view3dBg: isDark ? "#1a1a1a" : "#ffffff",
34
- axisX: "#d63b3b",
35
- axisY: "#2d8a2d",
36
- axisZ: "#2d6dd6"
37
- };
38
- }
39
- var DEFAULT_VIEW3D = {
40
- azimuth: 0.7,
41
- elevation: 0.4,
42
- bbox3D: [-3, -3, -3, 3, 3, 3]
43
- };
44
- var VIEW3D_ATTRS = (isDark) => {
45
- const p = paletteFor2(isDark);
46
- const axisLabel = (color) => ({
47
- strokeColor: color,
48
- fontSize: 14,
49
- offset: [10, 0]
50
- });
51
- return {
52
- az: { slider: { visible: false }, point2: { visible: false } },
53
- el: { slider: { visible: false } },
54
- projection: "central",
55
- // GeoGebra-style: axes pass through origin (0,0,0) instead of bbox border.
56
- axesPosition: "center",
57
- xAxis: {
58
- strokeColor: p.axisX,
59
- strokeWidth: 2,
60
- lastArrow: { type: 2, size: 8 },
61
- name: "x",
62
- withLabel: true,
63
- label: axisLabel(p.axisX)
64
- },
65
- yAxis: {
66
- strokeColor: p.axisY,
67
- strokeWidth: 2,
68
- lastArrow: { type: 2, size: 8 },
69
- name: "y",
70
- withLabel: true,
71
- label: axisLabel(p.axisY)
72
- },
73
- zAxis: {
74
- strokeColor: p.axisZ,
75
- strokeWidth: 2,
76
- lastArrow: { type: 2, size: 8 },
77
- name: "z",
78
- withLabel: true,
79
- label: axisLabel(p.axisZ)
80
- },
81
- // GeoGebra-style: hide ALL bbox wall planes; the XY ground plane is drawn
82
- // explicitly at z=0 via the helper below (so it coincides with Ox/Oy).
83
- xPlaneRear: { visible: false, mesh3d: { visible: false } },
84
- yPlaneRear: { visible: false, mesh3d: { visible: false } },
85
- zPlaneRear: { visible: false, mesh3d: { visible: false } }
86
- };
87
- };
88
- var GROUND_PLANE_ATTRS = (isDark) => ({
89
- fillColor: isDark ? "#2a2a2a" : "#e6e6e6",
90
- fillOpacity: isDark ? 0.5 : 0.55,
91
- strokeColor: isDark ? "#3a3a3a" : "#cfcfcf",
92
- strokeOpacity: 0.7,
93
- strokeWidth: 1,
94
- fixed: true,
95
- highlight: false,
96
- withLabel: false,
97
- layer: 0
98
- });
99
- var GROUND_PLANE_RANGE = [-3, 3];
100
-
101
- // src/stamps/geometry-3d/render.ts
102
- var OUTPUT_WIDTH = 1024;
103
- var OUTPUT_HEIGHT = 768;
104
- async function renderGeometry3DSvgFromState(jsonState) {
105
- const state = parseSerializedBoard3D(jsonState);
106
- const JXG = (await import('jsxgraph')).default;
107
- const div = document.createElement("div");
108
- div.style.cssText = `position:absolute;left:-9999px;top:-9999px;width:${OUTPUT_WIDTH}px;height:${OUTPUT_HEIGHT}px;`;
109
- document.body.appendChild(div);
110
- try {
111
- JXG.Options.text.display = "internal";
112
- const board = JXG.JSXGraph.initBoard(div, {
113
- boundingbox: state.bbox,
114
- keepaspectratio: true,
115
- axis: false,
116
- showCopyright: false,
117
- showNavigation: false,
118
- renderer: "svg"
119
- });
120
- const baseAttrs = VIEW3D_ATTRS(false);
121
- const view = board.create(
122
- "view3d",
123
- [
124
- [-5, -5],
125
- [10, 10],
126
- [
127
- [state.view.bbox3D[0], state.view.bbox3D[3]],
128
- [state.view.bbox3D[1], state.view.bbox3D[4]],
129
- [state.view.bbox3D[2], state.view.bbox3D[5]]
130
- ]
131
- ],
132
- {
133
- ...baseAttrs,
134
- // JSXGraph view3d đọc azimuth/elevation từ az.slider.start (không phải
135
- // az.value). Nếu pass `value` → JSXGraph bỏ qua → render rơi về default
136
- // (1.0 rad / 0.3 rad), không khớp góc user xoay trong editor.
137
- az: { ...baseAttrs.az, slider: { ...baseAttrs.az.slider, start: state.view.azimuth } },
138
- el: { ...baseAttrs.el, slider: { ...baseAttrs.el.slider, start: state.view.elevation } }
139
- }
140
- );
141
- try {
142
- const v = view;
143
- v?.az_slide?.setValue?.(state.view.azimuth);
144
- v?.el_slide?.setValue?.(state.view.elevation);
145
- v?.board?.update?.();
146
- } catch {
147
- }
148
- if (!state.showAxes) {
149
- view.defaultAxes = [];
150
- }
151
- try {
152
- view.create(
153
- "plane3d",
154
- [
155
- [0, 0, 0],
156
- [1, 0, 0],
157
- [0, 1, 0],
158
- GROUND_PLANE_RANGE,
159
- GROUND_PLANE_RANGE
160
- ],
161
- GROUND_PLANE_ATTRS(false)
162
- );
163
- } catch {
164
- }
165
- const idMap = /* @__PURE__ */ new Map();
166
- for (const el of state.elements) {
167
- const parents = el.parents.map(
168
- (p) => typeof p === "string" && p.startsWith("@id:") ? idMap.get(p.slice(4)) : p
169
- );
170
- const obj = view.create(el.type, parents, {
171
- ...el.attributes,
172
- id: el.id,
173
- name: el.label
174
- });
175
- idMap.set(el.id, obj);
176
- }
177
- const svg = div.querySelector("svg");
178
- if (!svg) {
179
- throw new Error("renderGeometry3DSvgFromState: SVG not produced");
180
- }
181
- const clone = svg.cloneNode(true);
182
- clone.setAttribute("width", String(OUTPUT_WIDTH));
183
- clone.setAttribute("height", String(OUTPUT_HEIGHT));
184
- const svgString = new XMLSerializer().serializeToString(clone);
185
- try {
186
- JXG.JSXGraph.freeBoard(board);
187
- } catch {
188
- }
189
- return { svgString, width: OUTPUT_WIDTH, height: OUTPUT_HEIGHT };
190
- } finally {
191
- document.body.removeChild(div);
192
- }
193
- }
194
-
195
- export { DEFAULT_VIEW3D, GROUND_PLANE_ATTRS, GROUND_PLANE_RANGE, VIEW3D_ATTRS, isGeometry3DCustomData, paletteFor2 as paletteFor, parseSerializedBoard3D, renderGeometry3DSvgFromState, serializeBoard3D };
196
- //# sourceMappingURL=chunk-WQOABS6N.mjs.map
197
- //# sourceMappingURL=chunk-WQOABS6N.mjs.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/stamps/geometry-3d/serialize.ts","../src/stamps/geometry-3d/editor/theme.ts","../src/stamps/geometry-3d/render.ts"],"names":["paletteFor"],"mappings":";;;AAWO,SAAS,uBAAuB,IAAA,EAA6C;AAClF,EAAA,IAAI,CAAC,IAAA,IAAQ,OAAO,IAAA,KAAS,UAAU,OAAO,KAAA;AAC9C,EAAA,MAAM,CAAA,GAAI,IAAA;AACV,EAAA,OACE,CAAA,CAAE,IAAA,KAAS,YAAA,KACV,CAAA,CAAE,OAAA,KAAY,CAAA,IAAK,CAAA,CAAE,OAAA,KAAY,CAAA,CAAA,IAClC,OAAO,CAAA,CAAE,SAAA,KAAc,QAAA;AAE3B;AAsCO,SAAS,iBAAiB,KAAA,EAAkC;AACjE,EAAA,OAAO,IAAA,CAAK,UAAU,KAAK,CAAA;AAC7B;AAEO,SAAS,uBAAuB,IAAA,EAAiC;AACtE,EAAA,MAAM,MAAA,GAAS,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC9B,EAAA,IAAI,CAAC,MAAA,IAAU,OAAO,MAAA,KAAW,QAAA,EAAU;AACzC,IAAA,MAAM,IAAI,MAAM,uCAAuC,CAAA;AAAA,EACzD;AACA,EAAA,MAAM,CAAA,GAAI,MAAA;AACV,EAAA,IAAI,CAAA,CAAE,OAAA,KAAY,CAAA,IAAK,CAAA,CAAE,YAAY,CAAA,EAAG;AACtC,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,4CAAA,EAA+C,OAAO,CAAA,CAAE,OAAO,CAAC,CAAA,CAAE,CAAA;AAAA,EACpF;AACA,EAAA,IAAI,CAAC,KAAA,CAAM,OAAA,CAAQ,CAAA,CAAE,QAAQ,CAAA,EAAG;AAC9B,IAAA,MAAM,IAAI,MAAM,0CAA0C,CAAA;AAAA,EAC5D;AACA,EAAA,OAAO,MAAA;AACT;;;AC9DO,SAASA,YAAW,MAAA,EAAgC;AACzD,EAAA,MAAM,IAAA,GAAO,WAAU,MAAM,CAAA;AAC7B,EAAA,OAAO;AAAA,IACL,GAAG,IAAA;AAAA,IACH,QAAA,EAAU,SAAS,SAAA,GAAY,SAAA;AAAA,IAC/B,KAAA,EAAO,SAAA;AAAA,IACP,KAAA,EAAO,SAAA;AAAA,IACP,KAAA,EAAO;AAAA,GACT;AACF;AAEO,IAAM,cAAA,GAIT;AAAA,EACF,OAAA,EAAS,GAAA;AAAA,EACT,SAAA,EAAW,GAAA;AAAA,EACX,QAAQ,CAAC,EAAA,EAAI,IAAI,EAAA,EAAI,CAAA,EAAG,GAAG,CAAC;AAC9B;AAEO,IAAM,YAAA,GAAe,CAAC,MAAA,KAAoB;AAC/C,EAAA,MAAM,CAAA,GAAIA,YAAW,MAAM,CAAA;AAC3B,EAAA,MAAM,SAAA,GAAY,CAAC,KAAA,MAAmB;AAAA,IACpC,WAAA,EAAa,KAAA;AAAA,IACb,QAAA,EAAU,EAAA;AAAA,IACV,MAAA,EAAQ,CAAC,EAAA,EAAI,CAAC;AAAA,GAChB,CAAA;AACA,EAAA,OAAO;AAAA,IACL,EAAA,EAAI,EAAE,MAAA,EAAQ,EAAE,OAAA,EAAS,KAAA,EAAM,EAAG,MAAA,EAAQ,EAAE,OAAA,EAAS,KAAA,EAAM,EAAE;AAAA,IAC7D,IAAI,EAAE,MAAA,EAAQ,EAAE,OAAA,EAAS,OAAM,EAAE;AAAA,IACjC,UAAA,EAAY,SAAA;AAAA;AAAA,IAEZ,YAAA,EAAc,QAAA;AAAA,IACd,KAAA,EAAO;AAAA,MACL,aAAa,CAAA,CAAE,KAAA;AAAA,MACf,WAAA,EAAa,CAAA;AAAA,MACb,SAAA,EAAW,EAAE,IAAA,EAAM,CAAA,EAAG,MAAM,CAAA,EAAE;AAAA,MAC9B,IAAA,EAAM,GAAA;AAAA,MACN,SAAA,EAAW,IAAA;AAAA,MACX,KAAA,EAAO,SAAA,CAAU,CAAA,CAAE,KAAK;AAAA,KAC1B;AAAA,IACA,KAAA,EAAO;AAAA,MACL,aAAa,CAAA,CAAE,KAAA;AAAA,MACf,WAAA,EAAa,CAAA;AAAA,MACb,SAAA,EAAW,EAAE,IAAA,EAAM,CAAA,EAAG,MAAM,CAAA,EAAE;AAAA,MAC9B,IAAA,EAAM,GAAA;AAAA,MACN,SAAA,EAAW,IAAA;AAAA,MACX,KAAA,EAAO,SAAA,CAAU,CAAA,CAAE,KAAK;AAAA,KAC1B;AAAA,IACA,KAAA,EAAO;AAAA,MACL,aAAa,CAAA,CAAE,KAAA;AAAA,MACf,WAAA,EAAa,CAAA;AAAA,MACb,SAAA,EAAW,EAAE,IAAA,EAAM,CAAA,EAAG,MAAM,CAAA,EAAE;AAAA,MAC9B,IAAA,EAAM,GAAA;AAAA,MACN,SAAA,EAAW,IAAA;AAAA,MACX,KAAA,EAAO,SAAA,CAAU,CAAA,CAAE,KAAK;AAAA,KAC1B;AAAA;AAAA;AAAA,IAGA,UAAA,EAAY,EAAE,OAAA,EAAS,KAAA,EAAO,QAAQ,EAAE,OAAA,EAAS,OAAM,EAAE;AAAA,IACzD,UAAA,EAAY,EAAE,OAAA,EAAS,KAAA,EAAO,QAAQ,EAAE,OAAA,EAAS,OAAM,EAAE;AAAA,IACzD,UAAA,EAAY,EAAE,OAAA,EAAS,KAAA,EAAO,QAAQ,EAAE,OAAA,EAAS,OAAM;AAAE,GAC3D;AACF;AAEO,IAAM,kBAAA,GAAqB,CAAC,MAAA,MAAqB;AAAA,EACtD,SAAA,EAAW,SAAS,SAAA,GAAY,SAAA;AAAA,EAChC,WAAA,EAAa,SAAS,GAAA,GAAM,IAAA;AAAA,EAC5B,WAAA,EAAa,SAAS,SAAA,GAAY,SAAA;AAAA,EAClC,aAAA,EAAe,GAAA;AAAA,EACf,WAAA,EAAa,CAAA;AAAA,EACb,KAAA,EAAO,IAAA;AAAA,EACP,SAAA,EAAW,KAAA;AAAA,EACX,SAAA,EAAW,KAAA;AAAA,EACX,KAAA,EAAO;AACT,CAAA;AAGO,IAAM,kBAAA,GAAuC,CAAC,EAAA,EAAI,CAAC;;;AChF1D,IAAM,YAAA,GAAe,IAAA;AACrB,IAAM,aAAA,GAAgB,GAAA;AAKtB,eAAsB,6BACpB,SAAA,EACuB;AACvB,EAAA,MAAM,KAAA,GAAQ,uBAAuB,SAAS,CAAA;AAC9C,EAAA,MAAM,GAAA,GAAA,CAAO,MAAM,OAAO,UAAU,CAAA,EAAG,OAAA;AAEvC,EAAA,MAAM,GAAA,GAAM,QAAA,CAAS,aAAA,CAAc,KAAK,CAAA;AACxC,EAAA,GAAA,CAAI,KAAA,CAAM,OAAA,GAAU,CAAA,iDAAA,EAAoD,YAAY,aAAa,aAAa,CAAA,GAAA,CAAA;AAC9G,EAAA,QAAA,CAAS,IAAA,CAAK,YAAY,GAAG,CAAA;AAE7B,EAAA,IAAI;AACF,IAAA,GAAA,CAAI,OAAA,CAAQ,KAAK,OAAA,GAAU,UAAA;AAE3B,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,QAAA,CAAS,SAAA,CAAU,GAAA,EAAK;AAAA,MACxC,aAAa,KAAA,CAAM,IAAA;AAAA,MACnB,eAAA,EAAiB,IAAA;AAAA,MACjB,IAAA,EAAM,KAAA;AAAA,MACN,aAAA,EAAe,KAAA;AAAA,MACf,cAAA,EAAgB,KAAA;AAAA,MAChB,QAAA,EAAU;AAAA,KACX,CAAA;AAED,IAAA,MAAM,SAAA,GAAY,aAAa,KAAK,CAAA;AACpC,IAAA,MAAM,OAAe,KAAA,CAAM,MAAA;AAAA,MACzB,QAAA;AAAA,MACA;AAAA,QACE,CAAC,IAAI,CAAA,CAAE,CAAA;AAAA,QACP,CAAC,IAAI,EAAE,CAAA;AAAA,QACP;AAAA,UACE,CAAC,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,CAAC,GAAG,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,CAAC,CAAC,CAAA;AAAA,UAC3C,CAAC,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,CAAC,GAAG,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,CAAC,CAAC,CAAA;AAAA,UAC3C,CAAC,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,CAAC,GAAG,KAAA,CAAM,IAAA,CAAK,MAAA,CAAO,CAAC,CAAC;AAAA;AAC7C,OACF;AAAA,MACA;AAAA,QACE,GAAG,SAAA;AAAA;AAAA;AAAA;AAAA,QAIH,EAAA,EAAI,EAAE,GAAG,SAAA,CAAU,IAAI,MAAA,EAAQ,EAAE,GAAG,SAAA,CAAU,GAAG,MAAA,EAAQ,KAAA,EAAO,KAAA,CAAM,IAAA,CAAK,SAAQ,EAAE;AAAA,QACrF,EAAA,EAAI,EAAE,GAAG,SAAA,CAAU,IAAI,MAAA,EAAQ,EAAE,GAAG,SAAA,CAAU,GAAG,MAAA,EAAQ,KAAA,EAAO,KAAA,CAAM,IAAA,CAAK,WAAU;AAAE;AACzF,KACF;AAIA,IAAA,IAAI;AAEF,MAAA,MAAM,CAAA,GAAI,IAAA;AACV,MAAA,CAAA,EAAG,QAAA,EAAU,QAAA,GAAW,KAAA,CAAM,IAAA,CAAK,OAAO,CAAA;AAC1C,MAAA,CAAA,EAAG,QAAA,EAAU,QAAA,GAAW,KAAA,CAAM,IAAA,CAAK,SAAS,CAAA;AAC5C,MAAA,CAAA,EAAG,OAAO,MAAA,IAAS;AAAA,IACrB,CAAA,CAAA,MAAQ;AAAA,IAER;AAEA,IAAA,IAAI,CAAC,MAAM,QAAA,EAAU;AACnB,MAAC,IAAA,CAAqC,cAAc,EAAC;AAAA,IACvD;AAGA,IAAA,IAAI;AACF,MAAC,IAAA,CAAqE,MAAA;AAAA,QACpE,SAAA;AAAA,QACA;AAAA,UACE,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA;AAAA,UACR,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA;AAAA,UACR,CAAC,CAAA,EAAG,CAAA,EAAG,CAAC,CAAA;AAAA,UACR,kBAAA;AAAA,UACA;AAAA,SACF;AAAA,QACA,mBAAmB,KAAK;AAAA,OAC1B;AAAA,IACF,CAAA,CAAA,MAAQ;AAAA,IAER;AAEA,IAAA,MAAM,KAAA,uBAAY,GAAA,EAAoB;AACtC,IAAA,KAAA,MAAW,EAAA,IAAM,MAAM,QAAA,EAAU;AAC/B,MAAA,MAAM,OAAA,GAAU,GAAG,OAAA,CAAQ,GAAA;AAAA,QAAI,CAAC,CAAA,KAC9B,OAAO,CAAA,KAAM,YAAY,CAAA,CAAE,UAAA,CAAW,MAAM,CAAA,GACxC,MAAM,GAAA,CAAI,CAAA,CAAE,KAAA,CAAM,CAAC,CAAC,CAAA,GACpB;AAAA,OACN;AACA,MAAA,MAAM,GAAA,GACJ,IAAA,CACA,MAAA,CAAO,EAAA,CAAG,MAAM,OAAA,EAAS;AAAA,QACzB,GAAG,EAAA,CAAG,UAAA;AAAA,QACN,IAAI,EAAA,CAAG,EAAA;AAAA,QACP,MAAM,EAAA,CAAG;AAAA,OACV,CAAA;AACD,MAAA,KAAA,CAAM,GAAA,CAAI,EAAA,CAAG,EAAA,EAAI,GAAG,CAAA;AAAA,IACtB;AAEA,IAAA,MAAM,GAAA,GAAM,GAAA,CAAI,aAAA,CAAc,KAAK,CAAA;AACnC,IAAA,IAAI,CAAC,GAAA,EAAK;AACR,MAAA,MAAM,IAAI,MAAM,gDAAgD,CAAA;AAAA,IAClE;AACA,IAAA,MAAM,KAAA,GAAQ,GAAA,CAAI,SAAA,CAAU,IAAI,CAAA;AAChC,IAAA,KAAA,CAAM,YAAA,CAAa,OAAA,EAAS,MAAA,CAAO,YAAY,CAAC,CAAA;AAChD,IAAA,KAAA,CAAM,YAAA,CAAa,QAAA,EAAU,MAAA,CAAO,aAAa,CAAC,CAAA;AAClD,IAAA,MAAM,SAAA,GAAY,IAAI,aAAA,EAAc,CAAE,kBAAkB,KAAK,CAAA;AAE7D,IAAA,IAAI;AAEF,MAAA,GAAA,CAAI,QAAA,CAAS,UAAU,KAAY,CAAA;AAAA,IACrC,CAAA,CAAA,MAAQ;AAAA,IAER;AAEA,IAAA,OAAO,EAAE,SAAA,EAAW,KAAA,EAAO,YAAA,EAAc,QAAQ,aAAA,EAAc;AAAA,EACjE,CAAA,SAAE;AACA,IAAA,QAAA,CAAS,IAAA,CAAK,YAAY,GAAG,CAAA;AAAA,EAC/B;AACF","file":"chunk-WQOABS6N.mjs","sourcesContent":["import type { BaseStampCustomData } from '../shared/types';\nimport type { Constraint } from './editor/scene/types';\n\nexport interface Geometry3DCustomData extends BaseStampCustomData {\n kind: 'geometry3d';\n version: 1 | 2;\n jsonState: string;\n svgWidth: number;\n svgHeight: number;\n}\n\nexport function isGeometry3DCustomData(data: unknown): data is Geometry3DCustomData {\n if (!data || typeof data !== 'object') return false;\n const d = data as Partial<Geometry3DCustomData>;\n return (\n d.kind === 'geometry3d' &&\n (d.version === 1 || d.version === 2) &&\n typeof d.jsonState === 'string'\n );\n}\n\nexport type Element3DType =\n | 'point3d'\n | 'line3d'\n | 'plane3d'\n | 'polygon3d'\n | 'sphere3d'\n | 'text3d';\n\nexport interface SerializedElement3D {\n type: Element3DType;\n /**\n * Parents passed to JSXGraph view.create. Either literal values (numbers,\n * strings) or `\"@id:<id>\"` placeholder strings referencing earlier created\n * objects in the log (resolved at deserialize time).\n */\n parents: unknown[];\n attributes: Record<string, unknown>;\n id: string;\n label?: string;\n /** v2 only — present on point3d elements to encode the surface constraint. */\n constraint?: Constraint;\n}\n\nexport interface SerializedBoard3D {\n version: 1 | 2;\n bbox: [number, number, number, number];\n view: {\n azimuth: number;\n elevation: number;\n bbox3D: [number, number, number, number, number, number];\n };\n showAxes: boolean;\n showMesh: boolean;\n elements: SerializedElement3D[];\n}\n\nexport function serializeBoard3D(state: SerializedBoard3D): string {\n return JSON.stringify(state);\n}\n\nexport function parseSerializedBoard3D(json: string): SerializedBoard3D {\n const parsed = JSON.parse(json) as unknown;\n if (!parsed || typeof parsed !== 'object') {\n throw new Error('parseSerializedBoard3D: not an object');\n }\n const p = parsed as Partial<SerializedBoard3D>;\n if (p.version !== 1 && p.version !== 2) {\n throw new Error(`parseSerializedBoard3D: unsupported version ${String(p.version)}`);\n }\n if (!Array.isArray(p.elements)) {\n throw new Error('parseSerializedBoard3D: elements missing');\n }\n return parsed as SerializedBoard3D;\n}\n","import {\n paletteFor as palette2D,\n type GeomPalette,\n} from '../../geometry-2d/editor/theme';\n\nexport type Geom3DPalette = GeomPalette & {\n view3dBg: string;\n axisX: string;\n axisY: string;\n axisZ: string;\n};\n\nexport function paletteFor(isDark: boolean): Geom3DPalette {\n const base = palette2D(isDark);\n return {\n ...base,\n view3dBg: isDark ? '#1a1a1a' : '#ffffff',\n axisX: '#d63b3b',\n axisY: '#2d8a2d',\n axisZ: '#2d6dd6',\n };\n}\n\nexport const DEFAULT_VIEW3D: {\n azimuth: number;\n elevation: number;\n bbox3D: [number, number, number, number, number, number];\n} = {\n azimuth: 0.7,\n elevation: 0.4,\n bbox3D: [-3, -3, -3, 3, 3, 3],\n};\n\nexport const VIEW3D_ATTRS = (isDark: boolean) => {\n const p = paletteFor(isDark);\n const axisLabel = (color: string) => ({\n strokeColor: color,\n fontSize: 14,\n offset: [10, 0] as [number, number],\n });\n return {\n az: { slider: { visible: false }, point2: { visible: false } },\n el: { slider: { visible: false } },\n projection: 'central' as const,\n // GeoGebra-style: axes pass through origin (0,0,0) instead of bbox border.\n axesPosition: 'center' as const,\n xAxis: {\n strokeColor: p.axisX,\n strokeWidth: 2,\n lastArrow: { type: 2, size: 8 },\n name: 'x',\n withLabel: true,\n label: axisLabel(p.axisX),\n },\n yAxis: {\n strokeColor: p.axisY,\n strokeWidth: 2,\n lastArrow: { type: 2, size: 8 },\n name: 'y',\n withLabel: true,\n label: axisLabel(p.axisY),\n },\n zAxis: {\n strokeColor: p.axisZ,\n strokeWidth: 2,\n lastArrow: { type: 2, size: 8 },\n name: 'z',\n withLabel: true,\n label: axisLabel(p.axisZ),\n },\n // GeoGebra-style: hide ALL bbox wall planes; the XY ground plane is drawn\n // explicitly at z=0 via the helper below (so it coincides with Ox/Oy).\n xPlaneRear: { visible: false, mesh3d: { visible: false } },\n yPlaneRear: { visible: false, mesh3d: { visible: false } },\n zPlaneRear: { visible: false, mesh3d: { visible: false } },\n };\n};\n\nexport const GROUND_PLANE_ATTRS = (isDark: boolean) => ({\n fillColor: isDark ? '#2a2a2a' : '#e6e6e6',\n fillOpacity: isDark ? 0.5 : 0.55,\n strokeColor: isDark ? '#3a3a3a' : '#cfcfcf',\n strokeOpacity: 0.7,\n strokeWidth: 1,\n fixed: true,\n highlight: false,\n withLabel: false,\n layer: 0,\n});\n\n/** XY ground plane extent (square around origin in user units). */\nexport const GROUND_PLANE_RANGE: [number, number] = [-3, 3];\n","\"use client\";\n\nimport { parseSerializedBoard3D } from './serialize';\nimport { GROUND_PLANE_ATTRS, GROUND_PLANE_RANGE, VIEW3D_ATTRS } from './editor/theme';\n\nexport interface RenderResult {\n svgString: string;\n width: number;\n height: number;\n}\n\nconst OUTPUT_WIDTH = 1024;\nconst OUTPUT_HEIGHT = 768;\n\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\ntype JxgObj = any;\n\nexport async function renderGeometry3DSvgFromState(\n jsonState: string,\n): Promise<RenderResult> {\n const state = parseSerializedBoard3D(jsonState);\n const JXG = (await import('jsxgraph')).default;\n\n const div = document.createElement('div');\n div.style.cssText = `position:absolute;left:-9999px;top:-9999px;width:${OUTPUT_WIDTH}px;height:${OUTPUT_HEIGHT}px;`;\n document.body.appendChild(div);\n\n try {\n JXG.Options.text.display = 'internal';\n\n const board = JXG.JSXGraph.initBoard(div, {\n boundingbox: state.bbox,\n keepaspectratio: true,\n axis: false,\n showCopyright: false,\n showNavigation: false,\n renderer: 'svg',\n }) as { create: (k: string, p: unknown[], a: unknown) => JxgObj };\n\n const baseAttrs = VIEW3D_ATTRS(false);\n const view: JxgObj = board.create(\n 'view3d',\n [\n [-5, -5],\n [10, 10],\n [\n [state.view.bbox3D[0], state.view.bbox3D[3]],\n [state.view.bbox3D[1], state.view.bbox3D[4]],\n [state.view.bbox3D[2], state.view.bbox3D[5]],\n ],\n ],\n {\n ...baseAttrs,\n // JSXGraph view3d đọc azimuth/elevation từ az.slider.start (không phải\n // az.value). Nếu pass `value` → JSXGraph bỏ qua → render rơi về default\n // (1.0 rad / 0.3 rad), không khớp góc user xoay trong editor.\n az: { ...baseAttrs.az, slider: { ...baseAttrs.az.slider, start: state.view.azimuth } },\n el: { ...baseAttrs.el, slider: { ...baseAttrs.el.slider, start: state.view.elevation } },\n },\n );\n\n // Defensive: setValue post-creation để chắc chắn slider khớp với serialized\n // angle, kể cả khi JSXGraph version khác xử lý `start` khác.\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n const v = view as any;\n v?.az_slide?.setValue?.(state.view.azimuth);\n v?.el_slide?.setValue?.(state.view.elevation);\n v?.board?.update?.();\n } catch {\n /* swallow — older JSXGraph may not expose az_slide on view3d */\n }\n\n if (!state.showAxes) {\n (view as { defaultAxes?: unknown[] }).defaultAxes = [];\n }\n\n // XY ground plane through origin (matches editor's MiniBoard3D).\n try {\n (view as { create: (k: string, p: unknown[], a: unknown) => JxgObj }).create(\n 'plane3d',\n [\n [0, 0, 0],\n [1, 0, 0],\n [0, 1, 0],\n GROUND_PLANE_RANGE,\n GROUND_PLANE_RANGE,\n ],\n GROUND_PLANE_ATTRS(false),\n );\n } catch {\n /* swallow */\n }\n\n const idMap = new Map<string, JxgObj>();\n for (const el of state.elements) {\n const parents = el.parents.map((p) =>\n typeof p === 'string' && p.startsWith('@id:')\n ? idMap.get(p.slice(4))\n : p,\n );\n const obj = (\n view as { create: (k: string, p: unknown[], a: unknown) => JxgObj }\n ).create(el.type, parents, {\n ...el.attributes,\n id: el.id,\n name: el.label,\n });\n idMap.set(el.id, obj);\n }\n\n const svg = div.querySelector('svg');\n if (!svg) {\n throw new Error('renderGeometry3DSvgFromState: SVG not produced');\n }\n const clone = svg.cloneNode(true) as SVGElement;\n clone.setAttribute('width', String(OUTPUT_WIDTH));\n clone.setAttribute('height', String(OUTPUT_HEIGHT));\n const svgString = new XMLSerializer().serializeToString(clone);\n\n try {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n JXG.JSXGraph.freeBoard(board as any);\n } catch {\n /* ignore teardown */\n }\n\n return { svgString, width: OUTPUT_WIDTH, height: OUTPUT_HEIGHT };\n } finally {\n document.body.removeChild(div);\n }\n}\n"]}
@@ -1 +0,0 @@
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,GAA2B;AAAA,EACtC,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,IAAA,IAAQ,CAAC,MAAA,EAAQ,OAAO,IAAA;AAC7B,IAAA,IAAI,CAAC,oBAAA,CAAqB,IAAI,CAAA,EAAG,OAAO,IAAA;AACxC,IAAA,MAAM,SAAA,GAAY,MAAM,0BAAA,CAA2B,IAAA,CAAK,SAAS,CAAA;AACjE,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-YVJP7NRG.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 {\n isGeometryCustomData,\n type GeometryCustomData,\n} from './types';\n\nexport { isGeometryCustomData };\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 = {\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) return null;\n if (!isGeometryCustomData(data)) return null;\n const svgString = await renderGeometrySvgFromState(data.jsonState);\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: GeometryStampHost,\n};\n"]}