@xom11/whiteboard 0.25.0 → 0.28.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/{ExcalidrawWithMenus-WENZRYYE.mjs → ExcalidrawWithMenus-2QPPTXJM.mjs} +3 -2
  2. package/dist/ExcalidrawWithMenus-2QPPTXJM.mjs.map +1 -0
  3. package/dist/ai.d.mts +3035 -108
  4. package/dist/ai.d.ts +3035 -108
  5. package/dist/ai.js +6780 -788
  6. package/dist/ai.js.map +1 -1
  7. package/dist/ai.mjs +5140 -577
  8. package/dist/ai.mjs.map +1 -1
  9. package/dist/catalog.json +5 -5
  10. package/dist/{chunk-NDEZJKNY.mjs → chunk-5JM35CXV.mjs} +4 -4
  11. package/dist/{chunk-NDEZJKNY.mjs.map → chunk-5JM35CXV.mjs.map} +1 -1
  12. package/dist/{chunk-VNCCIV6O.mjs → chunk-AJAHD35N.mjs} +779 -9
  13. package/dist/chunk-AJAHD35N.mjs.map +1 -0
  14. package/dist/{chunk-M42TGYT6.mjs → chunk-BNBOIDO5.mjs} +3 -3
  15. package/dist/{chunk-M42TGYT6.mjs.map → chunk-BNBOIDO5.mjs.map} +1 -1
  16. package/dist/{chunk-ONBCUWVI.mjs → chunk-BU5KLO3P.mjs} +3 -3
  17. package/dist/{chunk-ONBCUWVI.mjs.map → chunk-BU5KLO3P.mjs.map} +1 -1
  18. package/dist/{chunk-CJBLJUWG.mjs → chunk-CXHNVYMD.mjs} +4 -4
  19. package/dist/{chunk-CJBLJUWG.mjs.map → chunk-CXHNVYMD.mjs.map} +1 -1
  20. package/dist/chunk-H22OZYTW.mjs +265 -0
  21. package/dist/chunk-H22OZYTW.mjs.map +1 -0
  22. package/dist/chunk-J5LGTIGS.mjs +10 -0
  23. package/dist/chunk-J5LGTIGS.mjs.map +1 -0
  24. package/dist/{chunk-TB4CL25L.mjs → chunk-OQIQNKPQ.mjs} +206 -66
  25. package/dist/chunk-OQIQNKPQ.mjs.map +1 -0
  26. package/dist/{chunk-SGFJLHHG.mjs → chunk-PPKHCRRE.mjs} +3 -3
  27. package/dist/{chunk-SGFJLHHG.mjs.map → chunk-PPKHCRRE.mjs.map} +1 -1
  28. package/dist/{chunk-YSJOVBCD.mjs → chunk-QCZVFEN4.mjs} +4 -4
  29. package/dist/{chunk-YSJOVBCD.mjs.map → chunk-QCZVFEN4.mjs.map} +1 -1
  30. package/dist/{chunk-ESVPQWHX.mjs → chunk-QRUAEXLR.mjs} +5 -5
  31. package/dist/{chunk-ESVPQWHX.mjs.map → chunk-QRUAEXLR.mjs.map} +1 -1
  32. package/dist/{chunk-AYSFWUPK.mjs → chunk-SZDAS7LK.mjs} +79 -2
  33. package/dist/chunk-SZDAS7LK.mjs.map +1 -0
  34. package/dist/chunk-T3SOHYB2.mjs +851 -0
  35. package/dist/chunk-T3SOHYB2.mjs.map +1 -0
  36. package/dist/{chunk-I24QOHPU.mjs → chunk-V3YJ6JFL.mjs} +3 -3
  37. package/dist/{chunk-I24QOHPU.mjs.map → chunk-V3YJ6JFL.mjs.map} +1 -1
  38. package/dist/{chunk-REIJZDVZ.mjs → chunk-ZTQBUKLJ.mjs} +960 -196
  39. package/dist/chunk-ZTQBUKLJ.mjs.map +1 -0
  40. package/dist/geometry-2d.d.mts +1 -1
  41. package/dist/geometry-2d.d.ts +1 -1
  42. package/dist/geometry-2d.js +5521 -1384
  43. package/dist/geometry-2d.js.map +1 -1
  44. package/dist/geometry-2d.mjs +5 -4
  45. package/dist/geometry-3d.d.mts +1 -1
  46. package/dist/geometry-3d.d.ts +1 -1
  47. package/dist/geometry-3d.js +1351 -252
  48. package/dist/geometry-3d.js.map +1 -1
  49. package/dist/geometry-3d.mjs +4 -3
  50. package/dist/graph-2d.d.mts +1 -1
  51. package/dist/graph-2d.d.ts +1 -1
  52. package/dist/graph-2d.js +1517 -341
  53. package/dist/graph-2d.js.map +1 -1
  54. package/dist/graph-2d.mjs +7 -6
  55. package/dist/handleExtractProblem-C-U5KluK.d.mts +158 -0
  56. package/dist/handleExtractProblem-C-U5KluK.d.ts +158 -0
  57. package/dist/{host-A64ITWVX.mjs → host-2ISGVO7O.mjs} +6 -5
  58. package/dist/host-2ISGVO7O.mjs.map +1 -0
  59. package/dist/{host-L7FMFZUW.mjs → host-4P766V4J.mjs} +1363 -463
  60. package/dist/host-4P766V4J.mjs.map +1 -0
  61. package/dist/{host-QK53UYMD.mjs → host-HOSJHQ5H.mjs} +10 -9
  62. package/dist/host-HOSJHQ5H.mjs.map +1 -0
  63. package/dist/{host-QS2EOTRJ.mjs → host-ZQCDAT6O.mjs} +3 -2
  64. package/dist/host-ZQCDAT6O.mjs.map +1 -0
  65. package/dist/index.d.mts +10 -4
  66. package/dist/index.d.ts +10 -4
  67. package/dist/index.js +5746 -1603
  68. package/dist/index.js.map +1 -1
  69. package/dist/index.mjs +26 -22
  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.mjs +2 -1
  74. package/dist/render-ZX2O2IK7.mjs +10 -0
  75. package/dist/{render-3WTY7NZB.mjs.map → render-ZX2O2IK7.mjs.map} +1 -1
  76. package/dist/serialize-N4G6RFBB.mjs +9 -0
  77. package/dist/{serialize-SRJVKYUG.mjs.map → serialize-N4G6RFBB.mjs.map} +1 -1
  78. package/dist/{types-DWRyCa2m.d.ts → types-BHYC2Fiw.d.mts} +130 -1
  79. package/dist/{types-DWRyCa2m.d.mts → types-BHYC2Fiw.d.ts} +130 -1
  80. package/package.json +10 -1
  81. package/dist/ExcalidrawWithMenus-WENZRYYE.mjs.map +0 -1
  82. package/dist/chunk-AYSFWUPK.mjs.map +0 -1
  83. package/dist/chunk-REIJZDVZ.mjs.map +0 -1
  84. package/dist/chunk-TB4CL25L.mjs.map +0 -1
  85. package/dist/chunk-VNCCIV6O.mjs.map +0 -1
  86. package/dist/chunk-VRHWDZ66.mjs +0 -96
  87. package/dist/chunk-VRHWDZ66.mjs.map +0 -1
  88. package/dist/host-A64ITWVX.mjs.map +0 -1
  89. package/dist/host-L7FMFZUW.mjs.map +0 -1
  90. package/dist/host-QK53UYMD.mjs.map +0 -1
  91. package/dist/host-QS2EOTRJ.mjs.map +0 -1
  92. package/dist/render-3WTY7NZB.mjs +0 -9
  93. package/dist/serialize-SRJVKYUG.mjs +0 -8
