gridsmith-ui 0.4.0 → 0.6.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 (58) hide show
  1. package/README.md +63 -3
  2. package/dist/charts.d.ts +246 -0
  3. package/dist/charts.js +1028 -0
  4. package/dist/charts.js.map +1 -0
  5. package/dist/chunk-4SDKQDYV.js +93 -0
  6. package/dist/chunk-4SDKQDYV.js.map +1 -0
  7. package/dist/chunk-CHNY75QI.js +278 -0
  8. package/dist/chunk-CHNY75QI.js.map +1 -0
  9. package/dist/chunk-DPF6776I.js +36 -0
  10. package/dist/chunk-DPF6776I.js.map +1 -0
  11. package/dist/{chunk-5XIPPVH7.js → chunk-IUBWNDUK.js} +15 -4
  12. package/dist/chunk-IUBWNDUK.js.map +1 -0
  13. package/dist/chunk-OUYJXVLQ.js +41 -0
  14. package/dist/chunk-OUYJXVLQ.js.map +1 -0
  15. package/dist/{chunk-6TMNEBBJ.js → chunk-XUBVMRIE.js} +44 -35
  16. package/dist/chunk-XUBVMRIE.js.map +1 -0
  17. package/dist/data-grid.d.ts +27 -0
  18. package/dist/data-grid.js +293 -0
  19. package/dist/data-grid.js.map +1 -0
  20. package/dist/editor.d.ts +18 -0
  21. package/dist/editor.js +632 -0
  22. package/dist/editor.js.map +1 -0
  23. package/dist/{feather-432JQGUB.js → feather-V32UNIQL.js} +2 -3
  24. package/dist/feather-V32UNIQL.js.map +1 -0
  25. package/dist/{heroicons-CCKJHFZY.js → heroicons-CA5OQ76O.js} +2 -3
  26. package/dist/heroicons-CA5OQ76O.js.map +1 -0
  27. package/dist/icons.js +1 -2
  28. package/dist/index.d.ts +430 -133
  29. package/dist/index.js +10510 -98031
  30. package/dist/index.js.map +1 -1
  31. package/dist/kanban.d.ts +25 -0
  32. package/dist/kanban.js +224 -0
  33. package/dist/kanban.js.map +1 -0
  34. package/dist/{lucide-Z74WVRAK.js → lucide-AILUUOAJ.js} +2 -3
  35. package/dist/lucide-AILUUOAJ.js.map +1 -0
  36. package/dist/{phosphor-DYUCR5S2.js → phosphor-A4JOBGMT.js} +2 -3
  37. package/dist/phosphor-A4JOBGMT.js.map +1 -0
  38. package/dist/resizable.d.ts +39 -0
  39. package/dist/resizable.js +89 -0
  40. package/dist/resizable.js.map +1 -0
  41. package/dist/roots.css +46 -11
  42. package/dist/{tabler-GRXDPAHR.js → tabler-TVOR6AFM.js} +2 -3
  43. package/dist/tabler-TVOR6AFM.js.map +1 -0
  44. package/dist/use-breakpoint-Cn51IubK.d.ts +30 -0
  45. package/dist/utils.js +1 -2
  46. package/dist/virtualized.d.ts +16 -0
  47. package/dist/virtualized.js +55 -0
  48. package/dist/virtualized.js.map +1 -0
  49. package/package.json +144 -4
  50. package/dist/chunk-5WRI5ZAA.js +0 -29
  51. package/dist/chunk-5WRI5ZAA.js.map +0 -1
  52. package/dist/chunk-5XIPPVH7.js.map +0 -1
  53. package/dist/chunk-6TMNEBBJ.js.map +0 -1
  54. package/dist/feather-432JQGUB.js.map +0 -1
  55. package/dist/heroicons-CCKJHFZY.js.map +0 -1
  56. package/dist/lucide-Z74WVRAK.js.map +0 -1
  57. package/dist/phosphor-DYUCR5S2.js.map +0 -1
  58. package/dist/tabler-GRXDPAHR.js.map +0 -1
