@crewlethq/ui 0.34.2 → 0.35.1

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 (98) hide show
  1. package/README.md +51 -0
  2. package/dist/AppShell/index.js +2 -2
  3. package/dist/ArtifactBar/ArtifactBar.d.ts +6 -0
  4. package/dist/ArtifactBar/index.d.ts +1 -0
  5. package/dist/ArtifactBar/index.js +2 -2
  6. package/dist/{ArtifactBar-C462VD4C.css → ArtifactBar-HV5AT4RG.css} +39 -2
  7. package/dist/ArtifactCard/index.d.ts +1 -0
  8. package/dist/ArtifactCard/index.js +2 -2
  9. package/dist/{ArtifactCard-XHRLAL7C.css → ArtifactCard-OGTMO2KI.css} +15 -16
  10. package/dist/ArtifactKindIllustration/ArtifactKindIllustration.d.ts +14 -1
  11. package/dist/ArtifactKindIllustration/index.d.ts +1 -0
  12. package/dist/ArtifactKindIllustration/index.js +1 -1
  13. package/dist/{ArtifactKindIllustration-4NG6IE3C.css → ArtifactKindIllustration-5FPRJQ3P.css} +32 -26
  14. package/dist/ArtifactKinds-CVD3FCYO.css +15 -0
  15. package/dist/AvatarPicker/index.js +2 -2
  16. package/dist/CanvasBoard/CanvasBoard.d.ts +30 -12
  17. package/dist/CanvasBoard/index.js +1 -1
  18. package/dist/ChangeMark/index.js +2 -2
  19. package/dist/ChoiceList/ChoiceList.d.ts +7 -1
  20. package/dist/ChoiceList/index.js +1 -1
  21. package/dist/{ChoiceList-2PQJS4DX.css → ChoiceList-RBL7FNQL.css} +39 -0
  22. package/dist/DataTable/index.js +2 -2
  23. package/dist/DataView/index.js +3 -3
  24. package/dist/DocTabsEditor/index.js +2 -2
  25. package/dist/{Editor-JMRD6AZW.js → Editor-BVQHBF7L.js} +4 -4
  26. package/dist/FileDrop/FileDrop.d.ts +64 -0
  27. package/dist/FileDrop/index.d.ts +3 -0
  28. package/dist/FileDrop/index.js +4 -0
  29. package/dist/FileDrop-WE7BNZGV.css +80 -0
  30. package/dist/FindingList/FindingList.d.ts +35 -0
  31. package/dist/FindingList/index.d.ts +3 -0
  32. package/dist/FindingList/index.js +3 -0
  33. package/dist/FindingList-PMYA2R6Q.css +71 -0
  34. package/dist/HuePicker/HuePicker.d.ts +9 -0
  35. package/dist/HuePicker/index.d.ts +1 -1
  36. package/dist/HuePicker/index.js +1 -1
  37. package/dist/Markdown/index.js +1 -1
  38. package/dist/Menu/Menu.d.ts +6 -1
  39. package/dist/Menu/index.js +1 -1
  40. package/dist/RichEditor/index.js +3 -3
  41. package/dist/RichEditorMarkdown/index.js +3 -3
  42. package/dist/SheetColumnEditor/SheetColumnEditor.d.ts +94 -0
  43. package/dist/SheetColumnEditor/index.d.ts +3 -0
  44. package/dist/SheetColumnEditor/index.js +23 -0
  45. package/dist/SheetColumnEditor-BO2HKFRB.css +73 -0
  46. package/dist/SheetGrid/SheetGrid.d.ts +19 -1
  47. package/dist/SheetGrid/index.js +5 -4
  48. package/dist/{SheetGrid-GDPCCZ4V.css → SheetGrid-YRDXTIIZ.css} +48 -0
  49. package/dist/SheetModel/columns.d.ts +19 -0
  50. package/dist/SheetModel/index.d.ts +1 -0
  51. package/dist/SheetModel/index.js +1 -1
  52. package/dist/SheetModel/labels.d.ts +6 -0
  53. package/dist/SlideFilmstrip/SlideFilmstrip.d.ts +30 -1
  54. package/dist/SlideFilmstrip/index.js +5 -1
  55. package/dist/{SlideFilmstrip-5U6Z55GS.css → SlideFilmstrip-4ITZLMO4.css} +49 -0
  56. package/dist/SourceEditor/SourceEditor.d.ts +62 -0
  57. package/dist/SourceEditor/index.d.ts +3 -0
  58. package/dist/SourceEditor/index.js +3 -0
  59. package/dist/SourceEditor-G2C3STPX.css +160 -0
  60. package/dist/ThemeGallery/ThemeGallery.d.ts +10 -3
  61. package/dist/ThemeGallery/index.js +1 -1
  62. package/dist/{ThemeGallery-NITASULQ.css → ThemeGallery-HSUKBDWY.css} +9 -0
  63. package/dist/{chunk-UXXJDIYE.js → chunk-22CQQDFW.js} +1 -1
  64. package/dist/{chunk-EQ7P7DGN.js → chunk-3VD3G3IL.js} +2 -2
  65. package/dist/{chunk-SGHIAGPR.js → chunk-47JQAGRV.js} +1 -1
  66. package/dist/{chunk-7T5LSYZ2.js → chunk-7UYONN6K.js} +4 -3
  67. package/dist/{chunk-KK5OYNAF.js → chunk-BAKPMH3S.js} +8 -4
  68. package/dist/{chunk-3EB2AV3H.js → chunk-BUVCV3WI.js} +1 -1
  69. package/dist/{chunk-4B6LSFDS.js → chunk-CSQTDGTS.js} +1 -1
  70. package/dist/{chunk-WPX6A5BV.js → chunk-E35CMI5D.js} +22 -6
  71. package/dist/{chunk-EWAFN2IU.js → chunk-FF5OZV77.js} +281 -234
  72. package/dist/{chunk-OMLACFPP.js → chunk-FJJHD73S.js} +1 -1
  73. package/dist/chunk-J6YZ4IYV.js +341 -0
  74. package/dist/{chunk-VI5FWDZW.js → chunk-LFSIKGRQ.js} +17 -2
  75. package/dist/{chunk-ZADVT7IL.js → chunk-NKZCJRKO.js} +9 -4
  76. package/dist/chunk-NTB5LRRD.js +52 -0
  77. package/dist/{chunk-UOR7TOAU.js → chunk-OSFA5ROT.js} +86 -1
  78. package/dist/{chunk-T46NR3WP.js → chunk-PGZ2R3ON.js} +1 -1
  79. package/dist/chunk-PI7HKLIT.js +245 -0
  80. package/dist/{chunk-YZFLH463.js → chunk-QEFHYUCY.js} +1 -1
  81. package/dist/{chunk-NPQWKZTB.js → chunk-QFNWMWWY.js} +2 -2
  82. package/dist/{chunk-C7DKX453.js → chunk-QH6VYJ3X.js} +1 -1
  83. package/dist/{chunk-XV32RKQZ.js → chunk-QIXMNOBD.js} +67 -6
  84. package/dist/{chunk-GPHWCK7M.js → chunk-SUXP4OBT.js} +6 -3
  85. package/dist/{chunk-TRQCHF2F.js → chunk-SWD4QBL6.js} +1 -1
  86. package/dist/chunk-THDGBD6Y.js +154 -0
  87. package/dist/{chunk-C2FRTWKO.js → chunk-W3UVOZKL.js} +3 -2
  88. package/dist/{chunk-UDQW6UK7.js → chunk-W7QBBKUE.js} +34 -7
  89. package/dist/{chunk-QBXZLGK2.js → chunk-ZU3DUXD2.js} +3 -2
  90. package/dist/generated/index.d.ts +4 -0
  91. package/dist/generated/index.js +53 -49
  92. package/dist/index.js +53 -49
  93. package/dist/styles.css +652 -51
  94. package/dist/utils/csv.d.ts +35 -0
  95. package/dist/utils/index.d.ts +2 -1
  96. package/dist/utils/index.js +1 -1
  97. package/package.json +3 -3
  98. /package/dist/{chunk-VFRSSP3W.js → chunk-3WCNHYXE.js} +0 -0
