@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,341 @@
1
+ import { Select } from './chunk-BQ7AKL2N.js';
2
+ import { SHEET_LABELS, SHEET_COLUMN_TYPES, optionHue, retypeColumn } from './chunk-LFSIKGRQ.js';
3
+ import { HueSwatch } from './chunk-SUXP4OBT.js';
4
+ import { NODE_HUES, NODE_HUE_NAMES } from './chunk-AYKXMIOR.js';
5
+ import { Checkbox } from './chunk-CPEPST4S.js';
6
+ import { FormField, Input } from './chunk-6QFAK2WV.js';
7
+ import { Kbd } from './chunk-SMBKXWML.js';
8
+ import { Menu } from './chunk-BUVCV3WI.js';
9
+ import { Button } from './chunk-OG6CL3EB.js';
10
+ import { IconButton } from './chunk-4FQT6ZAY.js';
11
+ import { VisuallyHidden } from './chunk-BZFWDIV6.js';
12
+ import { cx } from './chunk-ODEQTPFM.js';
13
+ import './SheetColumnEditor-BO2HKFRB.css';
14
+ import { useId, useRef, useState, useEffect } from 'react';
15
+ import { StarGlyph, LinkGlyph, CalendarGlyph, UserGlyph, CircleCheckGlyph, ListChecksGlyph, ListGlyph, CheckGlyph, HashGlyph, TextAlignStartGlyph, TypeGlyph, XGlyph, ChevronLeftGlyph, ChevronRightGlyph, TrashGlyph } from '@crewlethq/icons/glyphs';
16
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
17
+
18
+ function SheetColumnTypeGlyph({ type, size = "sm" }) {
19
+ const Glyph = TYPE_GLYPHS[type];
20
+ return /* @__PURE__ */ jsx(Glyph, { size });
21
+ }
22
+ var TYPE_GLYPHS = {
23
+ text: TypeGlyph,
24
+ longtext: TextAlignStartGlyph,
25
+ number: HashGlyph,
26
+ checkbox: CheckGlyph,
27
+ select: ListGlyph,
28
+ multiselect: ListChecksGlyph,
29
+ status: CircleCheckGlyph,
30
+ person: UserGlyph,
31
+ date: CalendarGlyph,
32
+ url: LinkGlyph,
33
+ rating: StarGlyph
34
+ };
35
+ var CATEGORIES = ["todo", "in_progress", "done", "blocked"];
36
+ var SHEET_COLUMN_EDITOR_LABELS = {
37
+ name: "Name",
38
+ nameMissing: "Give the column a name.",
39
+ type: "Type",
40
+ types: SHEET_LABELS.types,
41
+ typeDetails: {
42
+ text: "One line of text",
43
+ longtext: "Paragraphs and notes",
44
+ number: "A figure, a count or an amount",
45
+ checkbox: "Yes or no",
46
+ select: "One choice from a list",
47
+ multiselect: "Any choices from a list",
48
+ status: "Where a row stands",
49
+ person: "A person or an agent",
50
+ date: "A calendar date",
51
+ url: "A web address",
52
+ rating: "A score in stars"
53
+ },
54
+ suggestion: (type) => `Looks like ${type.toLowerCase()}`,
55
+ useType: (type) => `Use ${type}`,
56
+ choices: "Choices",
57
+ addChoice: "Add a choice",
58
+ choiceName: (index) => `Choice ${index}`,
59
+ choiceColor: (choice) => `Color of ${choice}`,
60
+ noColor: "Plain",
61
+ choiceCategory: (choice) => `Category of ${choice}`,
62
+ categories: { todo: "To do", in_progress: "In progress", done: "Done", blocked: "Blocked" },
63
+ removeChoice: (choice) => `Remove ${choice}`,
64
+ noChoices: "Add at least one choice.",
65
+ duplicateChoice: (choice) => `There is already a choice called \u201C${choice}\u201D.`,
66
+ statusStarters: [
67
+ { label: "Not started", category: "todo" },
68
+ { label: "In progress", category: "in_progress" },
69
+ { label: "Done", category: "done" }
70
+ ],
71
+ required: "Required",
72
+ requiredDetail: "A row is not saved with this left empty.",
73
+ stars: "Stars",
74
+ moveBefore: "Move left",
75
+ moveAfter: "Move right",
76
+ deleteColumn: "Delete column",
77
+ done: "Done"
78
+ };
79
+ function defaultOptionId(label, taken) {
80
+ const base = label.normalize("NFKD").replace(/\p{M}+/gu, "").replace(/[^A-Za-z0-9_-]+/g, "_").replace(/^_+|_+$/g, "").toLowerCase().slice(0, 56) || "option";
81
+ if (!taken.includes(base)) return base;
82
+ for (let n = 2; ; n += 1) {
83
+ const id = `${base}_${n}`;
84
+ if (!taken.includes(id)) return id;
85
+ }
86
+ }
87
+ var hasChoices = (type) => type === "select" || type === "multiselect" || type === "status";
88
+ function nextHue(options) {
89
+ const used = new Set(options.map((option) => optionHue(option.color)));
90
+ return NODE_HUES.find((hue) => !used.has(hue)) ?? NODE_HUES[options.length % NODE_HUES.length];
91
+ }
92
+ function SheetColumnEditor({
93
+ column,
94
+ onChange,
95
+ onDone,
96
+ onDelete,
97
+ deleteDisabledReason,
98
+ onMove,
99
+ canMoveBefore = true,
100
+ canMoveAfter = true,
101
+ types = SHEET_COLUMN_TYPES,
102
+ typeLockedReason,
103
+ suggestedType,
104
+ nameError,
105
+ optionId = defaultOptionId,
106
+ selectName = false,
107
+ labels: overrides,
108
+ className
109
+ }) {
110
+ const labels = {
111
+ ...SHEET_COLUMN_EDITOR_LABELS,
112
+ ...overrides,
113
+ types: { ...SHEET_COLUMN_EDITOR_LABELS.types, ...overrides?.types },
114
+ typeDetails: { ...SHEET_COLUMN_EDITOR_LABELS.typeDetails, ...overrides?.typeDetails },
115
+ categories: { ...SHEET_COLUMN_EDITOR_LABELS.categories, ...overrides?.categories }
116
+ };
117
+ const id = useId();
118
+ const nameRef = useRef(null);
119
+ const addRef = useRef(null);
120
+ const [name, setName] = useState(column.label);
121
+ const [draft, setDraft] = useState("");
122
+ const [choiceError, setChoiceError] = useState(null);
123
+ const options = column.options ?? [];
124
+ useEffect(() => {
125
+ setName((now) => now.trim() === column.label ? now : column.label);
126
+ }, [column.label]);
127
+ useEffect(() => {
128
+ if (!selectName) return;
129
+ nameRef.current?.focus({ preventScroll: true });
130
+ nameRef.current?.select();
131
+ }, [selectName]);
132
+ const change = (patch) => onChange({ ...column, ...patch });
133
+ const setType = (type) => {
134
+ if (type === column.type) return;
135
+ const next = retypeColumn(column, type);
136
+ if (hasChoices(type)) {
137
+ const kept = hasChoices(column.type) ? options : [];
138
+ next.options = type === "status" ? kept.length > 0 ? kept.map((option) => ({ ...option, category: option.category ?? "todo" })) : starters(labels.statusStarters, optionId) : kept.map(({ category: _category, ...option }) => ({ ...option, color: option.color ?? nextHue(kept) }));
139
+ }
140
+ if (type === "rating") next.max = 5;
141
+ if (type === "checkbox") delete next.required;
142
+ onChange(next);
143
+ if (hasChoices(type) && type !== "status") window.requestAnimationFrame(() => addRef.current?.focus({ preventScroll: true }));
144
+ };
145
+ const setOption = (index, patch) => change({ options: options.map((option, at) => at === index ? { ...option, ...patch } : option) });
146
+ const addChoice = () => {
147
+ const label = draft.trim();
148
+ if (label === "") return;
149
+ if (options.some((option) => option.label.trim().toLowerCase() === label.toLowerCase())) {
150
+ setChoiceError(labels.duplicateChoice(label));
151
+ return;
152
+ }
153
+ const added = column.type === "status" ? { id: optionId(label, options.map((option) => option.id)), label, category: "todo" } : { id: optionId(label, options.map((option) => option.id)), label, color: nextHue(options) };
154
+ change({ options: [...options, added] });
155
+ setDraft("");
156
+ setChoiceError(null);
157
+ };
158
+ const onNameKey = (event) => {
159
+ if (event.key === "Enter" && !event.nativeEvent.isComposing) {
160
+ event.preventDefault();
161
+ onDone?.();
162
+ }
163
+ };
164
+ const typeName = (type) => labels.types[type];
165
+ const offered = types.includes(column.type) ? types : [column.type, ...types];
166
+ const suggestion = suggestedType && suggestedType !== column.type && !typeLockedReason && types.includes(suggestedType) ? suggestedType : void 0;
167
+ const nameMissing = name.trim() === "";
168
+ return /* @__PURE__ */ jsxs("div", { className: cx("crewlet-sheet-column-editor", className), children: [
169
+ /* @__PURE__ */ jsx(FormField, { label: labels.name, error: nameMissing ? labels.nameMissing : nameError, children: (field) => /* @__PURE__ */ jsx(
170
+ Input,
171
+ {
172
+ ref: nameRef,
173
+ id: field.id,
174
+ "aria-describedby": field.describedBy,
175
+ error: field.invalid,
176
+ inputSize: "sm",
177
+ width: "full",
178
+ value: name,
179
+ maxLength: 120,
180
+ onChange: (event) => {
181
+ setName(event.target.value);
182
+ if (event.target.value.trim() !== "") change({ label: event.target.value.trim() });
183
+ },
184
+ onBlur: () => {
185
+ if (nameMissing) setName(column.label);
186
+ },
187
+ onKeyDown: onNameKey
188
+ }
189
+ ) }),
190
+ /* @__PURE__ */ jsx(FormField, { label: labels.type, helper: typeLockedReason, children: (field) => /* @__PURE__ */ jsx(
191
+ Select,
192
+ {
193
+ id: field.id,
194
+ "aria-describedby": field.describedBy,
195
+ size: "sm",
196
+ width: "full",
197
+ disabled: typeLockedReason !== void 0,
198
+ value: column.type,
199
+ onChange: (value) => {
200
+ if (typeof value === "string" && SHEET_COLUMN_TYPES.includes(value)) setType(value);
201
+ },
202
+ options: offered.map((type) => ({
203
+ value: type,
204
+ label: typeName(type),
205
+ description: labels.typeDetails[type],
206
+ leading: /* @__PURE__ */ jsx(SheetColumnTypeGlyph, { type }),
207
+ text: typeName(type)
208
+ }))
209
+ }
210
+ ) }),
211
+ suggestion ? /* @__PURE__ */ jsxs("div", { className: "crewlet-sheet-column-editor__suggestion", children: [
212
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-sheet-column-editor__suggestion-text", children: [
213
+ /* @__PURE__ */ jsx(SheetColumnTypeGlyph, { type: suggestion, size: "xs" }),
214
+ labels.suggestion(typeName(suggestion))
215
+ ] }),
216
+ /* @__PURE__ */ jsx(Button, { size: "small", variant: "ghost", onClick: () => setType(suggestion), children: labels.useType(typeName(suggestion)) })
217
+ ] }) : null,
218
+ hasChoices(column.type) ? /* @__PURE__ */ jsxs(FormField, { as: "fieldset", label: labels.choices, error: options.length === 0 ? labels.noChoices : choiceError ?? void 0, children: [
219
+ /* @__PURE__ */ jsx("ul", { className: "crewlet-sheet-column-editor__choices", "aria-label": labels.choices, children: options.map((option, index) => /* @__PURE__ */ jsxs("li", { className: "crewlet-sheet-column-editor__choice", children: [
220
+ column.type === "status" ? null : /* @__PURE__ */ jsx(
221
+ Menu,
222
+ {
223
+ label: labels.choiceColor(option.label),
224
+ renderTrigger: (trigger) => /* @__PURE__ */ jsx(
225
+ IconButton,
226
+ {
227
+ ...trigger,
228
+ label: labels.choiceColor(option.label),
229
+ icon: /* @__PURE__ */ jsx(HueSwatch, { hue: optionHue(option.color) === "neutral" ? "none" : optionHue(option.color) }),
230
+ size: "sm"
231
+ }
232
+ ),
233
+ items: [
234
+ { key: "none", label: labels.noColor, icon: /* @__PURE__ */ jsx(HueSwatch, { hue: "none" }), checked: optionHue(option.color) === "neutral", onSelect: () => setOption(index, { color: "neutral" }) },
235
+ ...NODE_HUES.map((hue) => ({
236
+ key: hue,
237
+ label: NODE_HUE_NAMES[hue],
238
+ icon: /* @__PURE__ */ jsx(HueSwatch, { hue }),
239
+ checked: optionHue(option.color) === hue,
240
+ onSelect: () => setOption(index, { color: hue })
241
+ }))
242
+ ]
243
+ }
244
+ ),
245
+ /* @__PURE__ */ jsx(
246
+ Input,
247
+ {
248
+ "aria-label": labels.choiceName(index + 1),
249
+ inputSize: "sm",
250
+ width: "full",
251
+ value: option.label,
252
+ maxLength: 120,
253
+ onChange: (event) => setOption(index, { label: event.target.value }),
254
+ onBlur: (event) => {
255
+ if (event.target.value.trim() === "") setOption(index, { label: option.id });
256
+ }
257
+ }
258
+ ),
259
+ column.type === "status" ? /* @__PURE__ */ jsxs(Fragment, { children: [
260
+ /* @__PURE__ */ jsx(VisuallyHidden, { children: /* @__PURE__ */ jsx("label", { htmlFor: `${id}-category-${index}`, children: labels.choiceCategory(option.label) }) }),
261
+ /* @__PURE__ */ jsx(
262
+ Select,
263
+ {
264
+ id: `${id}-category-${index}`,
265
+ size: "sm",
266
+ width: "auto",
267
+ value: option.category ?? "todo",
268
+ onChange: (value) => setOption(index, { category: value }),
269
+ options: CATEGORIES.map((category) => ({ value: category, label: labels.categories[category] }))
270
+ }
271
+ )
272
+ ] }) : null,
273
+ /* @__PURE__ */ jsx(
274
+ IconButton,
275
+ {
276
+ label: labels.removeChoice(option.label),
277
+ icon: /* @__PURE__ */ jsx(XGlyph, { size: "sm" }),
278
+ size: "sm",
279
+ variant: "ghost-danger",
280
+ onClick: () => change({ options: options.filter((_, at) => at !== index) })
281
+ }
282
+ )
283
+ ] }, option.id)) }),
284
+ /* @__PURE__ */ jsx(
285
+ Input,
286
+ {
287
+ ref: addRef,
288
+ "aria-label": labels.addChoice,
289
+ placeholder: labels.addChoice,
290
+ inputSize: "sm",
291
+ width: "full",
292
+ value: draft,
293
+ maxLength: 120,
294
+ trailing: draft.trim() ? /* @__PURE__ */ jsx(Kbd, { subtle: true, children: "Enter" }) : void 0,
295
+ onChange: (event) => {
296
+ setDraft(event.target.value);
297
+ setChoiceError(null);
298
+ },
299
+ onKeyDown: (event) => {
300
+ if (event.key === "Enter" && !event.nativeEvent.isComposing) {
301
+ event.preventDefault();
302
+ addChoice();
303
+ }
304
+ }
305
+ }
306
+ )
307
+ ] }) : null,
308
+ column.type === "rating" ? /* @__PURE__ */ jsx(FormField, { label: labels.stars, children: (field) => /* @__PURE__ */ jsx(
309
+ Select,
310
+ {
311
+ id: field.id,
312
+ size: "sm",
313
+ width: "auto",
314
+ value: String(column.max ?? 5),
315
+ onChange: (value) => change({ max: Number(value) }),
316
+ options: [3, 4, 5, 6, 7, 8, 9, 10].map((n) => ({ value: String(n), label: String(n) }))
317
+ }
318
+ ) }) : null,
319
+ column.type === "checkbox" ? null : /* @__PURE__ */ jsx(Checkbox, { size: "sm", label: labels.required, description: labels.requiredDetail, checked: column.required ?? false, onChange: (event) => change({ required: event.target.checked || void 0 }) }),
320
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-sheet-column-editor__foot", children: [
321
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-sheet-column-editor__foot-start", children: [
322
+ onMove ? /* @__PURE__ */ jsxs(Fragment, { children: [
323
+ /* @__PURE__ */ jsx(IconButton, { label: labels.moveBefore, icon: /* @__PURE__ */ jsx(ChevronLeftGlyph, { size: "sm" }), size: "sm", disabledReason: canMoveBefore ? void 0 : labels.moveBefore, onClick: () => onMove(-1) }),
324
+ /* @__PURE__ */ jsx(IconButton, { label: labels.moveAfter, icon: /* @__PURE__ */ jsx(ChevronRightGlyph, { size: "sm" }), size: "sm", disabledReason: canMoveAfter ? void 0 : labels.moveAfter, onClick: () => onMove(1) })
325
+ ] }) : null,
326
+ onDelete ? /* @__PURE__ */ jsx(IconButton, { label: labels.deleteColumn, icon: /* @__PURE__ */ jsx(TrashGlyph, { size: "sm" }), size: "sm", variant: "ghost-danger", disabledReason: deleteDisabledReason, onClick: onDelete }) : null
327
+ ] }),
328
+ onDone ? /* @__PURE__ */ jsx(Button, { size: "small", variant: "primary", onClick: onDone, children: labels.done }) : null
329
+ ] })
330
+ ] });
331
+ }
332
+ function starters(list, optionId) {
333
+ const taken = [];
334
+ return list.map(({ label, category }) => {
335
+ const id = optionId(label, taken);
336
+ taken.push(id);
337
+ return { id, label, category };
338
+ });
339
+ }
340
+
341
+ export { SHEET_COLUMN_EDITOR_LABELS, SheetColumnEditor, SheetColumnTypeGlyph };
@@ -17,6 +17,18 @@ var SHEET_COLUMN_TYPES = [
17
17
  ];