@@ -1,13 +1,30 @@
1
1
  "use client";
2
- import { listObjects } from './chunk-REIJZDVZ.mjs';
2
+ import { listObjects } from './chunk-ZTQBUKLJ.mjs';
3
3
  import { createStore } from './chunk-IHUFOV7L.mjs';
4
4
  import { createEmptyState } from './chunk-73Q7ADVL.mjs';
5
5
  import { getKind } from './chunk-B4NJJZFR.mjs';
6
- import * as React2 from 'react';
7
- import React2__default, { createContext, useRef, useReducer, useCallback, useEffect, useMemo, useContext, useState } from 'react';
6
+ import * as React from 'react';
7
+ import React__default, { createContext, useRef, useReducer, useCallback, useEffect, useMemo, useContext, useState } from 'react';
8
8
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
9
9
  import { createPortal } from 'react-dom';
10
10
 
11
+ var FALLBACK_DEFAULT_WIDTH = 240;
12
+ var FALLBACK_MIN_WIDTH = 220;
13
+ var FALLBACK_MAX_WIDTH = 480;
14
+ function clamp(n, min, max) {
15
+ return Math.max(min, Math.min(max, n));
16
+ }
17
+ function readStoredWidth(key, fallback, min, max) {
18
+ if (!key || typeof window === "undefined") return fallback;
19
+ try {
20
+ const raw = window.localStorage.getItem(key);
21
+ if (!raw) return fallback;
22
+ const n = parseInt(raw, 10);
23
+ if (Number.isFinite(n)) return clamp(n, min, max);
24
+ } catch {
25
+ }
26
+ return fallback;
27
+ }
11
28
  function CloseIcon() {
12
29
  return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
13
30
  /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
@@ -15,8 +32,64 @@ function CloseIcon() {
15
32
  ] });