@@ -0,0 +1,245 @@
1
+ import { VisuallyHidden } from './chunk-BZFWDIV6.js';
2
+ import { cx } from './chunk-ODEQTPFM.js';
3
+ import './SourceEditor-G2C3STPX.css';
4
+ import { forwardRef, useRef, useId, useState, useImperativeHandle, useLayoutEffect } from 'react';
5
+ import { jsxs, jsx } from 'react/jsx-runtime';
6
+
7
+ var SOURCE_EDITOR_KEYS = "Tab indents and Shift+Tab outdents. Press Escape, then Tab, to move to the next control.";
8
+ function lineStart(text, line) {
9
+ let at = 0;
10
+ for (let n = 1; n < line; n++) {
11
+ const next = text.indexOf("\n", at);
12
+ if (next < 0) return text.length;
13
+ at = next + 1;
14
+ }
15
+ return at;
16
+ }
17
+ function insert(field, text, start, end) {
18
+ field.setSelectionRange(start, end);
19
+ const done = typeof document.execCommand === "function" && document.execCommand("insertText", false, text);
20
+ if (!done) field.setRangeText(text, start, end, "end");
21
+ return field.value;
22
+ }
23
+ var SourceEditor = forwardRef(
24
+ function SourceEditor2({
25
+ value,
26
+ onChange,
27
+ label,
28
+ marks = [],
29
+ onSubmit,
30
+ indent = 2,
31
+ fill = false,
32
+ rows = 12,
33
+ className,
34
+ fieldClassName,
35
+ onKeyDown,
36
+ onScroll,
37
+ "aria-describedby": describedBy,
38
+ ...rest
39
+ }, ref) {
40
+ const field = useRef(null);
41
+ const tabLeaves = useRef(false);
42
+ const keysId = useId();
43
+ const [scrollTop, setScrollTop] = useState(0);
44
+ const lines = countLines(value);
45
+ const tone = toneByLine(marks, lines);
46
+ useImperativeHandle(
47
+ ref,
48
+ () => ({
49
+ revealLine(line) {
50
+ const el = field.current;
51
+ if (!el) return;
52
+ const at = lineStart(el.value, Math.max(1, line));
53
+ el.focus();
54
+ el.setSelectionRange(at, at);
55
+ const height = parseFloat(getComputedStyle(el).lineHeight) || 20;
56
+ const top = (Math.max(1, line) - 1) * height;
57
+ if (top < el.scrollTop || top + height > el.scrollTop + el.clientHeight) {
58
+ el.scrollTop = Math.max(0, top - el.clientHeight / 3);
59
+ }
60
+ },
61
+ focus() {
62
+ field.current?.focus();
63
+ },
64
+ get element() {
65
+ return field.current;
66
+ }
67
+ }),
68
+ []
69
+ );
70
+ useLayoutEffect(() => {
71
+ const el = field.current;
72
+ if (el && el.scrollTop !== scrollTop) setScrollTop(el.scrollTop);
73
+ }, [value, scrollTop]);
74
+ const keyDown = (event) => {
75
+ onKeyDown?.(event);
76
+ if (event.defaultPrevented) return;
77
+ const el = event.currentTarget;
78
+ if (event.key === "Escape") {
79
+ tabLeaves.current = true;
80
+ return;
81
+ }
82
+ if (event.key === "Tab") {
83
+ if (tabLeaves.current || event.altKey || event.metaKey || event.ctrlKey) {
84
+ tabLeaves.current = false;
85
+ return;
86
+ }
87
+ event.preventDefault();
88
+ onChange(
89
+ event.shiftKey ? outdent(el, indent) : indentLines(el, indent)
90
+ );
91
+ return;
92
+ }
93
+ tabLeaves.current = false;
94
+ if (event.key === "Enter" && (event.metaKey || event.ctrlKey)) {
95
+ if (!onSubmit) return;
96
+ event.preventDefault();
97
+ onSubmit();
98
+ return;
99
+ }
100
+ if (event.key === "Enter" && !event.shiftKey && !event.altKey) {
101
+ const before = el.value.slice(0, el.selectionStart);
102
+ const lead = /^[\t ]*/.exec(before.slice(before.lastIndexOf("\n") + 1))?.[0] ?? "";
103
+ if (!lead) return;
104
+ event.preventDefault();
105
+ onChange(insert(el, `
106
+ ${lead}`, el.selectionStart, el.selectionEnd));
107
+ }
108
+ };
109
+ return /* @__PURE__ */ jsxs(
110
+ "div",
111
+ {
112
+ className: cx("crewlet-source-editor", fill && "is-fill", className),
113
+ style: {
114
+ "--crewlet-source-digits": String(String(lines).length)
115
+ },
116
+ children: [
117
+ /* @__PURE__ */ jsx("div", { className: "crewlet-source-editor__gutter", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
118
+ "div",
119
+ {
120
+ className: "crewlet-source-editor__numbers",
121
+ style: { transform: `translateY(${-scrollTop}px)` },
122
+ children: Array.from({ length: lines }, (_, i) => {
123
+ const line = i + 1;
124
+ const mark = tone.get(line);
125
+ return /* @__PURE__ */ jsx(
126
+ "span",
127
+ {
128
+ className: cx(
129
+ "crewlet-source-editor__number",
130
+ mark && `is-${mark.tone}`
131
+ ),
132
+ title: mark?.message,
133
+ children: line
134
+ },
135
+ line
136
+ );
137
+ })
138
+ }
139
+ ) }),
140
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-source-editor__body", children: [
141
+ /* @__PURE__ */ jsx("div", { className: "crewlet-source-editor__marks", "aria-hidden": "true", children: /* @__PURE__ */ jsx("div", { style: { transform: `translateY(${-scrollTop}px)` }, children: [...tone.entries()].map(([line, mark]) => /* @__PURE__ */ jsx(
142
+ "span",
143
+ {
144
+ className: cx(
145
+ "crewlet-source-editor__mark",
146
+ `is-${mark.tone}`
147
+ ),
148
+ style: {
149
+ "--crewlet-source-line": String(line - 1)
150
+ }
151
+ },
152
+ line
153
+ )) }) }),
154
+ /* @__PURE__ */ jsx(
155
+ "textarea",
156
+ {
157
+ ref: field,
158
+ className: cx("crewlet-source-editor__field", fieldClassName),
159
+ "aria-label": label,
160
+ "aria-describedby": cx(keysId, describedBy) || void 0,
161
+ "aria-invalid": [...tone.values()].some((m) => m.tone === "danger") || void 0,
162
+ value,
163
+ rows,
164
+ wrap: "off",
165
+ spellCheck: false,
166
+ autoCapitalize: "off",
167
+ autoComplete: "off",
168
+ autoCorrect: "off",
169
+ onChange: (event) => onChange(event.target.value),
170
+ onKeyDown: keyDown,
171
+ onBlur: () => {
172
+ tabLeaves.current = false;
173
+ },
174
+ onScroll: (event) => {
175
+ onScroll?.(event);
176
+ setScrollTop(event.currentTarget.scrollTop);
177
+ },
178
+ ...rest
179
+ }
180
+ )
181
+ ] }),
182
+ /* @__PURE__ */ jsx(VisuallyHidden, { id: keysId, children: SOURCE_EDITOR_KEYS })
183
+ ]
184
+ }
185
+ );
186
+ }
187
+ );
188
+ function countLines(text) {
189
+ let n = 1;
190
+ for (let i = text.indexOf("\n"); i >= 0; i = text.indexOf("\n", i + 1)) n++;
191
+ return n;
192
+ }
193
+ function toneByLine(marks, lines) {
194
+ const out = /* @__PURE__ */ new Map();
195
+ for (const mark of marks) {
196
+ if (!Number.isInteger(mark.line) || mark.line < 1 || mark.line > lines)
197
+ continue;
198
+ const held = out.get(mark.line);
199
+ if (!held || held.tone === "warning" && mark.tone === "danger")
200
+ out.set(mark.line, mark);
201
+ }
202
+ return out;
203
+ }
204
+ function selectedLines(el) {
205
+ const text = el.value;
206
+ const from = text.lastIndexOf("\n", el.selectionStart - 1) + 1;
207
+ const endAt = el.selectionEnd > el.selectionStart && text[el.selectionEnd - 1] === "\n" ? el.selectionEnd - 1 : el.selectionEnd;
208
+ const next = text.indexOf("\n", endAt);
209
+ return { from, to: next < 0 ? text.length : next };
210
+ }
211
+ function indentLines(el, size) {
212
+ const pad = " ".repeat(size);
213
+ if (el.selectionStart === el.selectionEnd) {
214
+ const column = el.selectionStart - (el.value.lastIndexOf("\n", el.selectionStart - 1) + 1);
215
+ return insert(
216
+ el,
217
+ " ".repeat(size - column % size),
218
+ el.selectionStart,
219
+ el.selectionEnd
220
+ );
221
+ }
222
+ const { from, to } = selectedLines(el);
223
+ const block = el.value.slice(from, to).replace(/^/gm, pad);
224
+ const value = insert(el, block, from, to);
225
+ el.setSelectionRange(from, from + block.length);
226
+ return value;
227
+ }
228
+ function outdent(el, size) {
229
+ const { from, to } = selectedLines(el);
230
+ const strip = new RegExp(`^(?: {1,${size}}|\\t)`, "gm");
231
+ const original = el.value.slice(from, to);
232
+ const block = original.replace(strip, "");
233
+ if (block === original) return el.value;
234
+ const caret = el.selectionStart === el.selectionEnd;
235
+ const at = el.selectionStart;
236
+ const removed = (/^(?: +|\t)/.exec(original)?.[0].length ?? 0) - (/^(?: +|\t)/.exec(block)?.[0].length ?? 0);
237
+ const value = insert(el, block, from, to);
238
+ if (caret) {
239
+ const back = Math.max(from, at - removed);
240
+ el.setSelectionRange(back, back);
241
+ } else el.setSelectionRange(from, from + block.length);
242
+ return value;
243
+ }
244
+
245
+ export { SOURCE_EDITOR_KEYS, SourceEditor, lineStart };
@@ -1,6 +1,6 @@
1
1
  import { useReorder } from './chunk-XJ2QEBMM.js';