package/dist/editor.js ADDED
@@ -0,0 +1,632 @@
1
+ import { useBreakpoint } from './chunk-DPF6776I.js';
2
+ import { Icon } from './chunk-XUBVMRIE.js';
3
+ import { cn } from './chunk-IUBWNDUK.js';
4
+ import { useState, useEffect, useRef, useCallback } from 'react';
5
+ import { useEditor, EditorContent } from '@tiptap/react';
6
+ import StarterKit from '@tiptap/starter-kit';
7
+ import Link from '@tiptap/extension-link';
8
+ import Image from '@tiptap/extension-image';
9
+ import { Table } from '@tiptap/extension-table';
10
+ import { TableRow } from '@tiptap/extension-table-row';
11
+ import { TableCell } from '@tiptap/extension-table-cell';
12
+ import { TableHeader } from '@tiptap/extension-table-header';
13
+ import Placeholder from '@tiptap/extension-placeholder';
14
+ import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
15
+ import Underline from '@tiptap/extension-underline';
16
+ import { TextStyle, FontSize } from '@tiptap/extension-text-style';
17
+ import Color from '@tiptap/extension-color';
18
+ import Highlight from '@tiptap/extension-highlight';
19
+ import TextAlign from '@tiptap/extension-text-align';
20
+ import TaskList from '@tiptap/extension-task-list';
21
+ import TaskItem from '@tiptap/extension-task-item';
22
+ import Subscript from '@tiptap/extension-subscript';
23
+ import Superscript from '@tiptap/extension-superscript';
24
+ import FontFamily from '@tiptap/extension-font-family';
25
+ import { createLowlight, all } from 'lowlight';
26
+ import { jsxs, jsx } from 'react/jsx-runtime';
27
+
28
+ var lowlight = createLowlight(all);
29
+ var DEFAULT_TOOLBAR = [
30
+ "blockType",
31
+ "fontFamily",
32
+ "textSize",
33
+ "formatting",
34
+ "colors",
35
+ "alignment",
36
+ "lists",
37
+ "blocks",
38
+ "insert",
39
+ "extras",
40
+ "history"
41
+ ];
42
+ var MINIMAL_TOOLBAR = [
43
+ "formatting",
44
+ "lists",
45
+ "insert",
46
+ "history"
47
+ ];
48
+ function RichTextEditor({
49
+ content = "",
50
+ placeholder = "Start writing...",
51
+ onChange,
52
+ editable = true,
53
+ toolbar = "full",
54
+ className,
55
+ ref,
56
+ label = "Rich text editor"
57
+ }) {
58
+ const resolvedToolbar = toolbar === "full" ? DEFAULT_TOOLBAR : toolbar === "minimal" ? MINIMAL_TOOLBAR : toolbar;
59
+ const editor = useEditor({
60
+ extensions: [
61
+ StarterKit.configure({ codeBlock: false }),
62
+ Link.configure({ openOnClick: false, HTMLAttributes: { class: "rte-link" } }),
63
+ Image.configure({ inline: false, allowBase64: true }),
64
+ CodeBlockLowlight.configure({ lowlight }),
65
+ Table.configure({ resizable: false }),
66
+ TableRow,
67
+ TableCell,
68
+ TableHeader,
69
+ Placeholder.configure({ placeholder }),
70
+ Underline,
71
+ TextStyle,
72
+ FontSize,
73
+ Color,
74
+ Highlight.configure({ multicolor: true }),
75
+ TextAlign.configure({ types: ["heading", "paragraph"] }),
76
+ TaskList,
77
+ TaskItem.configure({ nested: true }),
78
+ Subscript,
79
+ Superscript,
80
+ FontFamily
81
+ ],
82
+ content,
83
+ editable,
84
+ // Setting attributes replaces tiptap's defaults, so restore role="textbox" alongside the name.
85
+ editorProps: { attributes: { role: "textbox", "aria-label": label, "aria-multiline": "true" } },
86
+ onUpdate: ({ editor: e }) => onChange?.(e.getHTML())
87
+ });
88
+ if (!editor) return null;
89
+ return /* @__PURE__ */ jsxs(
90
+ "div",
91
+ {
92
+ ref,
93
+ className: cn(
94
+ "flex flex-col rounded-[var(--ds-radius-md)] overflow-hidden",
95
+ "border-[length:var(--ds-border-width)] border-[var(--ds-border-default)]",
96
+ "bg-[var(--ds-bg-surface)]",
97
+ className
98
+ ),
99
+ children: [
100
+ editable && /* @__PURE__ */ jsx(Toolbar, { editor, groups: resolvedToolbar }),
101
+ /* @__PURE__ */ jsx(
102
+ EditorContent,
103
+ {
104
+ editor,
105
+ className: cn(
106
+ "rte-content",
107
+ "flex-1 overflow-y-auto px-ds-4 py-ds-3",
108
+ "text-sm text-[var(--ds-text-primary)]",
109
+ "focus-within:outline-none"
110
+ )
111
+ }
112
+ )
113
+ ]
114
+ }
115
+ );
116
+ }
117
+ var TEXT_COLORS = [
118
+ { label: "Default", value: "" },
119
+ { label: "Red", value: "#ef4444" },
120
+ { label: "Orange", value: "#f97316" },
121
+ { label: "Yellow", value: "#eab308" },
122
+ { label: "Green", value: "#22c55e" },
123
+ { label: "Blue", value: "#3b82f6" },
124
+ { label: "Purple", value: "#a855f7" },
125
+ { label: "Pink", value: "#ec4899" }
126
+ ];
127
+ var HIGHLIGHT_COLORS = [
128
+ { label: "None", value: "" },
129
+ { label: "Yellow", value: "#fef08a" },
130
+ { label: "Green", value: "#bbf7d0" },
131
+ { label: "Blue", value: "#bfdbfe" },
132
+ { label: "Pink", value: "#fbcfe8" },
133
+ { label: "Orange", value: "#fed7aa" },
134
+ { label: "Purple", value: "#e9d5ff" }
135
+ ];
136
+ var FONT_FAMILIES = [
137
+ { label: "Default", value: "" },
138
+ { label: "Sans", value: "var(--ds-font-sans)" },
139
+ { label: "Mono", value: "var(--ds-font-mono)" },
140
+ { label: "Display", value: "var(--ds-font-display)" },
141
+ { label: "Serif", value: "Georgia, serif" }
142
+ ];
143
+ var TEXT_SIZES = [
144
+ { label: "Small", value: "0.85em" },
145
+ { label: "Normal", value: "" },
146
+ { label: "Large", value: "1.2em" },
147
+ { label: "Huge", value: "1.5em" }
148
+ ];
149
+ function Toolbar({ editor, groups }) {
150
+ const [, forceUpdate] = useState(0);
151
+ const isMobile = useBreakpoint("sm");
152
+ useEffect(() => {
153
+ const handler = () => forceUpdate((n) => n + 1);
154
+ editor.on("selectionUpdate", handler);
155
+ editor.on("transaction", handler);
156
+ return () => {
157
+ editor.off("selectionUpdate", handler);
158
+ editor.off("transaction", handler);
159
+ };
160
+ }, [editor]);
161
+ const inTable = editor.isActive("table");
162
+ const has = (g) => groups.includes(g);
163
+ const groupElements = [];
164
+ if (has("blockType")) groupElements.push(/* @__PURE__ */ jsx(BlockTypeSelect, { editor }, "blockType"));
165
+ if (has("fontFamily")) groupElements.push(/* @__PURE__ */ jsx(FontFamilySelect, { editor }, "fontFamily"));
166
+ if (has("textSize")) groupElements.push(/* @__PURE__ */ jsx(TextSizeControls, { editor }, "textSize"));
167
+ if (has("formatting")) {
168
+ groupElements.push(
169
+ /* @__PURE__ */ jsxs(ToolGroup, { children: [
170
+ /* @__PURE__ */ jsx(ToolBtn, { active: editor.isActive("bold"), onClick: () => editor.chain().focus().toggleBold().run(), title: "Bold (Ctrl+B)", children: /* @__PURE__ */ jsx(Icon, { name: "bold", size: 16 }) }),
171
+ /* @__PURE__ */ jsx(ToolBtn, { active: editor.isActive("italic"), onClick: () => editor.chain().focus().toggleItalic().run(), title: "Italic (Ctrl+I)", children: /* @__PURE__ */ jsx(Icon, { name: "italic", size: 16 }) }),
172
+ /* @__PURE__ */ jsx(ToolBtn, { active: editor.isActive("underline"), onClick: () => editor.chain().focus().toggleUnderline().run(), title: "Underline (Ctrl+U)", children: /* @__PURE__ */ jsx(Icon, { name: "underline", size: 16 }) }),
173
+ /* @__PURE__ */ jsx(ToolBtn, { active: editor.isActive("strike"), onClick: () => editor.chain().focus().toggleStrike().run(), title: "Strikethrough", children: /* @__PURE__ */ jsx(Icon, { name: "strikethrough", size: 16 }) })
174
+ ] }, "formatting")
175
+ );
176
+ }
177
+ if (has("colors")) {
178
+ groupElements.push(
179
+ /* @__PURE__ */ jsxs(ToolGroup, { children: [
180
+ /* @__PURE__ */ jsx(
181
+ ColorPicker,
182
+ {
183
+ colors: TEXT_COLORS,
184
+ activeColor: editor.getAttributes("textStyle").color ?? "",
185
+ onSelect: (c) => c ? editor.chain().focus().setColor(c).run() : editor.chain().focus().unsetColor().run(),
186
+ title: "Text color",
187
+ icon: /* @__PURE__ */ jsx(Icon, { name: "type", size: 16 })
188
+ }
189
+ ),
190
+ /* @__PURE__ */ jsx(
191
+ ColorPicker,
192
+ {
193
+ colors: HIGHLIGHT_COLORS,
194
+ activeColor: editor.getAttributes("highlight").color ?? "",
195
+ onSelect: (c) => c ? editor.chain().focus().toggleHighlight({ color: c }).run() : editor.chain().focus().unsetHighlight().run(),
196
+ title: "Highlight",
197
+ icon: /* @__PURE__ */ jsx(Icon, { name: "highlight", size: 16 })
198
+ }
199
+ )
200
+ ] }, "colors")
201
+ );
202
+ }
203
+ if (has("alignment")) {
204
+ groupElements.push(
205
+ /* @__PURE__ */ jsxs(ToolGroup, { children: [
206
+ /* @__PURE__ */ jsx(ToolBtn, { active: editor.isActive({ textAlign: "left" }), onClick: () => editor.chain().focus().setTextAlign("left").run(), title: "Align left", children: /* @__PURE__ */ jsx(Icon, { name: "align-left", size: 16 }) }),
207
+ /* @__PURE__ */ jsx(ToolBtn, { active: editor.isActive({ textAlign: "center" }), onClick: () => editor.chain().focus().setTextAlign("center").run(), title: "Align center", children: /* @__PURE__ */ jsx(Icon, { name: "align-center", size: 16 }) }),
208
+ /* @__PURE__ */ jsx(ToolBtn, { active: editor.isActive({ textAlign: "right" }), onClick: () => editor.chain().focus().setTextAlign("right").run(), title: "Align right", children: /* @__PURE__ */ jsx(Icon, { name: "align-right", size: 16 }) })
209
+ ] }, "alignment")
210
+ );
211
+ }
212
+ if (has("lists")) {
213
+ groupElements.push(
214
+ /* @__PURE__ */ jsxs(ToolGroup, { children: [
215
+ /* @__PURE__ */ jsx(ToolBtn, { active: editor.isActive("bulletList"), onClick: () => editor.chain().focus().toggleBulletList().run(), title: "Bullet list", children: /* @__PURE__ */ jsx(Icon, { name: "list", size: 16 }) }),
216
+ /* @__PURE__ */ jsx(ToolBtn, { active: editor.isActive("orderedList"), onClick: () => editor.chain().focus().toggleOrderedList().run(), title: "Ordered list", children: /* @__PURE__ */ jsx(Icon, { name: "list-ordered", size: 16 }) }),
217
+ /* @__PURE__ */ jsx(ToolBtn, { active: editor.isActive("taskList"), onClick: () => editor.chain().focus().toggleTaskList().run(), title: "Task list", children: /* @__PURE__ */ jsx(Icon, { name: "list-checks", size: 16 }) })
218
+ ] }, "lists")
219
+ );
220
+ }
221
+ if (has("blocks")) {
222
+ groupElements.push(
223
+ /* @__PURE__ */ jsxs(ToolGroup, { children: [
224
+ /* @__PURE__ */ jsx(ToolBtn, { active: editor.isActive("blockquote"), onClick: () => editor.chain().focus().toggleBlockquote().run(), title: "Blockquote", children: /* @__PURE__ */ jsx(Icon, { name: "quote", size: 16 }) }),
225
+ /* @__PURE__ */ jsx(ToolBtn, { active: editor.isActive("codeBlock"), onClick: () => editor.chain().focus().toggleCodeBlock().run(), title: "Code block", children: /* @__PURE__ */ jsx(Icon, { name: "code", size: 16 }) })
226
+ ] }, "blocks")
227
+ );
228
+ }
229
+ if (has("insert")) {
230
+ groupElements.push(
231
+ /* @__PURE__ */ jsxs(ToolGroup, { children: [
232
+ /* @__PURE__ */ jsx(LinkPopover, { editor }),
233
+ /* @__PURE__ */ jsx(ImagePopover, { editor }),
234
+ /* @__PURE__ */ jsx(ToolBtn, { onClick: () => editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run(), disabled: inTable, title: "Insert table", children: /* @__PURE__ */ jsx(Icon, { name: "table", size: 16 }) }),
235
+ /* @__PURE__ */ jsx(ToolBtn, { onClick: () => editor.chain().focus().setHorizontalRule().run(), title: "Horizontal rule", children: /* @__PURE__ */ jsx(Icon, { name: "minus", size: 16 }) })
236
+ ] }, "insert")
237
+ );
238
+ }
239
+ if (has("extras")) {
240
+ groupElements.push(
241
+ /* @__PURE__ */ jsxs(ToolGroup, { children: [
242
+ /* @__PURE__ */ jsx(ToolBtn, { active: editor.isActive("code"), onClick: () => editor.chain().focus().toggleCode().run(), title: "Inline code", children: /* @__PURE__ */ jsx(Icon, { name: "code", size: 16 }) }),
243
+ /* @__PURE__ */ jsx(ToolBtn, { active: editor.isActive("superscript"), onClick: () => editor.chain().focus().toggleSuperscript().run(), title: "Superscript", children: /* @__PURE__ */ jsx(Icon, { name: "superscript", size: 16 }) }),
244
+ /* @__PURE__ */ jsx(ToolBtn, { active: editor.isActive("subscript"), onClick: () => editor.chain().focus().toggleSubscript().run(), title: "Subscript", children: /* @__PURE__ */ jsx(Icon, { name: "subscript", size: 16 }) })
245
+ ] }, "extras")
246
+ );
247
+ }
248
+ if (has("history")) {
249
+ groupElements.push(
250
+ /* @__PURE__ */ jsxs(ToolGroup, { children: [
251
+ /* @__PURE__ */ jsx(ToolBtn, { onClick: () => editor.chain().focus().undo().run(), disabled: !editor.can().undo(), title: "Undo (Ctrl+Z)", children: /* @__PURE__ */ jsx(Icon, { name: "undo", size: 16 }) }),
252
+ /* @__PURE__ */ jsx(ToolBtn, { onClick: () => editor.chain().focus().redo().run(), disabled: !editor.can().redo(), title: "Redo (Ctrl+Shift+Z)", children: /* @__PURE__ */ jsx(Icon, { name: "redo", size: 16 }) })
253
+ ] }, "history")
254
+ );
255
+ }
256
+ const withDividers = groupElements.reduce((acc, el, i) => {
257
+ if (i > 0) acc.push(/* @__PURE__ */ jsx(Divider, {}, `d-${i}`));
258
+ acc.push(el);
259
+ return acc;
260
+ }, []);
261
+ return /* @__PURE__ */ jsxs("div", { className: "border-b-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] bg-[var(--ds-bg-muted)]", children: [
262
+ /* @__PURE__ */ jsx(
263
+ "div",
264
+ {
265
+ className: cn(
266
+ "flex items-center gap-ds-0.5 px-ds-2 py-ds-1",
267
+ isMobile ? "overflow-x-auto flex-nowrap scrollbar-none" : "flex-wrap"
268
+ ),
269
+ children: withDividers
270
+ }
271
+ ),
272
+ inTable && /* @__PURE__ */ jsxs("div", { className: cn(
273
+ "flex items-center gap-ds-0.5 px-ds-2 py-ds-1 border-t-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
274
+ isMobile && "overflow-x-auto flex-nowrap scrollbar-none"
275
+ ), children: [
276
+ /* @__PURE__ */ jsx("span", { className: "text-2xs text-[var(--ds-text-muted)] uppercase tracking-wider mr-ds-1 shrink-0", children: "Table" }),
277
+ /* @__PURE__ */ jsx(TableOpBtn, { onClick: () => editor.chain().focus().addRowAfter().run(), label: "+ Row" }),
278
+ /* @__PURE__ */ jsx(TableOpBtn, { onClick: () => editor.chain().focus().addColumnAfter().run(), label: "+ Col" }),
279
+ /* @__PURE__ */ jsx(Divider, {}),
280
+ /* @__PURE__ */ jsx(TableOpBtn, { onClick: () => editor.chain().focus().deleteRow().run(), label: "\u2212 Row" }),
281
+ /* @__PURE__ */ jsx(TableOpBtn, { onClick: () => editor.chain().focus().deleteColumn().run(), label: "\u2212 Col" }),
282
+ /* @__PURE__ */ jsx(Divider, {}),
283
+ /* @__PURE__ */ jsx(TableOpBtn, { onClick: () => editor.chain().focus().mergeCells().run(), label: "Merge" }),
284
+ /* @__PURE__ */ jsx(TableOpBtn, { onClick: () => editor.chain().focus().splitCell().run(), label: "Split" }),
285
+ /* @__PURE__ */ jsx(Divider, {}),
286
+ /* @__PURE__ */ jsx(ToolBtn, { onClick: () => editor.chain().focus().deleteTable().run(), title: "Delete table", children: /* @__PURE__ */ jsx(Icon, { name: "trash", size: 14 }) })
287
+ ] })
288
+ ] });
289
+ }
290
+ function BlockTypeSelect({ editor }) {
291
+ const value = editor.isActive("heading", { level: 1 }) ? "h1" : editor.isActive("heading", { level: 2 }) ? "h2" : editor.isActive("heading", { level: 3 }) ? "h3" : "p";
292
+ return /* @__PURE__ */ jsxs(
293
+ "select",
294
+ {
295
+ value,
296
+ onChange: (e) => {
297
+ const v = e.target.value;
298
+ if (v === "p") editor.chain().focus().setParagraph().run();
299
+ else if (v === "h1") editor.chain().focus().toggleHeading({ level: 1 }).run();
300
+ else if (v === "h2") editor.chain().focus().toggleHeading({ level: 2 }).run();
301
+ else if (v === "h3") editor.chain().focus().toggleHeading({ level: 3 }).run();
302
+ },
303
+ title: "Block type",
304
+ "aria-label": "Block type",
305
+ className: cn(
306
+ "h-7 px-ds-1.5 text-xs rounded-[var(--ds-radius-sm)] cursor-pointer",
307
+ "bg-transparent text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-surface)]",
308
+ "border-none outline-none",
309
+ "focus-visible:shadow-[var(--ds-shadow-focus)]"
310
+ ),
311
+ children: [
312
+ /* @__PURE__ */ jsx("option", { value: "p", children: "Paragraph" }),
313
+ /* @__PURE__ */ jsx("option", { value: "h1", children: "Heading 1" }),
314
+ /* @__PURE__ */ jsx("option", { value: "h2", children: "Heading 2" }),
315
+ /* @__PURE__ */ jsx("option", { value: "h3", children: "Heading 3" })
316
+ ]
317
+ }
318
+ );
319
+ }
320
+ function TextSizeControls({ editor }) {
321
+ const current = editor.getAttributes("textStyle").fontSize ?? "";
322
+ const idx = TEXT_SIZES.findIndex((s) => s.value === current);
323
+ const currentIdx = idx === -1 ? 1 : idx;
324
+ const decrease = () => {
325
+ const next = Math.max(0, currentIdx - 1);
326
+ const v = TEXT_SIZES[next].value;
327
+ v ? editor.chain().focus().setFontSize(v).run() : editor.chain().focus().unsetFontSize().run();
328
+ };
329
+ const increase = () => {
330
+ const next = Math.min(TEXT_SIZES.length - 1, currentIdx + 1);
331
+ const v = TEXT_SIZES[next].value;
332
+ v ? editor.chain().focus().setFontSize(v).run() : editor.chain().focus().unsetFontSize().run();
333
+ };
334
+ return /* @__PURE__ */ jsxs(ToolGroup, { children: [
335
+ /* @__PURE__ */ jsx(ToolBtn, { onClick: decrease, disabled: currentIdx <= 0, title: `Decrease text size (${TEXT_SIZES[Math.max(0, currentIdx - 1)]?.label})`, children: /* @__PURE__ */ jsx("span", { className: "text-2xs font-bold leading-none select-none", children: "A" }) }),
336
+ /* @__PURE__ */ jsx(ToolBtn, { onClick: increase, disabled: currentIdx >= TEXT_SIZES.length - 1, title: `Increase text size (${TEXT_SIZES[Math.min(TEXT_SIZES.length - 1, currentIdx + 1)]?.label})`, children: /* @__PURE__ */ jsx("span", { className: "text-base font-bold leading-none select-none", children: "A" }) })
337
+ ] });
338
+ }
339
+ function ColorPicker({
340
+ colors,
341
+ activeColor,
342
+ onSelect,
343
+ title,
344
+ icon
345
+ }) {
346
+ const [open, setOpen] = useState(false);
347
+ const ref = useRef(null);
348
+ const close = useCallback(() => setOpen(false), []);
349
+ useEffect(() => {
350
+ if (!open) return;
351
+ const onClick = (e) => {
352
+ if (!ref.current?.contains(e.target)) close();
353
+ };
354
+ const onKey = (e) => {
355
+ if (e.key === "Escape") close();
356
+ };
357
+ document.addEventListener("mousedown", onClick);
358
+ document.addEventListener("keydown", onKey);
359
+ return () => {
360
+ document.removeEventListener("mousedown", onClick);
361
+ document.removeEventListener("keydown", onKey);
362
+ };
363
+ }, [open, close]);
364
+ return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
365
+ /* @__PURE__ */ jsxs(
366
+ "button",
367
+ {
368
+ type: "button",
369
+ onClick: () => setOpen(!open),
370
+ title,
371
+ className: cn(
372
+ "flex flex-col items-center justify-center w-7 h-7 rounded-[var(--ds-radius-sm)] transition-colors cursor-pointer",
373
+ "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
374
+ open ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-surface)] hover:text-[var(--ds-text-primary)]"
375
+ ),
376
+ children: [
377
+ icon,
378
+ activeColor && /* @__PURE__ */ jsx("span", { className: "w-3.5 h-0.5 rounded-[var(--ds-radius-full)] -mt-ds-0.5", style: { backgroundColor: activeColor } })
379
+ ]
380
+ }
381
+ ),
382
+ open && /* @__PURE__ */ jsx("div", { className: "absolute top-full left-0 mt-ds-1 z-[var(--ds-z-popover)] p-ds-1.5 rounded-[var(--ds-radius-md)] border-[length:var(--ds-border-width)] border-[var(--ds-border-default)] bg-[var(--ds-bg-surface-elevated)] shadow-[var(--ds-shadow-dropdown)] flex gap-ds-1 flex-wrap w-[140px]", children: colors.map((c) => /* @__PURE__ */ jsx(
383
+ "button",
384
+ {
385
+ type: "button",
386
+ title: c.label,
387
+ onClick: () => {
388
+ onSelect(c.value);
389
+ close();
390
+ },
391
+ className: cn(
392
+ "w-6 h-6 rounded-[var(--ds-radius-sm)] border-[2px] transition-all cursor-pointer",
393
+ "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
394
+ activeColor === c.value ? "border-[var(--ds-accent-primary)] scale-110" : "border-transparent hover:scale-110"
395
+ ),
396
+ style: { backgroundColor: c.value || "var(--ds-text-primary)" },
397
+ children: !c.value && /* @__PURE__ */ jsx("span", { className: "block w-full h-full rounded-[calc(var(--ds-radius-sm)-2px)] bg-[var(--ds-bg-surface)] relative after:absolute after:inset-0 after:rounded-[inherit] after:bg-gradient-to-br after:from-transparent after:to-[var(--ds-text-muted)] after:opacity-30" })
398
+ },
399
+ c.label
400
+ )) })
401
+ ] });
402
+ }
403
+ function LinkPopover({ editor }) {
404
+ const [open, setOpen] = useState(false);
405
+ const [url, setUrl] = useState("");
406
+ const ref = useRef(null);
407
+ const inputRef = useRef(null);
408
+ const close = useCallback(() => {
409
+ setOpen(false);
410
+ setUrl("");
411
+ }, []);
412
+ const toggle = useCallback(() => {
413
+ if (editor.isActive("link")) {
414
+ editor.chain().focus().unsetLink().run();
415
+ return;
416
+ }
417
+ const existing = editor.getAttributes("link").href ?? "";
418
+ setUrl(existing);
419
+ setOpen((o) => !o);
420
+ }, [editor]);
421
+ useEffect(() => {
422
+ if (!open) return;
423
+ const onClick = (e) => {
424
+ if (!ref.current?.contains(e.target)) close();
425
+ };
426
+ const onKey = (e) => {
427
+ if (e.key === "Escape") close();
428
+ };
429
+ document.addEventListener("mousedown", onClick);
430
+ document.addEventListener("keydown", onKey);
431
+ requestAnimationFrame(() => inputRef.current?.focus());
432
+ return () => {
433
+ document.removeEventListener("mousedown", onClick);
434
+ document.removeEventListener("keydown", onKey);
435
+ };
436
+ }, [open, close]);
437
+ const apply = () => {
438
+ if (url) editor.chain().focus().setLink({ href: url }).run();
439
+ close();
440
+ };
441
+ return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
442
+ /* @__PURE__ */ jsx(ToolBtn, { active: editor.isActive("link"), onClick: toggle, title: "Link", children: /* @__PURE__ */ jsx(Icon, { name: "link", size: 16 }) }),
443
+ open && /* @__PURE__ */ jsxs("div", { className: "absolute top-full left-0 mt-ds-1 z-[var(--ds-z-popover)] p-ds-1.5 rounded-[var(--ds-radius-md)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] bg-[var(--ds-bg-surface-elevated)] shadow-[var(--ds-shadow-dropdown)] flex items-center gap-ds-1 w-[240px]", children: [
444
+ /* @__PURE__ */ jsx(
445
+ "input",
446
+ {
447
+ ref: inputRef,
448
+ type: "url",
449
+ value: url,
450
+ onChange: (e) => setUrl(e.target.value),
451
+ onKeyDown: (e) => {
452
+ if (e.key === "Enter") apply();
453
+ },
454
+ placeholder: "https://...",
455
+ className: cn(
456
+ "flex-1 h-6 px-ds-2 text-xs rounded-[var(--ds-radius-sm)]",
457
+ "bg-transparent text-[var(--ds-text-primary)] placeholder:text-[var(--ds-text-muted)]",
458
+ "border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
459
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--ds-input-focus-ring)]"
460
+ )
461
+ }
462
+ ),
463
+ /* @__PURE__ */ jsx(
464
+ "button",
465
+ {
466
+ type: "button",
467
+ onClick: apply,
468
+ className: cn(
469
+ "shrink-0 h-6 px-ds-2 text-xs font-medium rounded-[var(--ds-radius-sm)] cursor-pointer",
470
+ "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]",
471
+ "hover:opacity-90 transition-opacity",
472
+ "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]"
473
+ ),
474
+ children: "Apply"
475
+ }
476
+ )
477
+ ] })
478
+ ] });
479
+ }
480
+ function ImagePopover({ editor }) {
481
+ const [open, setOpen] = useState(false);
482
+ const [url, setUrl] = useState("");
483
+ const [alt, setAlt] = useState("");
484
+ const ref = useRef(null);
485
+ const inputRef = useRef(null);
486
+ const close = useCallback(() => {
487
+ setOpen(false);
488
+ setUrl("");
489
+ setAlt("");
490
+ }, []);
491
+ useEffect(() => {
492
+ if (!open) return;
493
+ const onClick = (e) => {
494
+ if (!ref.current?.contains(e.target)) close();
495
+ };
496
+ const onKey = (e) => {
497
+ if (e.key === "Escape") close();
498
+ };
499
+ document.addEventListener("mousedown", onClick);
500
+ document.addEventListener("keydown", onKey);
501
+ requestAnimationFrame(() => inputRef.current?.focus());
502
+ return () => {
503
+ document.removeEventListener("mousedown", onClick);
504
+ document.removeEventListener("keydown", onKey);
505
+ };
506
+ }, [open, close]);
507
+ const insert = () => {
508
+ if (url) editor.chain().focus().setImage({ src: url, alt: alt || void 0 }).run();
509
+ close();
510
+ };
511
+ return /* @__PURE__ */ jsxs("div", { ref, className: "relative", children: [
512
+ /* @__PURE__ */ jsx(ToolBtn, { onClick: () => setOpen((o) => !o), title: "Image", children: /* @__PURE__ */ jsx(Icon, { name: "image", size: 16 }) }),
513
+ open && /* @__PURE__ */ jsxs("div", { className: "absolute top-full left-0 mt-ds-1 z-[var(--ds-z-popover)] p-ds-1.5 rounded-[var(--ds-radius-md)] border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)] bg-[var(--ds-bg-surface-elevated)] shadow-[var(--ds-shadow-dropdown)] flex flex-col gap-ds-1 w-[240px]", children: [
514
+ /* @__PURE__ */ jsx(
515
+ "input",
516
+ {
517
+ ref: inputRef,
518
+ type: "url",
519
+ value: url,
520
+ onChange: (e) => setUrl(e.target.value),
521
+ placeholder: "Image URL",
522
+ className: cn(
523
+ "h-6 px-ds-2 text-xs rounded-[var(--ds-radius-sm)]",
524
+ "bg-transparent text-[var(--ds-text-primary)] placeholder:text-[var(--ds-text-muted)]",
525
+ "border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
526
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--ds-input-focus-ring)]"
527
+ )
528
+ }
529
+ ),
530
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-ds-1", children: [
531
+ /* @__PURE__ */ jsx(
532
+ "input",
533
+ {
534
+ type: "text",
535
+ value: alt,
536
+ onChange: (e) => setAlt(e.target.value),
537
+ onKeyDown: (e) => {
538
+ if (e.key === "Enter") insert();
539
+ },
540
+ placeholder: "Alt text (optional)",
541
+ className: cn(
542
+ "flex-1 h-6 px-ds-2 text-xs rounded-[var(--ds-radius-sm)]",
543
+ "bg-transparent text-[var(--ds-text-primary)] placeholder:text-[var(--ds-text-muted)]",
544
+ "border-[length:var(--ds-border-width)] border-[var(--ds-border-subtle)]",
545
+ "focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[var(--ds-input-focus-ring)]"
546
+ )
547
+ }
548
+ ),
549
+ /* @__PURE__ */ jsx(
550
+ "button",
551
+ {
552
+ type: "button",
553
+ onClick: insert,
554
+ className: cn(
555
+ "shrink-0 h-6 px-ds-2 text-xs font-medium rounded-[var(--ds-radius-sm)] cursor-pointer",
556
+ "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]",
557
+ "hover:opacity-90 transition-opacity",
558
+ "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]"
559
+ ),
560
+ children: "Insert"
561
+ }
562
+ )
563
+ ] })
564
+ ] })
565
+ ] });
566
+ }
567
+ function FontFamilySelect({ editor }) {
568
+ const current = editor.getAttributes("textStyle").fontFamily ?? "";
569
+ return /* @__PURE__ */ jsx(
570
+ "select",
571
+ {
572
+ value: current,
573
+ onChange: (e) => {
574
+ const v = e.target.value;
575
+ v ? editor.chain().focus().setFontFamily(v).run() : editor.chain().focus().unsetFontFamily().run();
576
+ },
577
+ title: "Font family",
578
+ "aria-label": "Font family",
579
+ className: cn(
580
+ "h-7 px-ds-1.5 text-xs rounded-[var(--ds-radius-sm)] cursor-pointer",
581
+ "bg-transparent text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-surface)]",
582
+ "border-none outline-none",
583
+ "focus-visible:shadow-[var(--ds-shadow-focus)]"
584
+ ),
585
+ children: FONT_FAMILIES.map((f) => /* @__PURE__ */ jsx("option", { value: f.value, children: f.label }, f.label))
586
+ }
587
+ );
588
+ }
589
+ function ToolGroup({ children }) {
590
+ return /* @__PURE__ */ jsx("div", { className: "flex items-center gap-ds-0.5", children });
591
+ }
592
+ function Divider() {
593
+ return /* @__PURE__ */ jsx("div", { className: "w-px h-5 mx-ds-1 bg-[var(--ds-border-subtle)]" });
594
+ }
595
+ function TableOpBtn({ onClick, label }) {
596
+ return /* @__PURE__ */ jsx(
597
+ "button",
598
+ {
599
+ type: "button",
600
+ onClick,
601
+ title: label,
602
+ className: cn(
603
+ "h-6 px-ds-1.5 text-2xs font-medium rounded-[var(--ds-radius-sm)] transition-colors cursor-pointer whitespace-nowrap",
604
+ "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
605
+ "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-surface)] hover:text-[var(--ds-text-primary)]"
606
+ ),
607
+ children: label
608
+ }
609
+ );
610
+ }
611
+ function ToolBtn({ active, disabled, onClick, title, children }) {
612
+ return /* @__PURE__ */ jsx(
613
+ "button",
614
+ {
615
+ type: "button",
616
+ onClick,
617
+ disabled,
618
+ title,
619
+ className: cn(
620
+ "flex items-center justify-center w-7 h-7 rounded-[var(--ds-radius-sm)] transition-colors cursor-pointer",
621
+ "focus-visible:outline-none focus-visible:shadow-[var(--ds-shadow-focus)]",
622
+ active ? "bg-[var(--ds-accent-primary)] text-[var(--ds-text-on-primary)]" : "text-[var(--ds-text-secondary)] hover:bg-[var(--ds-bg-surface)] hover:text-[var(--ds-text-primary)]",
623
+ disabled && "opacity-disabled pointer-events-none"
624
+ ),
625
+ children
626
+ }
627
+ );
628
+ }
629
+
630
+ export { RichTextEditor };
631
+ //# sourceMappingURL=editor.js.map
632
+ //# sourceMappingURL=editor.js.map