18
18
  var isSheetColumnType = (value) => typeof value === "string" && SHEET_COLUMN_TYPES.includes(value);
19
19
 
20
+ // src/SheetModel/columns.ts
21
+ var capsLength = (type) => type === "text" || type === "longtext";
22
+ function retypeColumn(column, type) {
23
+ const next = { field: column.field, label: column.label, type };
24
+ if (column.width !== void 0) next.width = column.width;
25
+ if (column.frozen !== void 0) next.frozen = column.frozen;
26
+ if (column.readOnly !== void 0) next.readOnly = column.readOnly;
27
+ if (column.required !== void 0) next.required = column.required;
28
+ if (column.maxLength !== void 0 && capsLength(column.type) && capsLength(type)) next.maxLength = column.maxLength;
29
+ return next;
30
+ }
31
+
20
32
  // src/SheetModel/labels.ts
21
33
  var TYPE_NAMES = {
22
34
  text: "Text",
@@ -83,7 +95,10 @@ var SHEET_LABELS = {
83
95
  noOptions: "No matching options",
84
96
  clear: "Clear",
85
97
  keyboardHelp: "Arrow keys move between cells. Enter or F2 edits a cell, Escape cancels, and typing replaces its value. Space toggles a checkbox. Control or Command with C and V copies and pastes. Control or Command with Alt and H opens the history of a cell.",
86
- sortBy: (column) => `Sort by ${column}`
98
+ sortBy: (column) => `Sort by ${column}`,
99
+ columnSettings: (column) => `Column settings for ${column}`,
100
+ columnSettingsHelp: "Enter on a column header opens the settings of that column.",
101
+ addColumn: "Add column"
87
102
  };
88
103
  function sheetLabels(overrides) {
89
104
  return { ...SHEET_LABELS, ...overrides, types: { ...SHEET_LABELS.types, ...overrides?.types } };
@@ -883,4 +898,4 @@ function cellSpeech(column, row, context) {
883
898
  return spokenText(column, normalizeValue(column, row.values[column.field]), context);
884
899
  }
885
900
 
886
- export { LONGTEXT_MAX_DEFAULT, RATING_MAX_CEILING, RATING_MAX_DEFAULT, SHEET_COLUMN_BOUNDS, SHEET_COLUMN_TYPES, SHEET_COLUMN_WIDTHS, SHEET_FIT_SAMPLES, SHEET_LABELS, SHEET_ROW_HEIGHT, SHEET_ROW_NUMBER_WIDTH, SheetPendingEdits, TEXT_MAX_DEFAULT, URL_MAX, cellDescription, cellKey, cellSpeech, changeText, changeTime, checkValue, columnOffsets, columnWidth, compareValues, displayText, drawnColumns, editText, editorSide, findOption, findPerson, fitSamples, fittedWidth, flagText, hasPrevious, inRange, isEmptyValue, isMod, isMultiCell, isSheetColumnType, isTypingKey, liveOptions, moveFrom, nextSort, normalizeValue, optionHue, parseOptionList, parseTSV, parseText, planClear, planPaste, presenceByCell, presenceByColumn, presenceByRow, rangeOf, ratingMax, refusalText, rowName, sameValue, scrollToRow, sheetLabels, sortRows, spokenText, statusTone, textMax, toTSV, visibleRows };
901
+ export { LONGTEXT_MAX_DEFAULT, RATING_MAX_CEILING, RATING_MAX_DEFAULT, SHEET_COLUMN_BOUNDS, SHEET_COLUMN_TYPES, SHEET_COLUMN_WIDTHS, SHEET_FIT_SAMPLES, SHEET_LABELS, SHEET_ROW_HEIGHT, SHEET_ROW_NUMBER_WIDTH, SheetPendingEdits, TEXT_MAX_DEFAULT, URL_MAX, cellDescription, cellKey, cellSpeech, changeText, changeTime, checkValue, columnOffsets, columnWidth, compareValues, displayText, drawnColumns, editText, editorSide, findOption, findPerson, fitSamples, fittedWidth, flagText, hasPrevious, inRange, isEmptyValue, isMod, isMultiCell, isSheetColumnType, isTypingKey, liveOptions, moveFrom, nextSort, normalizeValue, optionHue, parseOptionList, parseTSV, parseText, planClear, planPaste, presenceByCell, presenceByColumn, presenceByRow, rangeOf, ratingMax, refusalText, retypeColumn, rowName, sameValue, scrollToRow, sheetLabels, sortRows, spokenText, statusTone, textMax, toTSV, visibleRows };
@@ -2,11 +2,12 @@ import { isApplePlatform, Kbd } from './chunk-SMBKXWML.js';
2
2
  import { Tooltip } from './chunk-6ZJ3N7QI.js';
3
3
  import { SegmentedControl } from './chunk-CCAXYHQS.js';
4
4
  import { ArtifactIcon } from './chunk-ILDNJHRU.js';
5
- import { Menu } from './chunk-3EB2AV3H.js';
5
+ import { Menu } from './chunk-BUVCV3WI.js';
6
6
  import { Button } from './chunk-OG6CL3EB.js';
7
7
  import { IconButton } from './chunk-4FQT6ZAY.js';
8
8
  import { cx } from './chunk-ODEQTPFM.js';
9
- import './ArtifactBar-C462VD4C.css';
9
+ import './ArtifactKinds-CVD3FCYO.css';
10
+ import './ArtifactBar-HV5AT4RG.css';
10
11
  import { useId, useState, useRef, useEffect } from 'react';
11
12
  import { ChevronLeftGlyph, EllipsisGlyph, ChevronDownGlyph } from '@crewlethq/icons/glyphs';
12
13
  import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
@@ -82,7 +83,7 @@ function ArtifactBar({
82
83
  /* @__PURE__ */ jsxs("div", { className: "crewlet-artifact-bar__lead", children: [
83
84
  start,
84
85
  back ? /* @__PURE__ */ jsx(Tooltip, { content: back.label, placement: "bottom", children: /* @__PURE__ */ jsx(IconButton, { asChild: true, label: back.label, size: "sm", variant: "ghost", title: "", children: /* @__PURE__ */ jsx("a", { href: back.href, children: /* @__PURE__ */ jsx(ChevronLeftGlyph, { size: "sm" }) }) }) }) : null,
85
- /* @__PURE__ */ jsx("span", { className: "crewlet-artifact-bar__glyph", role: "img", "aria-label": kindLabel, title: kindLabel, children: /* @__PURE__ */ jsx(ArtifactIcon, { icon: icon ?? void 0, kind, size: "sm" }) }),
86
+ /* @__PURE__ */ jsx("span", { className: cx("crewlet-artifact-bar__glyph", `crewlet-kind--${kind}`), role: "img", "aria-label": kindLabel, title: kindLabel, children: /* @__PURE__ */ jsx(ArtifactIcon, { icon: icon ?? void 0, kind, size: "sm" }) }),
86
87
  pending ? /* @__PURE__ */ jsx("span", { className: "crewlet-artifact-bar__placeholder", "aria-hidden": true }) : /* @__PURE__ */ jsx(
87
88
  BarTitle,
88
89
  {
@@ -175,8 +176,12 @@ function LabelledTool({ tool, apple }) {
175
176
  leadingIcon: tool.icon,
176
177
  disabledReason: tool.disabledReason,
177
178
  "aria-keyshortcuts": shortcut,
179
+ "aria-label": tool.shortLabel ? tool.label : void 0,
178
180
  onClick: tool.onSelect,
179
- children: tool.label
181
+ children: tool.shortLabel ? /* @__PURE__ */ jsxs(Fragment, { children: [
182
+ /* @__PURE__ */ jsx("span", { className: "crewlet-artifact-bar__word-full", children: tool.label }),
183
+ /* @__PURE__ */ jsx("span", { className: "crewlet-artifact-bar__word-short", children: tool.shortLabel })
184
+ ] }) : tool.label
180
185
  }
181
186
  ) });
182
187
  }
@@ -0,0 +1,52 @@
1
+ import { VisuallyHidden } from './chunk-BZFWDIV6.js';
2
+ import { cx } from './chunk-ODEQTPFM.js';
3
+ import './FindingList-PMYA2R6Q.css';
4
+ import { CircleAlertGlyph, TriangleAlertGlyph } from '@crewlethq/icons/glyphs';
5
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
6
+
7
+ var TONE_LABELS = {
8
+ danger: "Problem",
9
+ warning: "Advice"
10
+ };
11
+ function FindingList({
12
+ findings,
13
+ label,
14
+ toneLabels,
15
+ className
16
+ }) {
17
+ const words = { ...TONE_LABELS, ...toneLabels };
18
+ const ordered = [
19
+ ...findings.filter((f) => f.tone === "danger"),
20
+ ...findings.filter((f) => f.tone === "warning")
21
+ ];
22
+ return /* @__PURE__ */ jsx("ul", { className: cx("crewlet-finding-list", className), "aria-label": label, children: ordered.map((f) => {
23
+ const body = /* @__PURE__ */ jsxs(Fragment, { children: [
24
+ /* @__PURE__ */ jsx(
25
+ "span",
26
+ {
27
+ className: cx("crewlet-finding-list__glyph", `is-${f.tone}`),
28
+ "aria-hidden": "true",
29
+ children: f.tone === "danger" ? /* @__PURE__ */ jsx(CircleAlertGlyph, { size: "sm" }) : /* @__PURE__ */ jsx(TriangleAlertGlyph, { size: "sm" })
30
+ }
31
+ ),
32
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-finding-list__text", children: [
33
+ f.location ? /* @__PURE__ */ jsx("span", { className: "crewlet-finding-list__location", children: f.location }) : null,
34
+ /* @__PURE__ */ jsxs("span", { className: "crewlet-finding-list__message", children: [
35
+ /* @__PURE__ */ jsx(VisuallyHidden, { children: `${words[f.tone]}: ` }),
36
+ f.message
37
+ ] })
38
+ ] })
39
+ ] });
40
+ return /* @__PURE__ */ jsx("li", { className: "crewlet-finding-list__item", children: f.onSelect ? /* @__PURE__ */ jsx(
41
+ "button",
42
+ {
43
+ type: "button",
44
+ className: "crewlet-finding-list__row",
45
+ onClick: f.onSelect,
46
+ children: body
47
+ }
48
+ ) : /* @__PURE__ */ jsx("div", { className: "crewlet-finding-list__row", children: body }) }, f.key);
49
+ }) });
50
+ }
51
+
52
+ export { FindingList };
@@ -22,6 +22,91 @@ function text(value) {
22
22
  function quote(written) {
23
23
  return /[",\r\n]/.test(written) ? `"${written.replace(/"/g, '""')}"` : written;
24
24
  }
25
+ var DELIMITERS = [",", ";", " "];
26
+ function parseCsv(text2, options = {}) {
27
+ const source = text2.charCodeAt(0) === 65279 ? text2.slice(1) : text2;
28
+ const delimiter = options.delimiter ?? sniffDelimiter(source);
29
+ const maxRows = options.maxRows ?? Number.POSITIVE_INFINITY;
30
+ const rows = [];
31
+ let record = [];
32
+ let value = "";
33
+ let quoted = false;
34
+ let wasQuoted = false;
35
+ let line = 1;
36
+ let quoteLine = 1;
37
+ let i = 0;
38
+ const endField = () => {
39
+ record.push(undefused(value));
40
+ value = "";
41
+ wasQuoted = false;
42
+ };
43
+ const endRecord = () => {
44
+ endField();
45
+ rows.push(record);
46
+ record = [];
47
+ return rows.length <= maxRows;
48
+ };
49
+ while (i < source.length) {
50
+ const ch = source[i];
51
+ if (quoted) {
52
+ if (ch === '"') {
53
+ if (source[i + 1] === '"') {
54
+ value += '"';
55
+ i += 2;
56
+ continue;
57
+ }
58
+ quoted = false;
59
+ i += 1;
60
+ continue;
61
+ }
62
+ if (ch === "\n" || ch === "\r" && source[i + 1] !== "\n") line += 1;
63
+ value += ch;
64
+ i += 1;
65
+ continue;
66
+ }
67
+ if (ch === '"' && value === "" && !wasQuoted) {
68
+ quoted = true;
69
+ wasQuoted = true;
70
+ quoteLine = line;
71
+ i += 1;
72
+ continue;
73
+ }
74
+ if (ch === delimiter) {
75
+ endField();
76
+ i += 1;
77
+ continue;
78
+ }
79
+ if (ch === "\r" || ch === "\n") {
80
+ const step = ch === "\r" && source[i + 1] === "\n" ? 2 : 1;
81
+ if (!endRecord()) return { ok: false, problem: "too_many_rows", line };
82
+ line += 1;
83
+ i += step;
84
+ continue;
85
+ }
86
+ value += ch;
87
+ i += 1;
88
+ }
89
+ if (quoted) return { ok: false, problem: "unterminated_quote", line: quoteLine };
90
+ if (value !== "" || wasQuoted || record.length > 0) {
91
+ if (!endRecord()) return { ok: false, problem: "too_many_rows", line };
92
+ }
93
+ return { ok: true, rows, delimiter };
94
+ }
95
+ function sniffDelimiter(source) {
96
+ const counts = new Map(DELIMITERS.map((d) => [d, 0]));
97
+ let quoted = false;
98
+ for (const ch of source) {
99
+ if (ch === '"') quoted = !quoted;
100
+ else if (!quoted && (ch === "\n" || ch === "\r")) break;
101
+ else if (!quoted && counts.has(ch)) counts.set(ch, counts.get(ch) + 1);
102
+ }
103
+ let best = ",";
104
+ for (const d of DELIMITERS) if (counts.get(d) > counts.get(best)) best = d;
105
+ return best;
106
+ }
107
+ function undefused(field2) {
108
+ return field2.length > 1 && field2[0] === "'" && FORMULA_LEAD.test(field2.slice(1)) ? field2.slice(1) : field2;
109
+ }
25
110
 
26
111
  // src/utils/elementToPng.ts
27
112
  var ElementToPngError = class extends Error {
@@ -188,4 +273,4 @@ function toBlob(canvas) {
188
273
  });
189
274
  }
190
275
 
191
- export { ElementToPngError, elementToPng, serializeElement, toCsv };
276
+ export { ElementToPngError, elementToPng, parseCsv, serializeElement, toCsv };
@@ -1,5 +1,5 @@
1
1
  import { useMediaMatch } from './chunk-RTPQDNW3.js';
2
- import { Menu } from './chunk-3EB2AV3H.js';
2
+ import { Menu } from './chunk-BUVCV3WI.js';
3
3
  import { useModalLayer, focusables } from './chunk-R25RGPUB.js';
4
4
  import { IconButton } from './chunk-4FQT6ZAY.js';
5
5
  import { VisuallyHidden } from './chunk-BZFWDIV6.js';