2
2
  import { Input } from './chunk-6QFAK2WV.js';
3
- import { Menu } from './chunk-3EB2AV3H.js';
3
+ import { Menu } from './chunk-BUVCV3WI.js';
4
4
  import { IconButton } from './chunk-4FQT6ZAY.js';
5
5
  import { VisuallyHidden } from './chunk-BZFWDIV6.js';
6
6
  import { cx } from './chunk-ODEQTPFM.js';
@@ -1,5 +1,5 @@
1
1
  import { PageHeader } from './chunk-HOXGBLEY.js';
2
- import { DataTable } from './chunk-UXXJDIYE.js';
2
+ import { DataTable } from './chunk-22CQQDFW.js';
3
3
  import { DateTimePicker } from './chunk-73A7D5CE.js';
4
4
  import { useListbox, useOptionKeys } from './chunk-F73MC7EZ.js';
5
5
  import { Toolbar } from './chunk-PGRPNDWM.js';
@@ -8,7 +8,7 @@ import { Input } from './chunk-6QFAK2WV.js';
8
8
  import { Kbd } from './chunk-SMBKXWML.js';
9
9
  import { RelativeTime } from './chunk-3CZ7XAVY.js';
10
10
  import { Count } from './chunk-BHHIIK3M.js';
11
- import { Menu } from './chunk-3EB2AV3H.js';
11
+ import { Menu } from './chunk-BUVCV3WI.js';
12
12
  import { Button } from './chunk-OG6CL3EB.js';
