@crewlethq/ui 0.23.0 → 0.25.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 (175) hide show
  1. package/README.md +75 -3
  2. package/dist/ArrangeList/index.js +2 -2
  3. package/dist/ArtifactCard/ArtifactCard.d.ts +3 -2
  4. package/dist/ArtifactCard/index.js +1 -1
  5. package/dist/ArtifactCover/ArtifactCover.d.ts +37 -0
  6. package/dist/ArtifactCover/DashboardCover.d.ts +46 -0
  7. package/dist/ArtifactCover/index.d.ts +6 -0
  8. package/dist/ArtifactCover/index.js +14 -0
  9. package/dist/ArtifactCover-D4MBEJ5H.css +67 -0
  10. package/dist/ArtifactIcon/ArtifactIcon.d.ts +29 -0
  11. package/dist/ArtifactIcon/index.d.ts +2 -0
  12. package/dist/ArtifactIcon/index.js +13 -0
  13. package/dist/AvatarPicker/index.js +6 -6
  14. package/dist/Canvas/Canvas.d.ts +13 -2
  15. package/dist/Canvas/index.js +1 -1
  16. package/dist/CanvasBoard/CanvasBoard.d.ts +117 -0
  17. package/dist/CanvasBoard/budget.d.ts +38 -0
  18. package/dist/CanvasBoard/index.d.ts +5 -0
  19. package/dist/CanvasBoard/index.js +10 -0
  20. package/dist/CanvasBoard-ZLPDINSM.css +236 -0
  21. package/dist/CanvasSchematic/CanvasSchematic.d.ts +31 -0
  22. package/dist/CanvasSchematic/index.d.ts +3 -0
  23. package/dist/CanvasSchematic/index.js +2 -0
  24. package/dist/CanvasSchematic-PLB3N46J.css +36 -0
  25. package/dist/ChangeMark/index.js +3 -3
  26. package/dist/CharacterPicker/index.js +2 -2
  27. package/dist/Checkbox/Checkbox.d.ts +6 -0
  28. package/dist/Checkbox/index.js +1 -1
  29. package/dist/{Checkbox-KT7RS6QQ.css → Checkbox-5UORUG3F.css} +16 -0
  30. package/dist/CommentThread/index.js +2 -2
  31. package/dist/DashboardCover-RMINBODB.css +143 -0
  32. package/dist/DataTable/index.js +3 -3
  33. package/dist/DataView/index.js +5 -5
  34. package/dist/DatasetSource/DatasetSource.d.ts +83 -0
  35. package/dist/DatasetSource/index.d.ts +3 -0
  36. package/dist/DatasetSource/index.js +7 -0
  37. package/dist/DatasetSource-47LBDMPR.css +129 -0
  38. package/dist/DocTabsEditor/DocTabsEditor.d.ts +60 -0
  39. package/dist/DocTabsEditor/index.d.ts +3 -0
  40. package/dist/DocTabsEditor/index.js +9 -0
  41. package/dist/DocTabsEditor-RCOWHX5J.css +78 -0
  42. package/dist/DownloadButton/index.js +2 -2
  43. package/dist/HuePicker/index.js +4 -4
  44. package/dist/LinkInterstitial/index.js +2 -2
  45. package/dist/MarkdownEditor/index.js +5 -5
  46. package/dist/MergeConflicts/MergeConflicts.d.ts +51 -0
  47. package/dist/MergeConflicts/index.d.ts +3 -0
  48. package/dist/MergeConflicts/index.js +9 -0
  49. package/dist/MergeConflicts-O44ESH4T.css +77 -0
  50. package/dist/Meter/index.js +1 -1
  51. package/dist/{Meter-5VCSFMZW.css → Meter-ZZRFUEOH.css} +15 -0
  52. package/dist/ObjectHeader/ObjectHeader.d.ts +7 -1
  53. package/dist/ObjectHeader/index.js +1 -1
  54. package/dist/{ObjectHeader-UNVYM2KD.css → ObjectHeader-D7GU7QNI.css} +15 -0
  55. package/dist/PeopleRail/index.js +3 -3
  56. package/dist/PresenceModel/index.d.ts +1 -1
  57. package/dist/PresenceModel/index.js +1 -1
  58. package/dist/PresenceModel/presence.d.ts +10 -2
  59. package/dist/PresenceStack/index.js +3 -3
  60. package/dist/PresenterView/PresenterView.d.ts +52 -0
  61. package/dist/PresenterView/index.d.ts +3 -0
  62. package/dist/PresenterView/index.js +8 -0
  63. package/dist/PresenterView-T2C6ZZ3N.css +103 -0
  64. package/dist/Section/index.js +1 -1
  65. package/dist/{Section-QRUPRIQ2.css → Section-QR3ILQVK.css} +4 -1
  66. package/dist/SegmentedControl/SegmentedControl.d.ts +17 -0
  67. package/dist/SegmentedControl/index.js +4 -4
  68. package/dist/{SegmentedControl-PKBLJVSA.css → SegmentedControl-FLI45IOW.css} +13 -0
  69. package/dist/ShareDialog/index.js +3 -3
  70. package/dist/SheetGrid/SheetGrid.d.ts +24 -2
  71. package/dist/SheetGrid/index.js +11 -7
  72. package/dist/{SheetGrid-4HOXDVIB.css → SheetGrid-J3TKKXZL.css} +87 -17
  73. package/dist/SheetModel/edits.d.ts +7 -1
  74. package/dist/SheetModel/index.js +2 -2
  75. package/dist/SheetModel/labels.d.ts +9 -0
  76. package/dist/SheetModel/types.d.ts +6 -0
  77. package/dist/SlideCompare/SlideCompare.d.ts +52 -0
  78. package/dist/SlideCompare/index.d.ts +3 -0
  79. package/dist/SlideCompare/index.js +7 -0
  80. package/dist/SlideCompare-MGM3U2ME.css +80 -0
  81. package/dist/SlideFilmstrip/SlideFilmstrip.d.ts +50 -0
  82. package/dist/SlideFilmstrip/index.d.ts +3 -0
  83. package/dist/SlideFilmstrip/index.js +8 -0
  84. package/dist/SlideFilmstrip-BCLNAHFX.css +108 -0
  85. package/dist/SlideGrid/SlideGrid.d.ts +32 -0
  86. package/dist/SlideGrid/index.d.ts +3 -0
  87. package/dist/SlideGrid/index.js +5 -0
  88. package/dist/SlideGrid-RT6QEQEE.css +82 -0
  89. package/dist/SlideStage/SlideStage.d.ts +47 -0
  90. package/dist/SlideStage/index.d.ts +5 -0
  91. package/dist/SlideStage/index.js +4 -0
  92. package/dist/SlideStage/useSlideKeys.d.ts +21 -0
  93. package/dist/SlideStage-XIOTMMRU.css +32 -0
  94. package/dist/Switcher/index.js +2 -2
  95. package/dist/TableCells/index.js +2 -2
  96. package/dist/Tabs/index.js +2 -2
  97. package/dist/Tabs/roving.d.ts +1 -0
  98. package/dist/TagGroup/TagGroup.d.ts +16 -1
  99. package/dist/TagGroup/index.js +1 -1
  100. package/dist/{TagGroup-BCQZD7ZD.css → TagGroup-NAXTJHDS.css} +22 -0
  101. package/dist/ThemeGallery/index.js +2 -2
  102. package/dist/ThemeSwitcher/index.js +5 -5
  103. package/dist/TreeCanvas/index.js +2 -2
  104. package/dist/VersionCompare/VersionCompare.d.ts +8 -1
  105. package/dist/VersionCompare/index.js +5 -5
  106. package/dist/VersionTimeline/index.js +1 -1
  107. package/dist/{VersionTimeline-EBJ6MIPJ.css → VersionTimeline-6KRXEEOO.css} +7 -7
  108. package/dist/WidgetFrame/WidgetFrame.d.ts +73 -0
  109. package/dist/WidgetFrame/index.d.ts +3 -0
  110. package/dist/WidgetFrame/index.js +6 -0
  111. package/dist/WidgetFrame-4EFJOJMW.css +127 -0
  112. package/dist/chunk-3D7PSL2L.js +438 -0
  113. package/dist/{chunk-SJCKMSQK.js → chunk-5LUI3WQP.js} +1 -1
  114. package/dist/{chunk-TODND5AD.js → chunk-6J35F334.js} +7 -4
  115. package/dist/{chunk-IASV5UCP.js → chunk-7T42VQQT.js} +3 -1
  116. package/dist/{chunk-BJCFNRGH.js → chunk-BEHFTRMZ.js} +1 -1
  117. package/dist/chunk-BQYQWJPK.js +80 -0
  118. package/dist/{chunk-KLIEPFEZ.js → chunk-C2FRTWKO.js} +1 -1
  119. package/dist/{chunk-APS747CR.js → chunk-EHQTPVV4.js} +5 -3
  120. package/dist/{chunk-A2UXBCLO.js → chunk-FAQGGX2Z.js} +1 -1
  121. package/dist/chunk-FQCLKD7U.js +2 -0
  122. package/dist/{chunk-J4OKGJQ6.js → chunk-FVLCTCMV.js} +1 -1
  123. package/dist/{chunk-XJFOOXCF.js → chunk-HJEVSQN6.js} +1 -1
  124. package/dist/chunk-HONZQ4UR.js +107 -0
  125. package/dist/{chunk-5PSXBL73.js → chunk-HVIX5ECM.js} +1 -1
  126. package/dist/{chunk-36CWC2NH.js → chunk-INTEH4F3.js} +271 -143
  127. package/dist/{chunk-F6NZOVWL.js → chunk-IW55G7XG.js} +6 -2
  128. package/dist/chunk-KNLJL3VP.js +101 -0
  129. package/dist/chunk-KSSSFLE2.js +105 -0
  130. package/dist/{chunk-TXAFKH2A.js → chunk-KZOGQXSP.js} +6 -0
  131. package/dist/{chunk-CS65QSIJ.js → chunk-LZHLSQYK.js} +2 -2
  132. package/dist/chunk-MQEEQVG6.js +62 -0
  133. package/dist/chunk-NBWNLYWZ.js +62 -0
  134. package/dist/{chunk-YYZRLLLY.js → chunk-NHBP2XS5.js} +19 -3
  135. package/dist/chunk-O2EGEONV.js +90 -0
  136. package/dist/{chunk-YM3GJHGW.js → chunk-ODT5H4MN.js} +1 -1
  137. package/dist/{chunk-JCSXFAXG.js → chunk-OJRDEVHV.js} +1 -1
  138. package/dist/{chunk-U63Y5OCW.js → chunk-PRNX5AFE.js} +2 -2
  139. package/dist/chunk-Q4A5CGUB.js +43 -0
  140. package/dist/{chunk-7QL2OFP6.js → chunk-RCQXUT5K.js} +25 -9
  141. package/dist/{chunk-CBOICT64.js → chunk-RKKO7QHH.js} +2 -0
  142. package/dist/{chunk-TBIZSQHB.js → chunk-RSC7UJCR.js} +1 -1
  143. package/dist/chunk-RWA4H2TV.js +73 -0
  144. package/dist/{chunk-OQT7GSKJ.js → chunk-SGIQCI6Y.js} +1 -1
  145. package/dist/{chunk-35UKQ33X.js → chunk-SVW3VG72.js} +2 -2
  146. package/dist/{chunk-4PZ6FJKW.js → chunk-UGNUUVRI.js} +2 -2
  147. package/dist/chunk-UOR7TOAU.js +191 -0
  148. package/dist/{chunk-XMGB3ZAT.js → chunk-URUGEAFB.js} +3 -2
  149. package/dist/chunk-UZ7ZUSLH.js +179 -0
  150. package/dist/chunk-V2Z24W2Z.js +75 -0
  151. package/dist/{chunk-I7WXZOWA.js → chunk-VGE3G3T4.js} +3 -1
  152. package/dist/{chunk-UNZ7TVV5.js → chunk-W4XYY73O.js} +7 -9
  153. package/dist/{chunk-IPM2E6UI.js → chunk-WJDQ7XQA.js} +1 -1
  154. package/dist/{chunk-PPAQFGS2.js → chunk-YEKYVJQR.js} +1 -1
  155. package/dist/chunk-Z54BPRL6.js +74 -0
  156. package/dist/{chunk-S35G6PNC.js → chunk-Z7JYG5TF.js} +5 -4
  157. package/dist/chunk-ZZDY4XL7.js +112 -0
  158. package/dist/generated/index.d.ts +13 -0
  159. package/dist/generated/index.js +92 -79
  160. package/dist/index.js +92 -79
  161. package/dist/styles.css +1644 -153
  162. package/dist/utils/csv.d.ts +18 -0
  163. package/dist/utils/elementToPng.d.ts +49 -0
  164. package/dist/utils/index.d.ts +4 -1
  165. package/dist/utils/index.js +2 -2
  166. package/dist/utils/useDownload.d.ts +14 -4
  167. package/package.json +3 -3
  168. package/dist/chunk-E6UWRXNM.js +0 -52
  169. package/dist/chunk-M54K52ZU.js +0 -2
  170. package/dist/chunk-U5LQ2AS5.js +0 -1
  171. package/dist/{chunk-KG6XRG5Z.js → chunk-5OY4LJFH.js} +1 -1
  172. package/dist/{chunk-ZM7HLFOA.js → chunk-L2OQRPXJ.js} +1 -1
  173. package/dist/{chunk-MUGM7SEG.js → chunk-LFNUH4GY.js} +1 -1
  174. package/dist/{chunk-PBNP7H2C.js → chunk-M2VJGNCD.js} +1 -1
  175. /package/dist/{chunk-MSSZHDZI.js → chunk-SLRKS5PF.js} +0 -0