16
33
  }
17
34
  function LeftPanelShell(props) {
18
- const { title, icon, onClose, isDark, tabs, activeTab, onTabChange, testId, children } = props;
35
+ const {
36
+ title,
37
+ icon,
38
+ onClose,
39
+ isDark,
40
+ tabs,
41
+ activeTab,
42
+ onTabChange,
43
+ testId,
44
+ resizable,
45
+ widthStorageKey,
46
+ defaultWidth,
47
+ minWidth,
48
+ maxWidth,
49
+ children
50
+ } = props;
19
51
  const showTabs = !!tabs && tabs.length >= 2;
52
+ const min = minWidth ?? FALLBACK_MIN_WIDTH;
53
+ const max = maxWidth ?? FALLBACK_MAX_WIDTH;
54
+ const initial = clamp(defaultWidth ?? FALLBACK_DEFAULT_WIDTH, min, max);
55
+ const [width, setWidth] = React.useState(
56
+ () => resizable ? readStoredWidth(widthStorageKey, initial, min, max) : initial
57
+ );
58
+ const widthRef = React.useRef(width);
59
+ widthRef.current = width;
60
+ React.useEffect(() => {
61
+ if (!resizable || !widthStorageKey || typeof window === "undefined") return;
62
+ try {
63
+ window.localStorage.setItem(widthStorageKey, String(width));
64
+ } catch {
65
+ }
66
+ }, [resizable, widthStorageKey, width]);
67
+ const onResizeStart = React.useCallback(
68
+ (e) => {
69
+ if (!resizable) return;
70
+ e.preventDefault();
71
+ const startX = e.clientX;
72
+ const startW = widthRef.current;
73
+ const onMove = (ev) => {
74
+ setWidth(clamp(startW + (ev.clientX - startX), min, max));
75
+ };
76
+ const onUp = () => {
77
+ window.removeEventListener("mousemove", onMove);
78
+ window.removeEventListener("mouseup", onUp);
79
+ document.body.style.cursor = "";
80
+ document.body.style.userSelect = "";
81
+ };
82
+ window.addEventListener("mousemove", onMove);
83
+ window.addEventListener("mouseup", onUp);
84
+ document.body.style.cursor = "ew-resize";
85
+ document.body.style.userSelect = "none";
86
+ },
87
+ [resizable, min, max]
88
+ );
89
+ const onResizeDoubleClick = React.useCallback(() => {
90
+ if (!resizable) return;
91
+ setWidth(initial);
92
+ }, [resizable, initial]);
20
93
  return /* @__PURE__ */ jsxs(
21
94
  "aside",
22
95
  {
@@ -24,10 +97,12 @@ function LeftPanelShell(props) {
24
97
  "aria-label": title,
25
98
  "data-testid": testId ?? "left-panel",
26
99
  "data-stamp-area": "true",
100
+ style: resizable ? { width: `${width}px` } : void 0,
27
101
  className: [
28
102
  isDark ? "theme--dark " : "",
29
- "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"
30
- ].join(""),
103
+ "absolute left-0 top-0 z-30 flex h-full flex-col border-r border-slate-200 bg-white shadow-md animate-in slide-in-from-left duration-200",
104
+ resizable ? "" : "w-60"
105
+ ].join(" "),
31
106
  children: [
32
107
  /* @__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: [
33
108
  /* @__PURE__ */ jsxs("h3", { className: "flex items-center gap-2 text-sm font-semibold text-slate-800", children: [
@@ -62,6 +137,20 @@ function LeftPanelShell(props) {
62
137
  className: "min-h-0 flex-1 overflow-y-auto p-3 space-y-3",
63
138
  children
64
139
  }
140
+ ),
141
+ resizable && /* @__PURE__ */ jsx(
142
+ "div",
143
+ {
144
+ role: "separator",
145
+ "aria-orientation": "vertical",
146
+ "aria-label": "K\xE9o \u0111\u1EC3 \u0111\u1ED5i r\u1ED9ng panel",
147
+ "data-testid": "left-panel-resizer",
148
+ onMouseDown: onResizeStart,
149
+ onDoubleClick: onResizeDoubleClick,
150
+ className: "group absolute right-0 top-0 z-40 h-full w-1.5 -mr-0.5 cursor-ew-resize select-none",
151
+ title: "K\xE9o \u0111\u1EC3 \u0111\u1ED5i r\u1ED9ng (double-click \u0111\u1EC3 reset)",
152
+ children: /* @__PURE__ */ jsx("div", { className: "pointer-events-none absolute inset-y-0 right-0 w-px bg-slate-200 transition group-hover:bg-emerald-400 group-hover:w-0.5 group-active:bg-emerald-500 group-active:w-0.5" })
153
+ }
65
154
  )
66
155
  ]
67
156
  }
@@ -134,7 +223,7 @@ function getKindUiMeta(kind) {
134
223
  }
135
224
  function ObjectRowMenu(props) {
136
225
  const { locked, onToggleLocked, onRename, onChangeColor, onDelete } = props;
137
- const [open, setOpen] = React2.useState(false);
226
+ const [open, setOpen] = React.useState(false);
138
227
  return /* @__PURE__ */ jsxs("div", { className: "relative inline-block", children: [
139
228
  /* @__PURE__ */ jsx(
140
229
  "button",
@@ -274,11 +363,11 @@ function ObjectRow(props) {
274
363
  }
275
364
  function ObjectListPanel(props) {
276
365
  const { store, selectedId, onSelect, renderRow } = props;
277
- const subscribe = React2.useCallback(
366
+ const subscribe = React.useCallback(
278
367
  (cb) => store.subscribe(() => cb()),
279
368
  [store]
280
369
  );
281
- const state = React2.useSyncExternalStore(subscribe, store.getState, store.getState);
370
+ const state = React.useSyncExternalStore(subscribe, store.getState, store.getState);
282
371
  const objects = listObjects(state);
283
372
  function handleSelect(id) {
284
373
  onSelect?.(id === selectedId ? null : id);
@@ -309,7 +398,7 @@ function ObjectListPanel(props) {
309
398
  if (renderRow) {
310
399
  const custom = renderRow(obj, { selected, onClick });
311
400
  if (custom != null) {
312
- return /* @__PURE__ */ jsx(React2.Fragment, { children: custom }, obj.id);
401
+ return /* @__PURE__ */ jsx(React.Fragment, { children: custom }, obj.id);
313
402
  }
314
403
  }
315
404
  return /* @__PURE__ */ jsx(
@@ -437,24 +526,115 @@ function useToolHoverTooltip() {
437
526
  }, []);
438
527
  return { hover, portalReady, showHover, hideHover };
439
528
  }
529
+ function normalize(s) {
530
+ return s.toLowerCase().normalize("NFD").replace(/[̀-ͯ]/g, "").replace(/đ/g, "d").replace(/Đ/g, "d");
531
+ }
532
+ function SearchIcon() {
533
+ return /* @__PURE__ */ jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.8", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
534
+ /* @__PURE__ */ jsx("circle", { cx: "11", cy: "11", r: "7" }),
535
+ /* @__PURE__ */ jsx("line", { x1: "20", y1: "20", x2: "16.5", y2: "16.5" })
536
+ ] });
537
+ }
538
+ function ClearIcon() {
539
+ return /* @__PURE__ */ jsxs("svg", { width: "12", height: "12", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", "aria-hidden": "true", children: [
540
+ /* @__PURE__ */ jsx("line", { x1: "6", y1: "6", x2: "18", y2: "18" }),
541
+ /* @__PURE__ */ jsx("line", { x1: "18", y1: "6", x2: "6", y2: "18" })
542
+ ] });
543
+ }
544
+ function ToolResultList(props) {
545
+ const { tools, activeTool, onToolChange } = props;
546
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-col gap-0.5", "data-testid": "tool-result-list", children: tools.map((t) => {
547
+ const active = activeTool === t.key;
548
+ return /* @__PURE__ */ jsxs(
549
+ "button",
550
+ {
551
+ type: "button",
552
+ "data-tool": t.key,
553
+ "aria-label": t.label,
554
+ "aria-pressed": active,
555
+ onClick: () => onToolChange(t.key),
556
+ className: [
557
+ "flex items-center gap-2 rounded-md px-2 py-1.5 text-left transition",
558
+ active ? "bg-emerald-600 text-white" : "text-slate-700 hover:bg-slate-100"
559
+ ].join(" "),
560
+ children: [
561
+ /* @__PURE__ */ jsx("span", { className: "flex h-6 w-6 shrink-0 items-center justify-center", children: t.icon }),
562
+ /* @__PURE__ */ jsxs("span", { className: "min-w-0", children: [
563
+ /* @__PURE__ */ jsx("span", { className: "block truncate text-[12px] font-medium leading-tight", children: t.label }),
564
+ t.hint && /* @__PURE__ */ jsx("span", { className: ["block truncate text-[10px] leading-tight", active ? "text-emerald-50" : "text-slate-400"].join(" "), children: t.hint })
565
+ ] })
566
+ ]
567
+ },
568
+ t.key
569
+ );
570
+ }) });
571
+ }
440
572
  function ToolGrid(props) {
441
573
  const { tools, groupOrder, groupLabels, activeTool, onToolChange, chord } = props;
442
574
  const { hover, portalReady, showHover, hideHover } = useToolHoverTooltip();
575
+ const [query, setQuery] = useState("");
576
+ const normalizedQuery = useMemo(() => normalize(query.trim()), [query]);
577
+ const filteredTools = useMemo(() => {
578
+ if (!normalizedQuery) return tools;
579
+ return tools.filter((t) => {
580
+ if (normalize(t.label).includes(normalizedQuery)) return true;
581
+ if (t.hint && normalize(t.hint).includes(normalizedQuery)) return true;
582
+ return false;
583
+ });
584
+ }, [tools, normalizedQuery]);
443
585
  const grouped = useMemo(() => {
444
586
  var _a;
445
587
  const acc = {};
446
- for (const t of tools) {
588
+ for (const t of filteredTools) {
447
589
  (acc[_a = t.group] ?? (acc[_a] = [])).push(t);
448
590
  }
449
591
  return acc;
450
- }, [tools]);
592
+ }, [filteredTools]);
451
593
  const groupKeys = useMemo(
452
- () => groupOrder.filter((g) => grouped[g]),
594
+ () => groupOrder.filter((g) => grouped[g] && grouped[g].length > 0),
453
595
  [grouped, groupOrder]
454
596
  );
455
- const activeGroupTools = chord?.activeGroup ? grouped[chord.activeGroup] ?? null : null;
597
+ const noMatch = normalizedQuery !== "" && groupKeys.length === 0;
456
598
  return /* @__PURE__ */ jsxs(Fragment, { children: [
457
- groupKeys.map((group) => {
599
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
600
+ /* @__PURE__ */ jsx("span", { className: "pointer-events-none absolute left-2 top-1/2 -translate-y-1/2 text-slate-400", children: /* @__PURE__ */ jsx(SearchIcon, {}) }),
601
+ /* @__PURE__ */ jsx(
602
+ "input",
603
+ {
604
+ type: "search",
605
+ value: query,
606
+ onChange: (e) => setQuery(e.target.value),
607
+ placeholder: "T\xECm c\xF4ng c\u1EE5\u2026",
608
+ "aria-label": "T\xECm c\xF4ng c\u1EE5",
609
+ "data-testid": "tool-search-input",
610
+ className: "w-full rounded-md border border-slate-200 bg-slate-50 py-1.5 pl-7 pr-7 text-[12px] text-slate-800 placeholder:text-slate-400 focus:border-emerald-400 focus:bg-white focus:outline-none focus:ring-1 focus:ring-emerald-300"
611
+ }
612
+ ),
613
+ query && /* @__PURE__ */ jsx(
614
+ "button",
615
+ {
616
+ type: "button",
617
+ onClick: () => setQuery(""),
618
+ "aria-label": "Xo\xE1 t\xECm ki\u1EBFm",
619
+ "data-testid": "tool-search-clear",
620
+ className: "absolute right-1.5 top-1/2 -translate-y-1/2 rounded p-0.5 text-slate-400 transition hover:bg-slate-200 hover:text-slate-700",
621
+ children: /* @__PURE__ */ jsx(ClearIcon, {})
622
+ }
623
+ )
624
+ ] }),
625
+ noMatch && /* @__PURE__ */ jsxs(
626
+ "div",
627
+ {
628
+ "data-testid": "tool-search-empty",
629
+ className: "rounded-md border border-dashed border-slate-200 bg-slate-50 px-3 py-4 text-center text-[11px] text-slate-500",
630
+ children: [
631
+ "Kh\xF4ng c\xF3 c\xF4ng c\u1EE5 n\xE0o kh\u1EDBp \u201C",
632
+ query.trim(),
633
+ "\u201D."
634
+ ]
635
+ }
636
+ ),
637
+ normalizedQuery !== "" && !noMatch ? /* @__PURE__ */ jsx(ToolResultList, { tools: filteredTools, activeTool, onToolChange }) : groupKeys.map((group) => {
458
638
  const isChordActive = chord?.activeGroup === group;
459
639
  const dimmed = chord?.activeGroup != null && !isChordActive;
460
640
  return /* @__PURE__ */ jsxs(
@@ -468,23 +648,10 @@ function ToolGrid(props) {
468
648
  dimmed ? "opacity-55" : "opacity-100"
469
649
  ].join(" "),
470
650
  children: [
471
- /* @__PURE__ */ jsxs("h4", { className: "mb-1.5 flex items-center justify-between text-[10px] font-semibold uppercase tracking-wider text-slate-500", children: [
472
- /* @__PURE__ */ jsx("span", { children: groupLabels[group] }),
473
- chord && /* @__PURE__ */ jsx(
474
- "span",
475
- {
476
- "data-testid": `chord-letter-${group}`,
477
- className: [
478
- "font-mono text-[10px] leading-none transition",
479
- isChordActive ? "text-emerald-700 font-bold" : "text-slate-400"
480
- ].join(" "),
481
- children: chord.letterForGroup(group)
482
- }
483
- )
484
- ] }),
485
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-4 gap-1", children: grouped[group].map((t, i) => {
651
+ /* @__PURE__ */ jsx("h4", { className: "mb-1.5 text-[10px] font-semibold uppercase tracking-wider text-slate-500", children: groupLabels[group] }),
652
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-4 gap-1", children: grouped[group].map((t) => {
486
653
  const active = activeTool === t.key;
487
- return /* @__PURE__ */ jsxs(
654
+ return /* @__PURE__ */ jsx(
488
655
  "button",
489
656
  {
490
657
  type: "button",
@@ -501,20 +668,7 @@ function ToolGrid(props) {
501
668
  "relative flex h-10 items-center justify-center rounded-md transition",
502
669
  active ? "bg-emerald-600 text-white shadow-sm" : "text-slate-700 hover:bg-slate-100 hover:text-slate-900"
503
670
  ].join(" "),
504
- children: [
505
- t.icon,
506
- chord && /* @__PURE__ */ jsx(
507
- "span",
508
- {
509
- "data-testid": `chord-num-${t.key}`,
510
- className: [
511
- "pointer-events-none absolute bottom-0 right-0.5 font-mono text-[9px] leading-none transition",
512
- active ? "text-white/70" : isChordActive ? "text-emerald-700 font-bold" : "text-slate-400"
513
- ].join(" "),
514
- children: i + 1
515
- }
516
- )
517
- ]
671
+ children: t.icon
518
672
  },
519
673
  t.key
520
674
  );
@@ -524,22 +678,6 @@ function ToolGrid(props) {
524
678
  group
525
679
  );
526
680
  }),
527
- chord?.activeGroup && activeGroupTools && /* @__PURE__ */ jsxs(
528
- "div",
529
- {
530
- "data-testid": "chord-hint",
531
- className: "mt-1 rounded border border-emerald-200 bg-emerald-50/60 px-2 py-1 text-[11px] leading-snug text-slate-600",
532
- children: [
533
- /* @__PURE__ */ jsx("span", { className: "font-mono font-semibold text-emerald-700", children: chord.letterForGroup(chord.activeGroup) }),
534
- /* @__PURE__ */ jsx("span", { className: "mx-1 text-slate-400", children: "\u2192" }),
535
- activeGroupTools.map((t, i) => /* @__PURE__ */ jsxs("span", { className: "mr-2 inline-block", children: [
536
- /* @__PURE__ */ jsx("span", { className: "font-mono font-semibold text-emerald-700", children: i + 1 }),
537
- /* @__PURE__ */ jsx("span", { className: "ml-1", children: t.label })
538
- ] }, t.key)),
539
- /* @__PURE__ */ jsx("span", { className: "text-slate-400", children: "Esc hu\u1EF7" })
540
- ]
541
- }
542
- ),
543
681
  portalReady && hover && typeof document !== "undefined" ? createPortal(
544
682
  /* @__PURE__ */ jsxs(
545
683
  "div",
@@ -600,6 +738,8 @@ function StampLeftPanelDesktop(props) {
600
738
  tabs: tabSpecs,
601
739
  activeTab: hasObjects ? tab : void 0,
602
740
  onTabChange: hasObjects ? setTab : void 0,
741
+ resizable: true,
742
+ widthStorageKey: "xom11.stamp-left-panel.width",
603
743
  children: !hasObjects || tab === "tools" ? /* @__PURE__ */ jsxs(Fragment, { children: [
604
744
  /* @__PURE__ */ jsx(AxisGridSection, { view, history }),
605
745
  /* @__PURE__ */ jsx(
@@ -652,9 +792,9 @@ function MobileToolDrawer({
652
792
  testId,
653
793
  objectsTab
654
794
  }) {
655
- const [mobileTab, setMobileTab] = React2__default.useState("tools");
656
- const prevOpenRef = React2__default.useRef(drawerOpen);
657
- React2__default.useEffect(() => {
795
+ const [mobileTab, setMobileTab] = React__default.useState("tools");
796
+ const prevOpenRef = React__default.useRef(drawerOpen);
797
+ React__default.useEffect(() => {
658
798
  if (!prevOpenRef.current && drawerOpen) setMobileTab("tools");
659
799
  prevOpenRef.current = drawerOpen;
660
800
  }, [drawerOpen]);
@@ -1165,5 +1305,5 @@ async function initJxgBoard(target, config) {
1165
1305
  }
1166
1306
 
1167
1307
  export { ObjectRow, STAMP_PANEL_DESKTOP, StampLeftPanel, ToastHost, ToastProvider, attachJxgWheelZoom, initJxgBoard, safeJsx, useStampStore, useToast };
1168
- //# sourceMappingURL=chunk-TB4CL25L.mjs.map
1169
- //# sourceMappingURL=chunk-TB4CL25L.mjs.map
1308
+ //# sourceMappingURL=chunk-OQIQNKPQ.mjs.map
1309
+ //# sourceMappingURL=chunk-OQIQNKPQ.mjs.map