13
13
  import { IconButton } from './chunk-4FQT6ZAY.js';
14
14
  import { VisuallyHidden } from './chunk-BZFWDIV6.js';
@@ -1,4 +1,4 @@
1
- import { MARKDOWN_CALLOUT_TONES, parseMarkdownSpans, markdownInline, markdownCodeText } from './chunk-KK5OYNAF.js';
1
+ import { MARKDOWN_CALLOUT_TONES, parseMarkdownSpans, markdownInline, markdownCodeText } from './chunk-BAKPMH3S.js';
2
2
  import { Schema, Fragment } from 'prosemirror-model';
3
3
 
4
4
  function headingTag(level) {
@@ -1,18 +1,24 @@
1
1
  import { SlideStage } from './chunk-LV4APO72.js';
2
2
  import { useReorder } from './chunk-XJ2QEBMM.js';
3
+ import { Kbd, isApplePlatform } from './chunk-SMBKXWML.js';
4
+ import { Menu } from './chunk-BUVCV3WI.js';
3
5
  import { VisuallyHidden } from './chunk-BZFWDIV6.js';
4
6
  import { cx } from './chunk-ODEQTPFM.js';
5
- import './SlideFilmstrip-5U6Z55GS.css';
6
- import { useRef, useLayoutEffect } from 'react';
7
- import { EyeOffGlyph, GripVerticalGlyph } from '@crewlethq/icons/glyphs';
7
+ import './SlideFilmstrip-4ITZLMO4.css';
8
+ import { useRef, useLayoutEffect, useState } from 'react';
9
+ import { EyeOffGlyph, GripVerticalGlyph, EllipsisGlyph, CopyGlyph, TrashGlyph } from '@crewlethq/icons/glyphs';
8
10
  import { jsxs, jsx } from 'react/jsx-runtime';
9
11
 
10
12
  var LABELS = {
11
13
  list: "Slides",
12
14
  slide: (position, title) => `Slide ${position}, ${title}`,
13
15
  hidden: "Hidden when presenting",
14
- untitled: "Untitled"
16
+ untitled: "Untitled",
17
+ actions: (position, title) => `Actions for slide ${position}, ${title}`,
18
+ duplicate: "Duplicate slide",
19
+ delete: "Delete slide"
15
20
  };
21
+ var DELETE_KEY = () => isApplePlatform() ? "Backspace" : "Delete";
16
22
  var STEP_KEYS = {
17
23
  vertical: { back: "ArrowUp", forward: "ArrowDown" },
18
24
  horizontal: { back: "ArrowLeft", forward: "ArrowRight" }
@@ -24,6 +30,10 @@ function SlideFilmstrip({
24
30
  renderSlide,
25
31
  onReorder,
26
32
  orientation = "vertical",
33
+ onDuplicate,
34
+ onDelete,
35
+ actions,
36
+ footer,
27
37
  className,
28
38
  labels
29
39
  }) {
@@ -53,7 +63,43 @@ function SlideFilmstrip({
53
63
  onSelect(id);
54
64
  options.current.get(id)?.focus();
55
65
  };
66
+ const [menuFor, setMenuFor] = useState(null);
67
+ const menuOf = (id) => [
68
+ ...onDuplicate ? [{
69
+ key: "duplicate",
70
+ label: text.duplicate,
71
+ icon: /* @__PURE__ */ jsx(CopyGlyph, { size: "sm" }),
72
+ onSelect: () => onDuplicate(id),
73
+ hint: /* @__PURE__ */ jsx(Kbd, { subtle: true, keys: ["Mod", "D"] })
74
+ }] : [],
75
+ ...onDelete ? [{
76
+ key: "delete",
77
+ label: text.delete,
78
+ icon: /* @__PURE__ */ jsx(TrashGlyph, { size: "sm" }),
79
+ onSelect: () => onDelete(id),
80
+ danger: true,
81
+ hint: /* @__PURE__ */ jsx(Kbd, { subtle: true, keys: [DELETE_KEY()] })
82
+ }] : [],
83
+ ...actions?.(id) ?? []
84
+ ];
56
85
  const onKeyDown = (event) => {
86
+ const mod = event.metaKey || event.ctrlKey;
87
+ if (onDuplicate && mod && !event.altKey && !event.shiftKey && event.key.toLowerCase() === "d") {
88
+ event.preventDefault();
89
+ onDuplicate(selected);
90
+ return;
91
+ }
92
+ if (event.key === "ContextMenu" || event.shiftKey && event.key === "F10") {
93
+ if (menuOf(selected).length === 0) return;
94
+ event.preventDefault();
95
+ setMenuFor(selected);
96
+ return;
97
+ }
98
+ if (onDelete && !mod && !event.altKey && (event.key === "Delete" || event.key === "Backspace")) {
99
+ event.preventDefault();
100
+ onDelete(selected);
101
+ return;
102
+ }
57
103
  const at = order.indexOf(selected);
58
104
  const step = STEP_KEYS[orientation];
59
105
  const to = {
@@ -82,6 +128,7 @@ function SlideFilmstrip({
82
128
  children: order.map((id, i) => {
83
129
  const slide = byId.get(id);
84
130
  const isSelected = id === selected;
131
+ const menu = menuOf(id);
85
132
  return /* @__PURE__ */ jsxs(
86
133
  "li",
87
134
  {
@@ -111,14 +158,28 @@ function SlideFilmstrip({
111
158
  ]
112
159
  }
113
160
  ),
114
- onReorder ? /* @__PURE__ */ jsx("button", { type: "button", className: "crewlet-slide-filmstrip__handle", ...reorder.handleProps(id), children: /* @__PURE__ */ jsx(GripVerticalGlyph, { size: "sm" }) }) : null
161
+ onReorder || menu.length > 0 ? /* @__PURE__ */ jsxs("span", { className: "crewlet-slide-filmstrip__controls", children: [
162
+ onReorder ? /* @__PURE__ */ jsx("button", { type: "button", className: "crewlet-slide-filmstrip__handle", ...reorder.handleProps(id), children: /* @__PURE__ */ jsx(GripVerticalGlyph, { size: "sm" }) }) : null,
163
+ menu.length > 0 ? /* @__PURE__ */ jsx(
164
+ Menu,
165
+ {
166
+ label: text.actions(i + 1, titleOf(id)),
167
+ icon: /* @__PURE__ */ jsx(EllipsisGlyph, { size: "sm" }),
168
+ triggerTabIndex: -1,
169
+ items: menu,
170
+ open: menuFor === id,
171
+ onOpenChange: (open) => setMenuFor(open ? id : null)
172
+ }
173
+ ) : null
174
+ ] }) : null
115
175
  ]
116
176
  },
117
177
  id
118
178
  );
119
179
  })
120
180
  }
121
- )
181
+ ),
182
+ footer ? /* @__PURE__ */ jsx("div", { className: "crewlet-slide-filmstrip__footer", children: footer }) : null
122
183
  ]
123
184
  }
124
185
  );