@@ -1,18 +1,20 @@
1
- import { Tag } from './chunk-GFV4LJHH.js';
2
- import { StatusDot } from './chunk-WIATRXEJ.js';
1
+ import { TagGroup } from './chunk-O2EGEONV.js';
3
2
  import { CursorFlag } from './chunk-3BYIWOTV.js';
4
3
  import { presenceHueClass } from './chunk-I6POAB4V.js';
5
- import { ChangeMark } from './chunk-PPAQFGS2.js';
6
- import { isEmptyValue, displayText, ratingMax, findPerson, findOption, statusTone, editText, textMax, isMod, sheetLabels, normalizeValue, sameValue, spokenText, drawnColumns, sortRows, SHEET_ROW_HEIGHT, columnOffsets, SHEET_ROW_NUMBER_WIDTH, columnWidth, rangeOf, presenceByCell, presenceByRow, presenceByColumn, cellKey, scrollToRow, planClear, visibleRows, cellDescription, nextSort, rowName, inRange, hasPrevious, flagText, toTSV, planPaste, parseTSV, moveFrom, isTypingKey, editorSide, optionHue, checkValue, parseText, liveOptions } from './chunk-YYZRLLLY.js';
7
- import { presenceLabels } from './chunk-S35G6PNC.js';
4
+ import { Tag } from './chunk-GFV4LJHH.js';
5
+ import { StatusDot } from './chunk-WIATRXEJ.js';
6
+ import { ChangeMark } from './chunk-YEKYVJQR.js';
7
+ import { isEmptyValue, displayText, ratingMax, findPerson, findOption, statusTone, editText, textMax, sheetLabels, normalizeValue, sameValue, spokenText, drawnColumns, sortRows, SHEET_ROW_HEIGHT, cellKey, columnOffsets, SHEET_ROW_NUMBER_WIDTH, columnWidth, rangeOf, presenceByCell, presenceByRow, presenceByColumn, scrollToRow, planClear, visibleRows, cellDescription, nextSort, rowName, inRange, hasPrevious, flagText, toTSV, planPaste, parseTSV, isMod, moveFrom, isTypingKey, editorSide, optionHue, parseText, liveOptions, checkValue } from './chunk-NHBP2XS5.js';
8
+ import { presenceLabels } from './chunk-Z7JYG5TF.js';
8
9
  import { Avatar } from './chunk-5OM447RW.js';
10
+ import { Menu } from './chunk-UFKUWVMV.js';
9
11
  import { Button } from './chunk-OG6CL3EB.js';
10
12
  import { isComposing } from './chunk-R25RGPUB.js';
11
13
  import { VisuallyHidden } from './chunk-BZFWDIV6.js';
12
14
  import { cx } from './chunk-ODEQTPFM.js';
13
- import './SheetGrid-4HOXDVIB.css';
15
+ import './SheetGrid-J3TKKXZL.css';
14
16
  import { useId, useRef, useMemo, useState, useLayoutEffect, useEffect, useCallback } from 'react';
15
- import { CheckGlyph, StarGlyph, CircleAlertGlyph, ArrowUpGlyph, ArrowDownGlyph, RotateCcwClockGlyph, PlusGlyph } from '@crewlethq/icons/glyphs';
17
+ import { CheckGlyph, StarGlyph, CircleAlertGlyph, ArrowUpGlyph, ArrowDownGlyph, TrashGlyph, EllipsisGlyph, RotateCcwClockGlyph, PlusGlyph } from '@crewlethq/icons/glyphs';
16
18
  import { isCrewletCharacter } from '@crewlethq/icons/characters';
17
19
  import { jsxs, jsx } from 'react/jsx-runtime';
18
20
 