@@ -3,10 +3,13 @@ import { SegmentedControl } from './chunk-CCAXYHQS.js';
3
3
  import { cx } from './chunk-ODEQTPFM.js';
4
4
  import { jsx } from 'react/jsx-runtime';
5
5
 
6
+ function HueSwatch({ hue, className }) {
7
+ return /* @__PURE__ */ jsx("span", { "aria-hidden": "true", className: cx("crewlet-hue-picker__swatch", `crewlet-hue-picker__swatch--${hue}`, className) });
8
+ }
6
9
  var optionsFor = (disabled) => NODE_HUES.map((hue) => ({
7
10
  value: hue,
8
11
  label: NODE_HUE_NAMES[hue],
9
- icon: /* @__PURE__ */ jsx("span", { className: `crewlet-hue-picker__swatch crewlet-hue-picker__swatch--${hue}` }),
12
+ icon: /* @__PURE__ */ jsx(HueSwatch, { hue }),
10
13
  disabled
11
14
  }));
12
15
  var ENABLED = optionsFor(false);
@@ -18,7 +21,7 @@ function HuePicker(props) {
18
21
  {
19
22
  value: "none",
20
23
  label: props.none,
21
- icon: /* @__PURE__ */ jsx("span", { className: "crewlet-hue-picker__swatch crewlet-hue-picker__swatch--none" }),
24
+ icon: /* @__PURE__ */ jsx(HueSwatch, { hue: "none" }),
22
25
  disabled
23
26
  },
24
27
  ...hues
@@ -42,4 +45,4 @@ function HuePicker(props) {
42
45
  );
43
46
  }
44
47
 
45
- export { HuePicker };
48
+ export { HuePicker, HueSwatch };
@@ -1,4 +1,4 @@
1
- import { readMarkdown, writeMarkdown } from './chunk-C7DKX453.js';
1
+ import { readMarkdown, writeMarkdown } from './chunk-QH6VYJ3X.js';
2
2
 
3
3
  // src/RichEditor/roundtrip.ts
4
4
  function roundTripMarkdown(markdown) {
@@ -0,0 +1,154 @@
1
+ import { Button } from './chunk-OG6CL3EB.js';
2
+ import { cx } from './chunk-ODEQTPFM.js';
3
+ import './FileDrop-WE7BNZGV.css';
4
+ import { forwardRef, useRef, useState } from 'react';
5
+ import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
6
+
7
+ function clean(path) {
8
+ return path.replace(/\\/g, "/").replace(/^\/+/, "");
9
+ }
10
+ async function walk(entry, out) {
11
+ if (entry.isFile && entry.file) {
12
+ const file = await new Promise((ok, fail) => entry.file(ok, fail));
13
+ out.push({ path: clean(entry.fullPath || entry.name), file });
14
+ return;
15
+ }
16
+ if (entry.isDirectory && entry.createReader) {
17
+ const reader = entry.createReader();
18
+ for (; ; ) {
19
+ const batch = await new Promise(
20
+ (ok, fail) => reader.readEntries(ok, fail)
21
+ );
22
+ if (batch.length === 0) break;
23
+ for (const child of batch) await walk(child, out);
24
+ }
25
+ }
26
+ }
27
+ async function droppedFiles(data) {
28
+ const entries = [];
29
+ for (const item of Array.from(data.items ?? [])) {
30
+ if (item.kind !== "file") continue;
31
+ const entry = item.webkitGetAsEntry?.();
32
+ if (entry) entries.push(entry);
33
+ }
34
+ if (entries.length === 0) {
35
+ return Array.from(data.files ?? []).map((file) => ({
36
+ path: clean(file.name),
37
+ file
38
+ }));
39
+ }
40
+ const out = [];
41
+ for (const entry of entries) await walk(entry, out);
42
+ return out;
43
+ }
44
+ function pickedFiles(list) {
45
+ return Array.from(list ?? []).map((file) => ({
46
+ path: clean(
47
+ file.webkitRelativePath || file.name
48
+ ),
49
+ file
50
+ }));
51
+ }
52
+ function carriesFiles(event) {
53
+ return Array.from(event.dataTransfer?.types ?? []).includes("Files");
54
+ }
55
+ var FilePickButton = forwardRef(function FilePickButton2({ onFiles, accept, directory = false, multiple = true, children, ...rest }, ref) {
56
+ const input = useRef(null);
57
+ const folder = directory ? {
58
+ webkitdirectory: "",
59
+ directory: ""
60
+ } : {};
61
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
62
+ /* @__PURE__ */ jsx(Button, { ref, ...rest, onClick: () => input.current?.click(), children }),
63
+ /* @__PURE__ */ jsx(
64
+ "input",
65
+ {
66
+ ref: input,
67
+ type: "file",
68
+ className: "crewlet-file-drop__input",
69
+ tabIndex: -1,
70
+ "aria-hidden": "true",
71
+ accept,
72
+ multiple: directory || multiple,
73
+ onChange: (event) => {
74
+ const files = pickedFiles(event.currentTarget.files);
75
+ event.currentTarget.value = "";
76
+ if (files.length > 0) onFiles(files);
77
+ },
78
+ ...folder
79
+ }
80
+ )
81
+ ] });
82
+ });
83
+ var FileDrop = forwardRef(
84
+ function FileDrop2({
85
+ onFiles,
86
+ children,
87
+ title,
88
+ hint,
89
+ actions,
90
+ disabled = false,
91
+ className,
92
+ ...rest
93
+ }, ref) {
94
+ const [over, setOver] = useState(false);
95
+ const depth = useRef(0);
96
+ const region = children !== void 0;
97
+ const enter = (event) => {
98
+ if (disabled || !carriesFiles(event)) return;
99
+ event.preventDefault();
100
+ depth.current += 1;
101
+ setOver(true);
102
+ };
103
+ const leave = (event) => {
104
+ if (disabled || !carriesFiles(event)) return;
105
+ depth.current = Math.max(0, depth.current - 1);
106
+ if (depth.current === 0) setOver(false);
107
+ };
108
+ const overIt = (event) => {
109
+ if (disabled || !carriesFiles(event)) return;
110
+ event.preventDefault();
111
+ event.dataTransfer.dropEffect = "copy";
112
+ };
113
+ const drop = (event) => {
114
+ if (disabled || !carriesFiles(event)) return;
115
+ event.preventDefault();
116
+ depth.current = 0;
117
+ setOver(false);
118
+ void droppedFiles(event.dataTransfer).then((files) => {
119
+ if (files.length > 0) onFiles(files);
120
+ });
121
+ };
122
+ return /* @__PURE__ */ jsx(
123
+ "div",
124
+ {
125
+ ref,
126
+ className: cx(
127
+ "crewlet-file-drop",
128
+ region ? "is-region" : "is-target",
129
+ over && "is-over",
130
+ disabled && "is-disabled",
131
+ className
132
+ ),
133
+ onDragEnter: enter,
134
+ onDragLeave: leave,
135
+ onDragOver: overIt,
136
+ onDrop: drop,
137
+ ...rest,
138
+ children: region ? /* @__PURE__ */ jsxs(Fragment, { children: [
139
+ children,
140
+ over ? /* @__PURE__ */ jsxs("div", { className: "crewlet-file-drop__overlay", "aria-hidden": "true", children: [
141
+ /* @__PURE__ */ jsx("span", { className: "crewlet-file-drop__title", children: title }),
142
+ hint ? /* @__PURE__ */ jsx("span", { className: "crewlet-file-drop__hint", children: hint }) : null
143
+ ] }) : null
144
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
145
+ /* @__PURE__ */ jsx("span", { className: "crewlet-file-drop__title", children: title }),
146
+ hint ? /* @__PURE__ */ jsx("span", { className: "crewlet-file-drop__hint", children: hint }) : null,
147
+ actions ? /* @__PURE__ */ jsx("div", { className: "crewlet-file-drop__actions", children: actions }) : null
148
+ ] })
149
+ }
150
+ );
151
+ }
152
+ );
153
+
154
+ export { FileDrop, FilePickButton, droppedFiles, pickedFiles };
@@ -2,7 +2,7 @@ import { ThemeSwatch } from './chunk-MNFU2RNT.js';
2
2
  import { useHighlightRest } from './chunk-F73MC7EZ.js';