@@ -57,7 +59,7 @@ function CellDisplay({ column, value, context, onOpenLink }) {
57
59
  case "select":
58
60
  return /* @__PURE__ */ jsx(OptionChip, { column, id: String(value), labels });
59
61
  case "multiselect":
60
- return /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-cell__chips", children: value.map((id) => /* @__PURE__ */ jsx(OptionChip, { column, id, labels }, id)) });
62
+ return /* @__PURE__ */ jsx(TagGroup, { label: column.label, overflow: "fit", className: "crewlet-sheet-cell__chips", children: value.map((id) => /* @__PURE__ */ jsx(OptionChip, { column, id, labels }, id)) });
61
63
  case "status": {
62
64
  const option = findOption(column, String(value));
63
65
  const words = displayText(column, value, context);
@@ -98,17 +100,27 @@ function CellDisplay({ column, value, context, onOpenLink }) {
98
100
  }
99
101
  }
100
102
  var LINE_TYPES = /* @__PURE__ */ new Set(["text", "number", "url", "date", "rating"]);
103
+ var TEXT_TYPES = /* @__PURE__ */ new Set(["text", "longtext"]);
104
+ var TEXT_EDITOR_LINES = 6;
105
+ var LIST_FILTER_FROM = 8;
101
106
  var LIST_TYPES = /* @__PURE__ */ new Set(["select", "multiselect", "status", "person"]);
102
107
  var hasEditor = (type) => type !== "checkbox";
103
108
  var takesTyping = (type) => LINE_TYPES.has(type) || LIST_TYPES.has(type) || type === "longtext";
104
109
  function endingMove(event, multiline) {
105
110
  if (event.key === "Tab") return event.shiftKey ? "left" : "right";
106
111
  if (event.key === "Enter") {
107
- if (multiline && !isMod(event)) return void 0;
112
+ if (multiline) return event.shiftKey && !isMod(event) ? void 0 : "down";
108
113
  return event.shiftKey ? "up" : "down";
109
114
  }
110
115
  return void 0;
111
116
  }
117
+ function focusInPlace(el, caretAtEnd) {
118
+ if (!el) return;
119
+ el.focus({ preventScroll: true });
120
+ if (caretAtEnd && (el instanceof HTMLInputElement || el instanceof HTMLTextAreaElement)) {
121
+ el.setSelectionRange(el.value.length, el.value.length);
122
+ }
123
+ }
112
124
  function useLeave(end) {
113
125
  const done = useRef(false);
114
126
  return {
@@ -148,7 +160,7 @@ function ErrorNote({ id, message }) {
148
160
  function CellEditor(props) {
149
161
  const { column } = props;
150
162
  if (LIST_TYPES.has(column.type)) return /* @__PURE__ */ jsx(ListEditor, { ...props });
151
- if (column.type === "longtext") return /* @__PURE__ */ jsx(LongTextEditor, { ...props });
163
+ if (TEXT_TYPES.has(column.type)) return /* @__PURE__ */ jsx(TextEditor, { ...props });
152
164
  return /* @__PURE__ */ jsx(LineEditor, { ...props });
153
165
  }
154
166
  function LineEditor({ column, value, context, seed, label, onCommit, onCancel, conflict }) {
@@ -166,10 +178,7 @@ function LineEditor({ column, value, context, seed, label, onCommit, onCancel, c
166
178
  const [draft, setDraft] = useState(initial);
167
179
  const [error, setError] = useState(null);
168
180
  useLayoutEffect(() => {
169
- const el = input.current;
170
- if (!el) return;
171
- el.focus();
172
- if (!isDate) el.setSelectionRange(el.value.length, el.value.length);
181
+ focusInPlace(input.current, !isDate);
173
182
  }, [isDate]);
174
183
  const commit = (move) => {
175
184
  const text = isDate && column.time && draft !== "" ? new Date(draft).toISOString() : draft;
@@ -226,18 +235,22 @@ function LineEditor({ column, value, context, seed, label, onCommit, onCancel, c
226
235
  ] }) : null
227
236
  ] });
228
237
  }
229
- function LongTextEditor({ column, value, context, seed, label, onCommit, onCancel, side = "below", conflict }) {
238
+ function TextEditor({ column, value, context, seed, label, onCommit, onCancel, side = "below", conflict }) {
230
239
  const { labels } = context;
240
+ const long = column.type === "longtext";
231
241
  const errorId = useId();
232
242
  const area = useRef(null);
233
243
  const [draft, setDraft] = useState(() => seed ?? editText(column, value, context));
234
244
  const [error, setError] = useState(null);
235
245
  useLayoutEffect(() => {
236
- const el = area.current;
237
- if (!el) return;
238
- el.focus();
239
- el.setSelectionRange(el.value.length, el.value.length);
246
+ focusInPlace(area.current, true);
240
247
  }, []);
248
+ useLayoutEffect(() => {
249
+ const el = area.current;
250
+ if (!el || el.scrollHeight === 0) return;
251
+ el.style.height = "auto";
252
+ el.style.height = `${el.scrollHeight}px`;
253
+ }, [draft]);
241
254
  const commit = (move) => {
242
255
  const parsed = parseText(column, draft, context);
243
256
  if (!parsed.ok) {
@@ -251,43 +264,52 @@ function LongTextEditor({ column, value, context, seed, label, onCommit, onCance
251
264
  const leave = useLeave(() => {
252
265
  if (!commit(null)) onCancel();
253
266
  });
254
- return /* @__PURE__ */ jsx("div", { className: cx("crewlet-sheet-editor", "crewlet-sheet-editor--long", `crewlet-sheet-editor--${side}`), onBlur: leave.onBlur, children: /* @__PURE__ */ jsxs("div", { className: "crewlet-sheet-editor__panel crewlet-sheet-editor__panel--long", children: [
255
- /* @__PURE__ */ jsx(
256
- "textarea",
257
- {
258
- ref: area,
259
- className: "crewlet-sheet-editor__textarea",
260
- "aria-label": label,
261
- "aria-invalid": error !== null,
262
- "aria-describedby": error !== null ? errorId : void 0,
263
- maxLength: textMax(column),
264
- rows: 5,
265
- value: draft,
266
- onChange: (event) => {
267
- setDraft(event.target.value);
268
- setError(null);
269
- },
270
- onKeyDown: (event) => {
271
- if (isComposing(event)) return;
272
- if (event.key === "Escape") {
273
- event.preventDefault();
274
- event.stopPropagation();
275
- leave.finish();
276
- onCancel();
277
- return;
267
+ return /* @__PURE__ */ jsx(
268
+ "div",
269
+ {
270
+ className: cx("crewlet-sheet-editor", "crewlet-sheet-editor--text", long && "crewlet-sheet-editor--long", `crewlet-sheet-editor--${side}`),
271
+ onBlur: leave.onBlur,
272
+ children: /* @__PURE__ */ jsxs("div", { className: "crewlet-sheet-editor__overlay", children: [
273
+ /* @__PURE__ */ jsx(
274
+ "textarea",
275
+ {
276
+ ref: area,
277
+ className: "crewlet-sheet-editor__textarea",
278
+ "aria-label": label,
279
+ "aria-multiline": long,
280
+ "aria-invalid": error !== null,
281
+ "aria-describedby": error !== null ? errorId : void 0,
282
+ maxLength: textMax(column),
283
+ rows: 1,
284
+ style: { "--crewlet-sheet-editor-lines": TEXT_EDITOR_LINES },
285
+ value: draft,
286
+ onChange: (event) => {
287
+ setDraft(long ? event.target.value : event.target.value.replace(/\r?\n/g, " "));
288
+ setError(null);
289
+ },
290
+ onKeyDown: (event) => {
291
+ if (isComposing(event)) return;
292
+ if (event.key === "Escape") {
293
+ event.preventDefault();
294
+ event.stopPropagation();
295
+ leave.finish();
296
+ onCancel();
297
+ return;
298
+ }
299
+ if (event.key === "Tab" && conflict) return;
300
+ const move = endingMove(event, long);
301
+ if (move === void 0) return;
302
+ event.preventDefault();
303
+ event.stopPropagation();
304
+ commit(move);
305
+ }
278
306
  }
279
- if (event.key === "Tab" && conflict) return;
280
- const move = endingMove(event, true);
281
- if (move === void 0) return;
282
- event.preventDefault();
283
- event.stopPropagation();
284
- commit(move);
285
- }
286
- }
287
- ),
288
- error !== null ? /* @__PURE__ */ jsx(ErrorNote, { id: errorId, message: error }) : null,
289
- conflict ? /* @__PURE__ */ jsx(ConflictNote, { conflict, labels }) : null
290
- ] }) });
307
+ ),
308
+ error !== null ? /* @__PURE__ */ jsx(ErrorNote, { id: errorId, message: error }) : null,
309
+ conflict ? /* @__PURE__ */ jsx(ConflictNote, { conflict, labels }) : null
310
+ ] })
311
+ }
312
+ );
291
313
  }
292
314
  function choicesOf(column, context) {
293
315
  if (column.type === "person") return (context.people ?? []).map((person) => ({ id: person.id, label: person.name, person }));
@@ -298,34 +320,39 @@ function ChoiceFace({ column, choice, context }) {
298
320
  if (column.type === "status" && choice.option) return /* @__PURE__ */ jsx(StatusDot, { tone: statusTone(choice.option), children: choice.label });
299
321
  return /* @__PURE__ */ jsx(OptionChip, { column, id: choice.id, labels: context.labels });
300
322
  }
323
+ var CLEAR = "";
301
324
  function ListEditor({ column, value, context, seed, label, onCommit, onCancel, side = "below", conflict }) {
302
325
  const { labels } = context;
303
326
  const multiple = column.type === "multiselect";
304
327
  const listId = useId();
305
328
  const errorId = useId();
306
329
  const filterRef = useRef(null);
307
- const [filter, setFilter] = useState(seed ?? "");
330
+ const listRef = useRef(null);
331
+ const all = useMemo(() => choicesOf(column, context), [column, context]);
332
+ const filterable = all.length > LIST_FILTER_FROM;
333
+ const [filter, setFilter] = useState(filterable ? seed ?? "" : "");
308
334
  const [chosen, setChosen] = useState(
309
335
  () => multiple ? [...value ?? []] : typeof value === "string" ? [value] : []
310
336
  );
311
337
  const [error, setError] = useState(null);
312
- const all = useMemo(() => choicesOf(column, context), [column, context]);
313
338
  const clearable = !multiple && !column.required;
314
339
  const shown = useMemo(() => {
315
340
  const wanted = filter.trim().toLowerCase();
316
341
  const matching = wanted === "" ? all : all.filter((choice) => choice.label.toLowerCase().includes(wanted));
317
- return clearable && wanted === "" ? [{ id: "", label: labels.clear }, ...matching] : matching;
342
+ return clearable && wanted === "" ? [...matching, { id: CLEAR, label: labels.clear }] : matching;
318
343
  }, [all, filter, clearable, labels.clear]);
319
344
  const [highlight, setHighlight] = useState(() => {
320
- const at = shown.findIndex((choice) => choice.id !== "" && chosen.includes(choice.id));
345
+ const typed = !filterable && seed ? shown.findIndex((choice) => choice.id !== CLEAR && choice.label.toLowerCase().startsWith(seed.toLowerCase())) : -1;
346
+ if (typed >= 0) return typed;
347
+ const at = shown.findIndex((choice) => choice.id !== CLEAR && chosen.includes(choice.id));
321
348
  return at >= 0 ? at : 0;
322
349
  });
323
350
  useEffect(() => {
324
351
  setHighlight((at) => Math.min(at, Math.max(shown.length - 1, 0)));
325
352
  }, [shown.length]);
326
353
  useLayoutEffect(() => {
327
- filterRef.current?.focus();
328
- }, []);
354
+ focusInPlace(filterable ? filterRef.current : listRef.current, false);
355
+ }, [filterable]);
329
356
  const finish = (next, move) => {
330
357
  const problem = checkValue(column, next, context);
331
358
  if (problem !== null) {
@@ -342,15 +369,76 @@ function ListEditor({ column, value, context, seed, label, onCommit, onCancel, s
342
369
  const pick = (choice, move) => {
343
370
  if (!choice) return;
344
371
  if (!multiple) {
345
- finish(choice.id === "" ? null : choice.id, move);
372
+ finish(choice.id === CLEAR ? null : choice.id, move);
346
373
  return;
347
374
  }
348
375
  setError(null);
349
376
  setChosen((current) => current.includes(choice.id) ? current.filter((id) => id !== choice.id) : [...current, choice.id]);
350
377
  };
351
378
  const optionId = (index) => `${listId}-${index}`;
379
+ const active = shown.length > 0 ? optionId(highlight) : void 0;
380
+ const onListKeys = (event) => {
381
+ if (isComposing(event)) return;
382
+ switch (event.key) {
383
+ case "Escape":
384
+ event.preventDefault();
385
+ event.stopPropagation();
386
+ leave.finish();
387
+ onCancel();
388
+ return;
389
+ case "ArrowDown":
390
+ event.preventDefault();
391
+ event.stopPropagation();
392
+ setHighlight((at) => Math.min(at + 1, shown.length - 1));
393
+ return;
394
+ case "ArrowUp":
395
+ event.preventDefault();
396
+ event.stopPropagation();
397
+ setHighlight((at) => Math.max(at - 1, 0));
398
+ return;
399
+ case "Home":
400
+ case "End":
401
+ if (filter !== "") return;
402
+ event.preventDefault();
403
+ event.stopPropagation();
404
+ setHighlight(event.key === "Home" ? 0 : shown.length - 1);
405
+ return;
406
+ case "Enter":
407
+ event.preventDefault();
408
+ event.stopPropagation();
409
+ if (multiple && isMod(event)) finish(chosen, "down");
410
+ else pick(shown[highlight], event.shiftKey ? "up" : "down");
411
+ return;
412
+ case " ":
413
+ if (filterable) return;
414
+ event.preventDefault();
415
+ event.stopPropagation();
416
+ pick(shown[highlight], multiple ? null : "down");
417
+ return;
418
+ case "Tab":
419
+ if (conflict) return;
420
+ event.preventDefault();
421
+ event.stopPropagation();
422
+ if (multiple) finish(chosen, event.shiftKey ? "left" : "right");
423
+ else finish(value, event.shiftKey ? "left" : "right");
424
+ return;
425
+ default:
426
+ if (!filterable && event.key.length === 1 && !event.ctrlKey && !event.metaKey && !event.altKey) {
427
+ const key = event.key.toLowerCase();
428
+ const from = highlight + 1;
429
+ const order = [...shown.slice(from), ...shown.slice(0, from)];
430
+ const hit = order.find((choice) => choice.id !== CLEAR && choice.label.toLowerCase().startsWith(key));
431
+ if (hit) {
432
+ event.preventDefault();
433
+ event.stopPropagation();
434
+ setHighlight(shown.indexOf(hit));
435
+ }
436
+ }
437
+ return;
438
+ }
439
+ };
352
440
  return /* @__PURE__ */ jsx("div", { className: cx("crewlet-sheet-editor", "crewlet-sheet-editor--list", `crewlet-sheet-editor--${side}`), onBlur: leave.onBlur, children: /* @__PURE__ */ jsxs("div", { className: "crewlet-sheet-editor__panel crewlet-sheet-editor__panel--list", children: [
353
- /* @__PURE__ */ jsx(
441
+ filterable ? /* @__PURE__ */ jsx(
354
442
  "input",
355
443
  {
356
444
  ref: filterRef,
@@ -360,7 +448,7 @@ function ListEditor({ column, value, context, seed, label, onCommit, onCancel, s
360
448
  "aria-expanded": "true",
361
449
  "aria-controls": listId,
362
450
  "aria-autocomplete": "list",
363
- "aria-activedescendant": shown.length > 0 ? optionId(highlight) : void 0,
451
+ "aria-activedescendant": active,
364
452
  "aria-invalid": error !== null,
365
453
  "aria-describedby": error !== null ? errorId : void 0,
366
454
  placeholder: labels.filter,
@@ -369,78 +457,53 @@ function ListEditor({ column, value, context, seed, label, onCommit, onCancel, s
369
457
  setFilter(event.target.value);
370
458
  setHighlight(0);
371
459
  },
372
- onKeyDown: (event) => {
373
- if (isComposing(event)) return;
374
- switch (event.key) {
375
- case "Escape":
376
- event.preventDefault();
377
- event.stopPropagation();
378
- leave.finish();
379
- onCancel();
380
- return;
381
- case "ArrowDown":
382
- event.preventDefault();
383
- event.stopPropagation();
384
- setHighlight((at) => Math.min(at + 1, shown.length - 1));
385
- return;
386
- case "ArrowUp":
387
- event.preventDefault();
388
- event.stopPropagation();
389
- setHighlight((at) => Math.max(at - 1, 0));
390
- return;
391
- case "Home":
392
- case "End":
393
- if (filter !== "") return;
394
- event.preventDefault();
395
- event.stopPropagation();
396
- setHighlight(event.key === "Home" ? 0 : shown.length - 1);
397
- return;
398
- case "Enter":
399
- event.preventDefault();
400
- event.stopPropagation();
401
- if (multiple && isMod(event)) finish(chosen, "down");
402
- else pick(shown[highlight], event.shiftKey ? "up" : "down");
403
- return;
404
- case "Tab":
405
- if (conflict) return;
406
- event.preventDefault();
407
- event.stopPropagation();
408
- if (multiple) finish(chosen, event.shiftKey ? "left" : "right");
409
- else finish(value, event.shiftKey ? "left" : "right");
410
- return;
411
- default:
412
- return;
413
- }
414
- }
460
+ onKeyDown: onListKeys
461
+ }
462
+ ) : null,
463
+ /* @__PURE__ */ jsx(
464
+ "ul",
465
+ {
466
+ ref: listRef,
467
+ className: "crewlet-sheet-editor__list",
468
+ id: listId,
469
+ role: "listbox",
470
+ "aria-label": label,
471
+ "aria-multiselectable": multiple || void 0,
472
+ tabIndex: -1,
473
+ "aria-activedescendant": filterable ? void 0 : active,
474
+ "aria-invalid": filterable ? void 0 : error !== null,
475
+ "aria-describedby": !filterable && error !== null ? errorId : void 0,
476
+ onKeyDown: filterable ? void 0 : onListKeys,
477
+ children: shown.length === 0 ? /* @__PURE__ */ jsx("li", { className: "crewlet-sheet-editor__empty", role: "presentation", children: labels.noOptions }) : shown.map((choice, index) => {
478
+ const selected = choice.id === CLEAR ? chosen.length === 0 : chosen.includes(choice.id);
479
+ return (
480
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events -- focus stays on the combobox or the list, whose keys move and pick through aria-activedescendant
481
+ /* @__PURE__ */ jsxs(
482
+ "li",
483
+ {
484
+ id: optionId(index),
485
+ role: "option",
486
+ "aria-selected": selected,
487
+ className: cx(
488
+ "crewlet-sheet-editor__option",
489
+ choice.id === CLEAR && "crewlet-sheet-editor__option--clear",
490
+ index === highlight && "crewlet-sheet-editor__option--highlight",
491
+ selected && "crewlet-sheet-editor__option--selected"
492
+ ),
493
+ onMouseDown: (event) => event.preventDefault(),
494
+ onMouseEnter: () => setHighlight(index),
495
+ onClick: () => pick(choice, multiple ? null : "down"),
496
+ children: [
497
+ /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-editor__mark", "aria-hidden": "true", children: selected && choice.id !== CLEAR ? /* @__PURE__ */ jsx(CheckGlyph, { size: "xs" }) : null }),
498
+ choice.id === CLEAR ? /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-cell__muted", children: choice.label }) : /* @__PURE__ */ jsx(ChoiceFace, { column, choice, context })
499
+ ]
500
+ },
501
+ choice.id === CLEAR ? "\0clear" : choice.id
502
+ )
503
+ );
504
+ })
415
505
  }
416
506
  ),
417
- /* @__PURE__ */ jsx("ul", { className: "crewlet-sheet-editor__list", id: listId, role: "listbox", "aria-label": label, "aria-multiselectable": multiple || void 0, children: shown.length === 0 ? /* @__PURE__ */ jsx("li", { className: "crewlet-sheet-editor__empty", role: "presentation", children: labels.noOptions }) : shown.map((choice, index) => {
418
- const selected = choice.id === "" ? chosen.length === 0 : chosen.includes(choice.id);
419
- return (
420
- // eslint-disable-next-line jsx-a11y/click-events-have-key-events -- focus stays on the combobox, whose keys move and pick through aria-activedescendant
421
- /* @__PURE__ */ jsxs(
422
- "li",
423
- {
424
- id: optionId(index),
425
- role: "option",
426
- "aria-selected": selected,
427
- className: cx(
428
- "crewlet-sheet-editor__option",
429
- index === highlight && "crewlet-sheet-editor__option--highlight",
430
- selected && "crewlet-sheet-editor__option--selected"
431
- ),
432
- onMouseDown: (event) => event.preventDefault(),
433
- onMouseEnter: () => setHighlight(index),
434
- onClick: () => pick(choice, multiple ? null : "down"),
435
- children: [
436
- /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-editor__mark", "aria-hidden": "true", children: selected ? /* @__PURE__ */ jsx(CheckGlyph, { size: "xs" }) : null }),
437
- choice.id === "" ? /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-cell__muted", children: choice.label }) : /* @__PURE__ */ jsx(ChoiceFace, { column, choice, context })
438
- ]
439
- },
440
- choice.id === "" ? "\0clear" : choice.id
441
- )
442
- );
443
- }) }),
444
507
  error !== null ? /* @__PURE__ */ jsx(ErrorNote, { id: errorId, message: error }) : null,
445
508
  conflict ? /* @__PURE__ */ jsx(ConflictNote, { conflict, labels }) : null
446
509
  ] }) });
@@ -572,6 +635,9 @@ function SheetGrid({
572
635
  sync,
573
636
  onRetry,
574
637
  height,
638
+ fit = "container",
639
+ onDeleteRow,
640
+ onDismissRefused,
575
641
  locale,
576
642
  className,
577
643
  labels: labelOverrides,
@@ -584,6 +650,7 @@ function SheetGrid({
584
650
  const columns = useMemo(() => drawnColumns(declared), [declared]);
585
651
  const rows = useMemo(() => sortRows(columns, given, sort, context), [columns, given, sort, context]);
586
652
  const editable = mode === "edit" && onEdit !== void 0;
653
+ const rowActions = mode === "edit" && onDeleteRow !== void 0;
587
654
  const scroller = useRef(null);
588
655
  const probe = useRef(null);
589
656
  const grid = useRef(null);
@@ -599,7 +666,23 @@ function SheetGrid({
599
666
  const [local, setLocal] = useState(/* @__PURE__ */ new Map());
600
667
  const [announcement, setAnnouncement] = useState({ text: "", n: 0 });
601
668
  const dragging = useRef(false);
669
+ const [hoveredRow, setHoveredRow] = useState(null);
670
+ const [rowMenu, setRowMenu] = useState(null);
602
671
  const say = useCallback((text) => setAnnouncement((current2) => ({ text, n: current2.n + 1 })), []);
672
+ const heardRefusals = useRef(/* @__PURE__ */ new Map());
673
+ useEffect(() => {
674
+ const now = /* @__PURE__ */ new Map();
675
+ for (const row of given) {
676
+ for (const [field, reason] of Object.entries(row.refused ?? {})) now.set(cellKey(row.id, field), reason);
677
+ }
678
+ for (const [key, reason] of now) {
679
+ if (heardRefusals.current.get(key) !== reason) {
680
+ say(labels.notSaved(reason));
681
+ break;
682
+ }
683
+ }
684
+ heardRefusals.current = now;
685
+ }, [given, labels, say]);
603
686
  useLayoutEffect(() => {
604
687
  const measure = () => {
605
688
  const row = probe.current?.offsetHeight ?? 0;
@@ -617,7 +700,10 @@ function SheetGrid({
617
700
  }, []);
618
701
  const offsets = useMemo(() => columnOffsets(columns), [columns]);
619
702
  const template = useMemo(
620
- () => [`${SHEET_ROW_NUMBER_WIDTH}px`, ...columns.map((column) => `${columnWidth(column)}px`)].join(" "),
703
+ () => [
704
+ `${SHEET_ROW_NUMBER_WIDTH}px`,
705
+ ...columns.map((column, index) => index === columns.length - 1 ? `minmax(${columnWidth(column)}px, 1fr)` : `${columnWidth(column)}px`)
706
+ ].join(" "),
621
707
  [columns]
622
708
  );
623
709
  const totalWidth = useMemo(() => columns.reduce((sum, column) => sum + columnWidth(column), SHEET_ROW_NUMBER_WIDTH), [columns]);
@@ -893,6 +979,11 @@ function SheetGrid({
893
979
  focusCell();
894
980
  return;
895
981
  }
982
+ if (rowActions && currentCell.rowId !== null && (event.key === "ContextMenu" || event.shiftKey && event.key === "F10")) {
983
+ event.preventDefault();
984
+ setRowMenu(currentCell.rowId);
985
+ return;
986
+ }
896
987
  const moved = moveFrom(current, event, { rows: rows.length, cols: columns.length, page });
897
988
  if (moved) {
898
989
  event.preventDefault();
@@ -1007,19 +1098,21 @@ function SheetGrid({
1007
1098
  "--crewlet-sheet-row-height": `${rowHeight}px`,
1008
1099
  width: `${totalWidth}px`
1009
1100
  };
1101
+ const contentHeight = headerHeight + rows.length * rowHeight + 2;
1102
+ const scrollerStyle = fit === "content" ? { height: `${height !== void 0 ? Math.min(contentHeight, height) : contentHeight}px` } : height !== void 0 ? { height: `${height}px` } : void 0;
1010
1103
  const side = (rowAt) => {
1011
1104
  const top = headerHeight + rowAt * rowHeight - scrollTop;
1012
1105
  const view = viewport > 0 ? viewport : rowHeight * 12;
1013
1106
  return editorSide(top, top + rowHeight, headerHeight, view, rowHeight * 8);
1014
1107
  };
1015
- return /* @__PURE__ */ jsxs("div", { className: cx("crewlet-sheet-grid", mode === "read" && "crewlet-sheet-grid--read", className), children: [
1108
+ return /* @__PURE__ */ jsxs("div", { className: cx("crewlet-sheet-grid", mode === "read" && "crewlet-sheet-grid--read", fit === "content" && "crewlet-sheet-grid--fit-content", className), children: [
1016
1109
  /* @__PURE__ */ jsx("div", { ref: probe, className: "crewlet-sheet-grid__probe", "aria-hidden": "true" }),
1017
1110
  /* @__PURE__ */ jsx(
1018
1111
  "div",
1019
1112
  {
1020
1113
  ref: scroller,
1021
1114
  className: "crewlet-sheet-grid__scroller",
1022
- style: height !== void 0 ? { height: `${height}px` } : void 0,
1115
+ style: scrollerStyle,
1023
1116
  onScroll: (event) => setScrollTop(event.currentTarget.scrollTop),
1024
1117
  children: /* @__PURE__ */ jsxs(
1025
1118
  "div",
@@ -1036,6 +1129,11 @@ function SheetGrid({
1036
1129
  className: "crewlet-sheet-grid__grid",
1037
1130
  style,
1038
1131
  onKeyDown,
1132
+ onMouseMove: rowActions ? (event) => {
1133
+ const over = event.target.closest("[data-row-id]")?.dataset["rowId"] ?? null;
1134
+ setHoveredRow((was) => was === over ? was : over);
1135
+ } : void 0,
1136
+ onMouseLeave: rowActions ? () => setHoveredRow(null) : void 0,
1039
1137
  onCopy,
1040
1138
  onCut,
1041
1139
  onPaste,
@@ -1089,6 +1187,7 @@ function SheetGrid({
1089
1187
  "aria-rowindex": index + 2,
1090
1188
  className: "crewlet-sheet-grid__row",
1091
1189
  style: { top: `${index * rowHeight}px` },
1190
+ "data-row-id": row.id,
1092
1191
  children: [
1093
1192
  /* @__PURE__ */ jsxs(
1094
1193
  "div",
@@ -1098,7 +1197,26 @@ function SheetGrid({
1098
1197
  className: cx("crewlet-sheet-grid__rownum", rowPresence && "crewlet-sheet-grid__rownum--present", rowPresence && presenceHueClass(rowPresence.hue)),
1099
1198
  children: [
1100
1199
  /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: index + 1 }),
1101
- /* @__PURE__ */ jsx(VisuallyHidden, { children: rowName(columns, row, index, context) })
1200
+ /* @__PURE__ */ jsx(VisuallyHidden, { children: rowName(columns, row, index, context) }),
1201
+ rowActions && (rowMenu === row.id || hoveredRow === row.id || current?.row === index) ? /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-grid__row-actions", children: /* @__PURE__ */ jsx(
1202
+ Menu,
1203
+ {
1204
+ label: labels.rowActions(index + 1),
1205
+ icon: /* @__PURE__ */ jsx(EllipsisGlyph, { size: "xs" }),
1206
+ triggerTabIndex: -1,
1207
+ open: rowMenu === row.id,
1208
+ onOpenChange: (open) => setRowMenu(open ? row.id : null),
1209
+ items: [
1210
+ {
1211
+ key: "delete",
1212
+ label: labels.deleteRow,
1213
+ icon: /* @__PURE__ */ jsx(TrashGlyph, { size: "sm" }),
1214
+ danger: true,
1215
+ onSelect: () => onDeleteRow?.(row.id)
1216
+ }
1217
+ ]
1218
+ }
1219
+ ) }) : null
1102
1220
  ]
1103
1221
  }
1104
1222
  ),
@@ -1212,14 +1330,24 @@ function SheetGrid({
1212
1330
  }
1213
1331
  ),
1214
1332
  /* @__PURE__ */ jsx(VisuallyHidden, { id: describedId, children: description }),
1215
- /* @__PURE__ */ jsx(VisuallyHidden, { id: helpId, children: labels.keyboardHelp }),
1333
+ /* @__PURE__ */ jsx(VisuallyHidden, { id: helpId, children: rowActions ? `${labels.keyboardHelp} ${labels.rowActionsHelp}` : labels.keyboardHelp }),
1216
1334
  /* @__PURE__ */ jsx(VisuallyHidden, { children: /* @__PURE__ */ jsx("span", { role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsx("span", { children: announcement.text }, announcement.n) }) }),
1217
1335
  onAddRow || sync ? /* @__PURE__ */ jsxs("div", { className: "crewlet-sheet-grid__foot", children: [
1218
1336
  onAddRow && editable ? /* @__PURE__ */ jsx(Button, { size: "small", variant: "ghost", onClick: onAddRow, leadingIcon: /* @__PURE__ */ jsx(PlusGlyph, { size: "sm" }), children: labels.addRow }) : null,
1219
- sync ? /* @__PURE__ */ jsxs("span", { className: cx("crewlet-sheet-grid__sync", (sync.failed ?? 0) > 0 && "crewlet-sheet-grid__sync--failed"), children: [
1220
- /* @__PURE__ */ jsx("span", { children: labels.sync(sync) }),
1221
- (sync.failed ?? 0) > 0 && onRetry ? /* @__PURE__ */ jsx(Button, { size: "small", variant: "ghost", onClick: onRetry, children: labels.retry }) : null
1222
- ] }) : null
1337
+ sync ? /* @__PURE__ */ jsxs(
1338
+ "span",
1339
+ {
1340
+ className: cx(
1341
+ "crewlet-sheet-grid__sync",
1342
+ ((sync.failed ?? 0) > 0 || !sync.offline && (sync.refused ?? 0) > 0) && "crewlet-sheet-grid__sync--failed"
1343
+ ),
1344
+ children: [
1345
+ /* @__PURE__ */ jsx("span", { children: labels.sync(sync) }),
1346
+ (sync.failed ?? 0) > 0 && onRetry ? /* @__PURE__ */ jsx(Button, { size: "small", variant: "ghost", onClick: onRetry, children: labels.retry }) : null,
1347
+ (sync.failed ?? 0) === 0 && (sync.refused ?? 0) > 0 && onDismissRefused ? /* @__PURE__ */ jsx(Button, { size: "small", variant: "ghost", onClick: onDismissRefused, children: labels.dismiss }) : null
1348
+ ]
1349
+ }
1350
+ ) : null
1223
1351
  ] }) : null
1224
1352
  ] });
1225
1353
  }
@@ -13,13 +13,17 @@ function useDownload(options = {}) {
13
13
  return { state, attempt: outcome.attempt, download };
14
14
  }
15
15
  function downloadText(text, filename, mime = DEFAULT_MIME) {
16
+ if (typeof Blob === "undefined") return { started: false, name: safeFilename(filename) };
17
+ return downloadBlob(new Blob([text], { type: mime }), filename);
18
+ }
19
+ function downloadBlob(blob, filename) {
16
20
  const name = safeFilename(filename);
17
21
  if (typeof URL === "undefined" || typeof URL.createObjectURL !== "function" || typeof HTMLAnchorElement === "undefined" || !("download" in HTMLAnchorElement.prototype)) {
18
22
  return { started: false, name };
19
23
  }
20
24
  let url = "";
21
25
  try {
22
- url = URL.createObjectURL(new Blob([text], { type: mime }));
26
+ url = URL.createObjectURL(blob);
23
27
  const link = document.createElement("a");
24
28
  link.href = url;
25
29
  link.download = name;
@@ -65,4 +69,4 @@ function safeFilename(name) {
65
69
  return cutToBytes(stem, MAX_FILENAME - bytes(extension)) + extension;
66
70
  }
67
71
 
68
- export { downloadText, safeFilename, useDownload };
72
+ export { downloadBlob, downloadText, safeFilename, useDownload };