3
3
  import { useRoving, tabStop } from './chunk-RKKO7QHH.js';
4
4
  import { cx } from './chunk-ODEQTPFM.js';
5
- import './ThemeGallery-NITASULQ.css';
5
+ import './ThemeGallery-HSUKBDWY.css';
6
6
  import { useId } from 'react';
7
7
  import { themeGroups, themeRegistry } from '@crewlethq/tokens';
8
8
  import { jsx, jsxs } from 'react/jsx-runtime';
@@ -16,6 +16,7 @@ function ThemeGallery({
16
16
  groups = themeGroups,
17
17
  columns = 3,
18
18
  disabled = false,
19
+ renderPreview,
19
20
  className,
20
21
  style,
21
22
  ...rest
@@ -77,7 +78,7 @@ function ThemeGallery({
77
78
  onKeyDown: (event) => onKeyDown(event, true),
78
79
  onMouseEnter: () => preview.rest(theme.id),
79
80
  children: [
80
- /* @__PURE__ */ jsx(ThemeSwatch, { swatch: theme.swatch, size: "card" }),
81
+ renderPreview ? /* @__PURE__ */ jsx("span", { className: "crewlet-theme-gallery__preview", "aria-hidden": "true", children: renderPreview(theme) }) : theme.swatch ? /* @__PURE__ */ jsx(ThemeSwatch, { swatch: theme.swatch, size: "card" }) : null,
81
82
  /* @__PURE__ */ jsx("span", { className: "crewlet-theme-gallery__name", children: theme.name })
82
83
  ]
83
84
  },