@crewlethq/ui 0.33.0 → 0.34.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 (69) hide show
  1. package/README.md +18 -0
  2. package/dist/ArtifactBar/index.js +6 -4
  3. package/dist/ArtifactCard/ArtifactCard.d.ts +51 -11
  4. package/dist/ArtifactCard/index.d.ts +2 -2
  5. package/dist/ArtifactCard/index.js +3 -1
  6. package/dist/ArtifactCard-XHRLAL7C.css +289 -0
  7. package/dist/ArtifactCover/index.js +4 -2
  8. package/dist/ArtifactIcon/index.js +4 -2
  9. package/dist/ArtifactKindIllustration/ArtifactKindIllustration.d.ts +53 -0
  10. package/dist/ArtifactKindIllustration/index.d.ts +3 -0
  11. package/dist/ArtifactKindIllustration/index.js +3 -0
  12. package/dist/ArtifactKindIllustration-4NG6IE3C.css +211 -0
  13. package/dist/Canvas/index.js +2 -2
  14. package/dist/CanvasBoard/index.js +4 -4
  15. package/dist/CanvasToolbar/index.js +3 -3
  16. package/dist/ChangeMark/index.js +3 -3
  17. package/dist/FactList/index.js +2 -2
  18. package/dist/LivePreview/LivePreview.d.ts +83 -0
  19. package/dist/LivePreview/budget.d.ts +50 -0
  20. package/dist/LivePreview/index.d.ts +5 -0
  21. package/dist/LivePreview/index.js +2 -0
  22. package/dist/LivePreview-SRO7KHO4.css +65 -0
  23. package/dist/RichEditor/index.js +2 -2
  24. package/dist/SandboxedFrame/index.js +2 -2
  25. package/dist/Select/Select.d.ts +10 -0
  26. package/dist/Select/index.d.ts +1 -1
  27. package/dist/Select/index.js +1 -1
  28. package/dist/{Select-2P7AWUE4.css → Select-A54YS5EP.css} +29 -0
  29. package/dist/ShareDialog/index.js +2 -2
  30. package/dist/SheetGrid/SheetGrid.d.ts +4 -2
  31. package/dist/SheetGrid/index.js +4 -4
  32. package/dist/{SheetGrid-AKPUCK67.css → SheetGrid-GDPCCZ4V.css} +52 -11
  33. package/dist/SheetModel/index.js +1 -1
  34. package/dist/SheetModel/labels.d.ts +1 -1
  35. package/dist/Switcher/index.js +2 -2
  36. package/dist/TimeWindowPicker/index.js +2 -2
  37. package/dist/Tooltip/Tooltip.d.ts +12 -1
  38. package/dist/Tooltip/index.d.ts +1 -1
  39. package/dist/Tooltip/index.js +1 -1
  40. package/dist/TreeCanvas/index.js +3 -3
  41. package/dist/{chunk-22UEIXZE.js → chunk-3P4Y5RX6.js} +1 -1
  42. package/dist/{chunk-GLSOD5Z4.js → chunk-3Y56GQPM.js} +1 -1
  43. package/dist/{chunk-5FYPE5GL.js → chunk-6ZJ3N7QI.js} +5 -2
  44. package/dist/{chunk-2DHMVB5J.js → chunk-AORVEG5V.js} +1 -1
  45. package/dist/{chunk-UQGFHNDI.js → chunk-BQ7AKL2N.js} +3 -2
  46. package/dist/{chunk-ORT5IKHP.js → chunk-CCLNGVCM.js} +1 -1
  47. package/dist/{chunk-UNXLTJU7.js → chunk-CFAJP777.js} +1 -1
  48. package/dist/{chunk-G4ZW7A2W.js → chunk-EWAFN2IU.js} +33 -34
  49. package/dist/{chunk-7WHDPLVJ.js → chunk-HOIZ6MIS.js} +1 -1
  50. package/dist/{chunk-Z6GFMI2Z.js → chunk-IUD5VA2Z.js} +2 -2
  51. package/dist/chunk-KF6YGL4B.js +160 -0
  52. package/dist/{chunk-HH2GKAAE.js → chunk-PLDUNXAO.js} +1 -1
  53. package/dist/{chunk-ZNBLFCQS.js → chunk-SGHIAGPR.js} +2 -2
  54. package/dist/chunk-UDQW6UK7.js +129 -0
  55. package/dist/{chunk-4IJS5CPF.js → chunk-UHJQDFKQ.js} +1 -1
  56. package/dist/{chunk-J6ZSH3F5.js → chunk-VI5FWDZW.js} +2 -2
  57. package/dist/{chunk-MDNVHIB2.js → chunk-WPX6A5BV.js} +2 -2
  58. package/dist/chunk-XTOVADJU.js +170 -0
  59. package/dist/generated/index.d.ts +2 -0
  60. package/dist/generated/index.js +48 -46
  61. package/dist/index.js +48 -46
  62. package/dist/styles.css +544 -27
  63. package/package.json +3 -3
  64. package/dist/ArtifactCard-AS5Z4CVI.css +0 -124
  65. package/dist/chunk-DFZMYB5X.js +0 -108
  66. package/dist/{chunk-3U7S6ZUN.js → chunk-356JNSER.js} +1 -1
  67. package/dist/{chunk-MTMKIUWF.js → chunk-E4NNGC6X.js} +1 -1
  68. package/dist/{chunk-O37AQNLK.js → chunk-GMU6SQ6B.js} +1 -1
  69. /package/dist/{chunk-2JJIN6JN.js → chunk-YCARLYI6.js} +0 -0
@@ -194,25 +194,56 @@
194
194
  box-shadow: 0 0 0 2px var(--crewlet-presence-hue);
195
195
  }
196
196
 
197
+ /* THE GUTTER: one centred stack, so the number and the row's actions button
198
+ take exactly the same place and swapping one for the other moves nothing.
199
+ Its width is the component's fixed track (SHEET_ROW_NUMBER_WIDTH), which
200
+ holds five tabular digits, so row 9 and row 10000 stand in one column. */
197
201
  .crewlet-sheet-grid__rownum {
202
+ display: grid;
203
+ grid-template-areas: 'gutter';
204
+ place-items: center;
198
205
  color: var(--color-text-tertiary);
199
206
  font-size: var(--font-size-xs);
200
207
  font-variant-numeric: tabular-nums;
201
208
  }
202
209
 
203
- /* The row actions sit at the number's trailing edge, over the cell's own
204
- background, so the number stays readable beside them. */
210
+ .crewlet-sheet-grid__rownum-number,
211
+ .crewlet-sheet-grid__row-actions {
212
+ grid-area: gutter;
213
+ }
214
+
215
+ /* THE ACTIONS BUTTON IS ALWAYS THERE, drawn clear until it is wanted, so a
216
+ tap on the number opens the row's menu on a screen with no pointer to
217
+ hover with. It shows, and the number gives way to it, under the pointer,
218
+ on the row holding focus (the active cell, or the button itself) and while
219
+ its menu is open. A row the reader has not entered keeps its number. */
205
220
  .crewlet-sheet-grid__row-actions {
206
- position: absolute;
207
- top: 50%;
208
- inset-inline-end: 0;
209
221
  display: inline-flex;
210
- transform: translateY(-50%);
222
+ opacity: 0;
223
+ }
224
+
225
+ .crewlet-sheet-grid__row:is(:hover, :focus-within) > .crewlet-sheet-grid__rownum > .crewlet-sheet-grid__row-actions,
226
+ .crewlet-sheet-grid__rownum--open > .crewlet-sheet-grid__row-actions {
227
+ opacity: 1;
211
228
  }
212
229
 
213
- /* The active row's actions and the active cell's history are the reader's
214
- tools while they are in the grid, not marks on a sheet nobody has entered. */
215
- .crewlet-sheet-grid__grid:not(:focus-within) .crewlet-sheet-grid__row-actions--active-row,
230
+ .crewlet-sheet-grid__row:is(:hover, :focus-within) > .crewlet-sheet-grid__rownum--actions > .crewlet-sheet-grid__rownum-number,
231
+ .crewlet-sheet-grid__rownum--open > .crewlet-sheet-grid__rownum-number {
232
+ visibility: hidden;
233
+ }
234
+
235
+ /* THE SELECTED ROWS' NUMBERS are marked as their cells are, and on the same
236
+ condition (the reader is in the grid, or the selection spans more than one
237
+ cell), the way a spreadsheet lights the row headers of a selection. The
238
+ tint is laid over the gutter's own ground, which a sticky gutter needs: a
239
+ translucent tint alone lets the values scrolling under it show through. */
240
+ .crewlet-sheet-grid__grid:is(:focus-within, .crewlet-sheet-grid__grid--range) .crewlet-sheet-grid__rownum--selected {
241
+ background: linear-gradient(var(--color-brand-accent-soft), var(--color-brand-accent-soft)) var(--color-surface-background);
242
+ color: var(--color-brand-accent-ink);
243
+ }
244
+
245
+ /* The active cell's history is the reader's tool while they are in the grid,
246
+ not a mark on a sheet nobody has entered. */
216
247
  .crewlet-sheet-grid__grid:not(:focus-within) .crewlet-sheet-grid__cell--history:not(:hover) > .crewlet-sheet-grid__history {
217
248
  visibility: hidden;
218
249
  }
@@ -255,9 +286,11 @@
255
286
  /* THE SELECTION IS TINTED WHILE THE READER IS IN THE GRID, or while it
256
287
  spans more than one cell. A grid nobody has entered still holds its first
257
288
  cell as the active one (the keys start there), and tinting it drew a stray
258
- highlight on every sheet as it opened. */
289
+ highlight on every sheet as it opened. The tint is laid over the cell's
290
+ own ground: alone, a translucent tint on a frozen cell let the values
291
+ scrolling under it show through. */
259
292
  .crewlet-sheet-grid__grid:is(:focus-within, .crewlet-sheet-grid__grid--range) .crewlet-sheet-grid__cell--selected {
260
- background: var(--color-brand-accent-soft);
293
+ background: linear-gradient(var(--color-brand-accent-soft), var(--color-brand-accent-soft)) var(--color-surface-background);
261
294
  }
262
295
 
263
296
  /* THE FROZEN EDGE once the grid is scrolled sideways: the columns that stay
@@ -758,6 +791,14 @@
758
791
  color: HighlightText;
759
792
  }
760
793
 
794
+ /* A selected row's number takes the selection's ink on the page's ground:
795
+ on a Highlight ground the browser's backplate behind the digits drew
796
+ them as a solid block. */
797
+ .crewlet-sheet-grid__grid:is(:focus-within, .crewlet-sheet-grid__grid--range) .crewlet-sheet-grid__rownum--selected {
798
+ background: Canvas;
799
+ color: Highlight;
800
+ }
801
+
761
802
  .crewlet-sheet-grid__rownum--present::before {
762
803
  background: Highlight;
763
804
  }
@@ -1,3 +1,3 @@
1
- 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 } from '../chunk-J6ZSH3F5.js';
1
+ 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 } from '../chunk-VI5FWDZW.js';
2
2
  import '../chunk-Z7JYG5TF.js';
3
3
  import '../chunk-AYKXMIOR.js';
@@ -59,7 +59,7 @@ export interface SheetLabels {
59
59
  dismiss: string;
60
60
  /** A refusal, as the grid announces it: "Not saved: <reason>". */
61
61
  notSaved: (reason: string) => string;
62
- /** The row actions control on a row's number, and its delete item. */
62
+ /** The row actions button in a row's number gutter (its name and tooltip), and its delete item. */
63
63
  rowActions: (row: number) => string;
64
64
  deleteRow: string;
65
65
  /** Appended to the keyboard help when a row has actions. */
@@ -1,10 +1,10 @@
1
- export { Switcher } from '../chunk-O37AQNLK.js';
2
- import '../chunk-TEUQWOWZ.js';
1
+ export { Switcher } from '../chunk-GMU6SQ6B.js';
3
2
  import '../chunk-XEGZHHRH.js';
4
3
  import '../chunk-F73MC7EZ.js';
5
4
  import '../chunk-K3IJQNB7.js';
6
5
  import '../chunk-FYTIJM2T.js';
7
6
  import '../chunk-6QFAK2WV.js';
7
+ import '../chunk-TEUQWOWZ.js';
8
8
  import '../chunk-PQADV6P6.js';
9
9
  import '../chunk-RTPQDNW3.js';
10
10
  import '../chunk-OMV5W53A.js';
@@ -1,5 +1,5 @@
1
- export { DEFAULT_TIME_WINDOW_LABELS, TIME_WINDOW_SPANS, TimeWindowPicker, describeTimeWindow, isTimeWindowSet, parseTimeWindowDuration, resolveTimeWindow, timeWindowMatchesPreset } from '../chunk-2DHMVB5J.js';
2
- import '../chunk-UQGFHNDI.js';
1
+ export { DEFAULT_TIME_WINDOW_LABELS, TIME_WINDOW_SPANS, TimeWindowPicker, describeTimeWindow, isTimeWindowSet, parseTimeWindowDuration, resolveTimeWindow, timeWindowMatchesPreset } from '../chunk-AORVEG5V.js';
2
+ import '../chunk-BQ7AKL2N.js';
3
3
  import '../chunk-F73MC7EZ.js';
4
4
  import '../chunk-K3IJQNB7.js';
5
5
  import '../chunk-GXQ3B46I.js';
@@ -1,10 +1,21 @@
1
1
  import { type ReactElement, type ReactNode } from 'react';
2
2
  export type TooltipPlacement = 'top' | 'bottom';
3
+ /**
4
+ * Where along the trigger the panel lines up: `center` on its middle, or
5
+ * `start` on its leading edge, running toward the inline end.
6
+ */
7
+ export type TooltipAlign = 'center' | 'start';
3
8
  export interface TooltipProps {
4
9
  /** One short line. A tooltip is not a place to put a list or a paragraph. */
5
10
  content: ReactNode;
6
11
  /** The preferred side. It flips when there is not room and there is more room the other way. */
7
12
  placement?: TooltipPlacement | undefined;
13
+ /**
14
+ * `start` for a trigger at the leading edge of what it belongs to, such as a
15
+ * grid's row gutter: a centred tip there hangs past that edge, and at the
16
+ * page's edge is pushed off its trigger by the clamp.
17
+ */
18
+ align?: TooltipAlign | undefined;
8
19
  /** Nothing opens: for a trigger whose tip is only worth showing sometimes. */
9
20
  disabled?: boolean | undefined;
10
21
  /** Milliseconds a pointer rests on the trigger before the panel appears. Focus never waits. */
@@ -48,4 +59,4 @@ export interface TooltipProps {
48
59
  * control has a name already, and replacing it with the hint is how a Close
49
60
  * button came to be announced as "Closing is unavailable until this finishes".
50
61
  */
51
- export declare function Tooltip({ content, placement, disabled, openDelay, closeDelay, className, children, }: TooltipProps): import("react").JSX.Element;
62
+ export declare function Tooltip({ content, placement, align, disabled, openDelay, closeDelay, className, children, }: TooltipProps): import("react").JSX.Element;
@@ -1,3 +1,3 @@
1
1
  import './Tooltip.css';
2
2
  export { Tooltip } from './Tooltip.js';
3
- export type { TooltipPlacement, TooltipProps } from './Tooltip.js';
3
+ export type { TooltipAlign, TooltipPlacement, TooltipProps } from './Tooltip.js';
@@ -1,4 +1,4 @@
1
- export { Tooltip } from '../chunk-5FYPE5GL.js';
1
+ export { Tooltip } from '../chunk-6ZJ3N7QI.js';
2
2
  import '../chunk-OMV5W53A.js';
3
3
  import '../chunk-R25RGPUB.js';
4
4
  import '../chunk-ODEQTPFM.js';
@@ -1,9 +1,9 @@
1
- export { TreeCanvas } from '../chunk-4IJS5CPF.js';
1
+ export { TreeCanvas } from '../chunk-UHJQDFKQ.js';
2
2
  import '../chunk-MY374UUD.js';
3
- import '../chunk-22UEIXZE.js';
3
+ import '../chunk-3P4Y5RX6.js';
4
4
  import '../chunk-LM3ISVJA.js';
5
5
  import '../chunk-SMBKXWML.js';
6
- import '../chunk-5FYPE5GL.js';
6
+ import '../chunk-6ZJ3N7QI.js';
7
7
  import '../chunk-OMV5W53A.js';
8
8
  import '../chunk-R25RGPUB.js';
9
9
  import '../chunk-OG6CL3EB.js';
@@ -1,6 +1,6 @@
1
1
  import { LayerNodeBridge } from './chunk-LM3ISVJA.js';
2
2
  import { Kbd } from './chunk-SMBKXWML.js';
3
- import { Tooltip } from './chunk-5FYPE5GL.js';
3
+ import { Tooltip } from './chunk-6ZJ3N7QI.js';
4
4
  import { LAYER_REPOSITION_EVENT, LayerHost } from './chunk-OMV5W53A.js';
5
5
  import { Button } from './chunk-OG6CL3EB.js';
6
6
  import { IconButton } from './chunk-4FQT6ZAY.js';
@@ -1,5 +1,5 @@
1
1
  import { Toolbar } from './chunk-PGRPNDWM.js';
2
- import { ShortcutTip } from './chunk-22UEIXZE.js';
2
+ import { ShortcutTip } from './chunk-3P4Y5RX6.js';
3
3
  import { IconButton } from './chunk-4FQT6ZAY.js';
4
4
  import { cx } from './chunk-ODEQTPFM.js';
5
5
  import './CanvasToolbar-EM5EVZED.css';
@@ -28,6 +28,7 @@ var CLOSE_DELAY = ms(motion.duration.base);
28
28
  function Tooltip({
29
29
  content,
30
30
  placement = "top",
31
+ align = "center",
31
32
  disabled = false,
32
33
  openDelay = OPEN_DELAY,
33
34
  closeDelay = CLOSE_DELAY,
@@ -94,8 +95,10 @@ function Tooltip({
94
95
  const host = inHost ? container.getBoundingClientRect() : viewportBounds();
95
96
  const rect = at.getBoundingClientRect();
96
97
  const own = box.getBoundingClientRect();
98
+ const rtl = getComputedStyle(at).direction === "rtl";
99
+ const startX = align === "center" ? rect.x + rect.width / 2 - own.width / 2 : rtl ? rect.right - own.width : rect.x;
97
100
  const local = {
98
- x: rect.x + rect.width / 2 - own.width / 2 - (inHost ? host.x : 0),
101
+ x: startX - (inHost ? host.x : 0),
99
102
  y: rect.y - (inHost ? host.y : 0),
100
103
  width: rect.width,
101
104
  height: rect.height
@@ -114,7 +117,7 @@ function Tooltip({
114
117
  setPlace(
115
118
  (was) => was && was.left === spot.left && was.top === spot.top && was.side === spot.side ? was : spot
116
119
  );
117
- }, [container, placement]);
120
+ }, [align, container, placement]);
118
121
  useLayoutEffect(() => {
119
122
  if (!open) return;
120
123
  position();
@@ -1,4 +1,4 @@
1
- import { Select } from './chunk-UQGFHNDI.js';
1
+ import { Select } from './chunk-BQ7AKL2N.js';
2
2
  import { Popover } from './chunk-K3IJQNB7.js';
3
3
  import { today, shiftMonth, CalendarGrid, compareDays } from './chunk-GXQ3B46I.js';
4
4
  import { Input } from './chunk-6QFAK2WV.js';
@@ -1,7 +1,7 @@
1
1
  import { useListbox, useOptionKeys, useHighlightRest } from './chunk-F73MC7EZ.js';
2
2
  import { useLayerContainer, viewportBounds, anchorGone, placePopup, LAYER_GAP, LAYER_REPOSITION_EVENT } from './chunk-OMV5W53A.js';
3
3
  import { cx } from './chunk-ODEQTPFM.js';
4
- import './Select-2P7AWUE4.css';
4
+ import './Select-A54YS5EP.css';
5
5
  import { forwardRef, useId, useMemo, useState, useRef, useCallback, useEffect, useLayoutEffect } from 'react';
6
6
  import { createPortal } from 'react-dom';
7
7
  import { ChevronDownGlyph, CheckGlyph, SearchGlyph } from '@crewlethq/icons/glyphs';
@@ -24,6 +24,7 @@ var Select = forwardRef(function Select2({
24
24
  align = "left",
25
25
  active = false,
26
26
  error = false,
27
+ appearance = "field",
27
28
  ariaLabel,
28
29
  searchable = false,
29
30
  searchPlaceholder = "Search",
@@ -37,7 +38,7 @@ var Select = forwardRef(function Select2({
37
38
  }, ref) {
38
39
  const generatedId = useId();
39
40
  const rootId = id ?? generatedId;
40
- const classes = cx(className, width === "auto" && "crewlet-select--auto");
41
+ const classes = cx(className, width === "auto" && "crewlet-select--auto", appearance === "quiet" && "crewlet-select--quiet");
41
42
  const chosen = useMemo(() => {
42
43
  if (multiple) return Array.isArray(value) ? value : [];
43
44
  if (value === void 0 || value === null) return [];
@@ -1,4 +1,4 @@
1
- import { Select } from './chunk-UQGFHNDI.js';
1
+ import { Select } from './chunk-BQ7AKL2N.js';
2
2
  import { CopyButton } from './chunk-W46LPU4R.js';
3
3
  import { Modal } from './chunk-XEGZHHRH.js';
4
4
  import { Callout } from './chunk-SZZRIHGT.js';
@@ -1,5 +1,5 @@
1
1
  import { Document } from './chunk-TYKQ33CL.js';
2
- import { ArtifactKindGlyph } from './chunk-DFZMYB5X.js';
2
+ import { ArtifactKindGlyph } from './chunk-XTOVADJU.js';
3
3
  import { cx } from './chunk-ODEQTPFM.js';
4
4
  import './ArtifactCover-D4MBEJ5H.css';
5
5
  import './DashboardCover-KYGYMRKW.css';
@@ -3,16 +3,18 @@ import { Tag } from './chunk-GFV4LJHH.js';
3
3
  import { StatusDot } from './chunk-WIATRXEJ.js';
4
4
  import { CursorFlag } from './chunk-3BYIWOTV.js';
5
5
  import { presenceHueClass } from './chunk-I6POAB4V.js';
6
- import { ChangeMark } from './chunk-ZNBLFCQS.js';
7
- import { isEmptyValue, displayText, ratingMax, findPerson, findOption, statusTone, editText, textMax, sheetLabels, normalizeValue, sameValue, spokenText, drawnColumns, fitSamples, fittedWidth, columnWidth, SHEET_COLUMN_BOUNDS, sortRows, SHEET_ROW_HEIGHT, cellKey, columnOffsets, SHEET_ROW_NUMBER_WIDTH, 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-J6ZSH3F5.js';
6
+ import { ChangeMark } from './chunk-SGHIAGPR.js';
7
+ import { isEmptyValue, displayText, ratingMax, findPerson, findOption, statusTone, editText, textMax, sheetLabels, normalizeValue, sameValue, spokenText, drawnColumns, fitSamples, fittedWidth, columnWidth, SHEET_COLUMN_BOUNDS, sortRows, SHEET_ROW_HEIGHT, cellKey, columnOffsets, SHEET_ROW_NUMBER_WIDTH, 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-VI5FWDZW.js';
8
8
  import { presenceLabels } from './chunk-Z7JYG5TF.js';
9
+ import { Tooltip } from './chunk-6ZJ3N7QI.js';
9
10
  import { Avatar } from './chunk-PZKHBB7D.js';
10
11
  import { Menu } from './chunk-3EB2AV3H.js';
11
12
  import { isComposing } from './chunk-R25RGPUB.js';
12
13
  import { Button } from './chunk-OG6CL3EB.js';
14
+ import { IconButton } from './chunk-4FQT6ZAY.js';
13
15
  import { VisuallyHidden } from './chunk-BZFWDIV6.js';
14
16
  import { cx } from './chunk-ODEQTPFM.js';
15
- import './SheetGrid-AKPUCK67.css';
17
+ import './SheetGrid-GDPCCZ4V.css';
16
18
  import { useId, useRef, useMemo, useState, useLayoutEffect, useEffect, useCallback } from 'react';
17
19
  import { CheckGlyph, StarGlyph, CircleAlertGlyph, ArrowUpGlyph, ArrowDownGlyph, TrashGlyph, EllipsisGlyph, RotateCcwClockGlyph, PlusGlyph } from '@crewlethq/icons/glyphs';
18
20
  import { isCrewletCharacter } from '@crewlethq/icons/characters';
@@ -720,7 +722,6 @@ function SheetGrid({
720
722
  const [local, setLocal] = useState(/* @__PURE__ */ new Map());
721
723
  const [announcement, setAnnouncement] = useState({ text: "", n: 0 });
722
724
  const dragging = useRef(false);
723
- const [hoveredRow, setHoveredRow] = useState(null);
724
725
  const [rowMenu, setRowMenu] = useState(null);
725
726
  const say = useCallback((text) => setAnnouncement((current2) => ({ text, n: current2.n + 1 })), []);
726
727
  const heardRefusals = useRef(/* @__PURE__ */ new Map());
@@ -1003,6 +1004,7 @@ function SheetGrid({
1003
1004
  const page = Math.max(1, Math.floor(Math.max(viewport - headerHeight, rowHeight) / rowHeight) - 1);
1004
1005
  const onKeyDown = (event) => {
1005
1006
  if (editing || isComposing(event) || !current || !currentCell) return;
1007
+ if (event.target.closest(".crewlet-sheet-grid__row-actions")) return;
1006
1008
  const column = columns[current.col];
1007
1009
  const mod = isMod(event);
1008
1010
  if (mod && event.altKey && event.key.toLowerCase() === "h") {
@@ -1191,11 +1193,6 @@ function SheetGrid({
1191
1193
  className: cx("crewlet-sheet-grid__grid", range && (range.top !== range.bottom || range.left !== range.right) && "crewlet-sheet-grid__grid--range"),
1192
1194
  style,
1193
1195
  onKeyDown,
1194
- onMouseMove: rowActions ? (event) => {
1195
- const over = event.target.closest("[data-row-id]")?.dataset["rowId"] ?? null;
1196
- setHoveredRow((was) => was === over ? was : over);
1197
- } : void 0,
1198
- onMouseLeave: rowActions ? () => setHoveredRow(null) : void 0,
1199
1196
  onCopy,
1200
1197
  onCut,
1201
1198
  onPaste,
@@ -1248,6 +1245,9 @@ function SheetGrid({
1248
1245
  /* @__PURE__ */ jsx("div", { role: "rowgroup", className: "crewlet-sheet-grid__body", style: { height: `${rows.length * rowHeight}px` }, children: drawn.map((index) => {
1249
1246
  const row = rows[index];
1250
1247
  const rowPresence = onRows.get(row.id);
1248
+ const rowNameId = `${id}-r${index}-name`;
1249
+ const rowSelected = range !== null && index >= range.top && index <= range.bottom;
1250
+ const actionsLabel = labels.rowActions(index + 1);
1251
1251
  return /* @__PURE__ */ jsxs(
1252
1252
  "div",
1253
1253
  {
@@ -1262,43 +1262,42 @@ function SheetGrid({
1262
1262
  {
1263
1263
  role: "rowheader",
1264
1264
  "aria-colindex": 1,
1265
+ "aria-labelledby": rowNameId,
1265
1266
  className: cx(
1266
1267
  "crewlet-sheet-grid__rownum",
1267
1268
  lastFrozen < 0 && "crewlet-sheet-grid__frozen-edge",
1269
+ rowActions && "crewlet-sheet-grid__rownum--actions",
1270
+ rowSelected && "crewlet-sheet-grid__rownum--selected",
1271
+ rowMenu === row.id && "crewlet-sheet-grid__rownum--open",
1268
1272
  rowPresence && "crewlet-sheet-grid__rownum--present",
1269
1273
  rowPresence && presenceHueClass(rowPresence.hue)
1270
1274
  ),
1271
1275
  children: [
1272
- /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: index + 1 }),
1273
- /* @__PURE__ */ jsx(VisuallyHidden, { children: rowName(columns, row, index, context) }),
1274
- rowActions && (rowMenu === row.id || hoveredRow === row.id || current?.row === index) ? /* @__PURE__ */ jsx(
1275
- "span",
1276
+ /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-grid__rownum-number", "aria-hidden": "true", children: index + 1 }),
1277
+ /* @__PURE__ */ jsx(VisuallyHidden, { id: rowNameId, children: rowName(columns, row, index, context) }),
1278
+ rowActions ? /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-grid__row-actions", children: /* @__PURE__ */ jsx(
1279
+ Menu,
1276
1280
  {
1277
- className: cx(
1278
- "crewlet-sheet-grid__row-actions",
1279
- rowMenu !== row.id && hoveredRow !== row.id && "crewlet-sheet-grid__row-actions--active-row"
1281
+ label: actionsLabel,
1282
+ triggerTabIndex: -1,
1283
+ open: rowMenu === row.id,
1284
+ onOpenChange: (open) => setRowMenu(open ? row.id : null),
1285
+ renderTrigger: (trigger) => (
1286
+ // Lined up with the button's leading edge: the gutter is the
1287
+ // grid's first column, and a centred tip hung past the grid.
1288
+ /* @__PURE__ */ jsx(Tooltip, { content: actionsLabel, align: "start", children: /* @__PURE__ */ jsx(IconButton, { ...trigger, label: actionsLabel, icon: /* @__PURE__ */ jsx(EllipsisGlyph, {}), size: "sm", title: "" }) })
1280
1289
  ),
1281
- children: /* @__PURE__ */ jsx(
1282
- Menu,
1290
+ items: [
1283
1291
  {
1284
- label: labels.rowActions(index + 1),
1285
- icon: /* @__PURE__ */ jsx(EllipsisGlyph, { size: "xs" }),
1286
- triggerTabIndex: -1,
1287
- open: rowMenu === row.id,
1288
- onOpenChange: (open) => setRowMenu(open ? row.id : null),
1289
- items: [
1290
- {
1291
- key: "delete",
1292
- label: labels.deleteRow,
1293
- icon: /* @__PURE__ */ jsx(TrashGlyph, { size: "sm" }),
1294
- danger: true,
1295
- onSelect: () => onDeleteRow?.(row.id)
1296
- }
1297
- ]
1292
+ key: "delete",
1293
+ label: labels.deleteRow,
1294
+ icon: /* @__PURE__ */ jsx(TrashGlyph, { size: "sm" }),
1295
+ danger: true,
1296
+ onSelect: () => onDeleteRow?.(row.id)
1298
1297
  }
1299
- )
1298
+ ]
1300
1299
  }
1301
- ) : null
1300
+ ) }) : null
1302
1301
  ]
1303
1302
  }
1304
1303
  ),
@@ -1,4 +1,4 @@
1
- import { Tooltip } from './chunk-5FYPE5GL.js';
1
+ import { Tooltip } from './chunk-6ZJ3N7QI.js';
2
2
  import { VisuallyHidden } from './chunk-BZFWDIV6.js';
3
3
  import { cx } from './chunk-ODEQTPFM.js';
4
4
  import './FactList-G2H73NFR.css';
@@ -1,7 +1,7 @@
1
1
  import { isApplePlatform, Kbd } from './chunk-SMBKXWML.js';
2
- import { Tooltip } from './chunk-5FYPE5GL.js';
2
+ import { Tooltip } from './chunk-6ZJ3N7QI.js';
3
3
  import { SegmentedControl } from './chunk-CCAXYHQS.js';
4
- import { ArtifactIcon } from './chunk-HH2GKAAE.js';
4
+ import { ArtifactIcon } from './chunk-PLDUNXAO.js';
5
5
  import { Menu } from './chunk-3EB2AV3H.js';
6
6
  import { Button } from './chunk-OG6CL3EB.js';
7
7
  import { IconButton } from './chunk-4FQT6ZAY.js';
@@ -0,0 +1,160 @@
1
+ import { cx } from './chunk-ODEQTPFM.js';
2
+ import './LivePreview-SRO7KHO4.css';
3
+ import { createContext, useMemo, useContext, useRef, useState, useEffect, useLayoutEffect } from 'react';
4
+ import { jsx, jsxs } from 'react/jsx-runtime';
5
+
6
+ // src/LivePreview/budget.ts
7
+ var LIVE_PREVIEW_MAX = 12;
8
+ var immediately = (run) => {
9
+ run();
10
+ return () => {
11
+ };
12
+ };
13
+ var perFrame = (run) => {
14
+ if (typeof requestAnimationFrame === "undefined") return immediately(run);
15
+ let timer;
16
+ const frame = requestAnimationFrame(() => {
17
+ timer = setTimeout(run, 0);
18
+ });
19
+ return () => {
20
+ cancelAnimationFrame(frame);
21
+ if (timer !== void 0) clearTimeout(timer);
22
+ };
23
+ };
24
+ function createLivePreviewBudget(max = LIVE_PREVIEW_MAX, schedule = immediately) {
25
+ const held = /* @__PURE__ */ new Set();
26
+ const queue = [];
27
+ let pending = null;
28
+ const next = () => {
29
+ if (pending || held.size >= max || queue.length === 0) return;
30
+ let ran = false;
31
+ const cancel = schedule(() => {
32
+ ran = true;
33
+ pending = null;
34
+ const ask = queue.shift();
35
+ if (ask && held.size < max) {
36
+ held.add(ask.ticket);
37
+ ask.grant();
38
+ } else if (ask) {
39
+ queue.unshift(ask);
40
+ }
41
+ next();
42
+ });
43
+ if (!ran) pending = cancel;
44
+ };
45
+ return {
46
+ max,
47
+ request(grant) {
48
+ const ticket = {};
49
+ queue.push({ ticket, grant });
50
+ next();
51
+ let done = false;
52
+ return () => {
53
+ if (done) return;
54
+ done = true;
55
+ if (held.delete(ticket)) {
56
+ next();
57
+ return;
58
+ }
59
+ const at = queue.findIndex((a) => a.ticket === ticket);
60
+ if (at >= 0) queue.splice(at, 1);
61
+ if (queue.length === 0 && pending) {
62
+ pending();
63
+ pending = null;
64
+ }
65
+ };
66
+ },
67
+ live: () => held.size,
68
+ waiting: () => queue.length
69
+ };
70
+ }
71
+ var LIVE_PREVIEW_DWELL_MS = 120;
72
+ var DefaultBudget = createLivePreviewBudget(LIVE_PREVIEW_MAX, perFrame);
73
+ var BudgetContext = createContext(DefaultBudget);
74
+ function LivePreviewBudgetProvider({ max = LIVE_PREVIEW_MAX, children }) {
75
+ const budget = useMemo(() => createLivePreviewBudget(max, perFrame), [max]);
76
+ return /* @__PURE__ */ jsx(BudgetContext.Provider, { value: budget, children });
77
+ }
78
+ var NOOP = { ready: () => {
79
+ }, fail: () => {
80
+ } };
81
+ var SignalContext = createContext(NOOP);
82
+ function useLivePreview() {
83
+ return useContext(SignalContext);
84
+ }
85
+ function LivePreview({ children, fallback, width, rootMargin = "200px 0px", disabled = false, dwellMs = LIVE_PREVIEW_DWELL_MS, onStateChange, className }) {
86
+ const budget = useContext(BudgetContext);
87
+ const box = useRef(null);
88
+ const [near, setNear] = useState(false);
89
+ const [state, setState] = useState("idle");
90
+ const [failed, setFailed] = useState(false);
91
+ useEffect(() => {
92
+ const el = box.current;
93
+ if (!el || disabled || typeof IntersectionObserver === "undefined") return void 0;
94
+ const seen = new IntersectionObserver((entries) => setNear(entries.some((e) => e.isIntersecting)), { rootMargin });
95
+ seen.observe(el);
96
+ return () => seen.disconnect();
97
+ }, [disabled, rootMargin]);
98
+ useEffect(() => {
99
+ if (!near || disabled || failed) {
100
+ setState(failed ? "failed" : "idle");
101
+ return void 0;
102
+ }
103
+ setState("waiting");
104
+ let granted = false;
105
+ let giveBack = () => {
106
+ };
107
+ const dwell = setTimeout(() => {
108
+ giveBack = budget.request(() => {
109
+ granted = true;
110
+ setState("live");
111
+ });
112
+ }, dwellMs);
113
+ return () => {
114
+ clearTimeout(dwell);
115
+ giveBack();
116
+ if (granted) setState("idle");
117
+ };
118
+ }, [near, disabled, failed, budget, dwellMs]);
119
+ useEffect(() => onStateChange?.(state), [state, onStateChange]);
120
+ const mounted = state === "live" || state === "ready";
121
+ const signals = useMemo(
122
+ () => ({
123
+ ready: () => setState((s) => s === "live" ? "ready" : s),
124
+ fail: () => setFailed(true)
125
+ }),
126
+ []
127
+ );
128
+ return /* @__PURE__ */ jsxs("span", { ref: box, className: cx("crewlet-live-preview", className), "data-state": state, "aria-hidden": "true", children: [
129
+ /* @__PURE__ */ jsx("span", { className: "crewlet-live-preview__fallback", children: fallback }),
130
+ mounted ? /* @__PURE__ */ jsx("span", { className: "crewlet-live-preview__live", inert: true, children: /* @__PURE__ */ jsx(SignalContext.Provider, { value: signals, children: width === void 0 ? children : /* @__PURE__ */ jsx(PreviewScale, { width, children }) }) }) : null
131
+ ] });
132
+ }
133
+ function PreviewScale({ children, width, height, fit = "width" }) {
134
+ const box = useRef(null);
135
+ const [size, setSize] = useState(null);
136
+ useLayoutEffect(() => {
137
+ const el = box.current;
138
+ if (!el) return void 0;
139
+ const measure = () => {
140
+ const rect = el.getBoundingClientRect();
141
+ if (rect.width > 0) setSize((was) => was && was.w === rect.width && was.h === rect.height ? was : { w: rect.width, h: rect.height });
142
+ };
143
+ measure();
144
+ if (typeof ResizeObserver === "undefined") return void 0;
145
+ const watch = new ResizeObserver(measure);
146
+ watch.observe(el);
147
+ return () => watch.disconnect();
148
+ }, []);
149
+ let layer = { width, visibility: "hidden" };
150
+ if (size) {
151
+ const k = fit === "contain" && height ? Math.min(size.w / width, size.h / height) : size.w / width;
152
+ const h = height ?? size.h / k;
153
+ const left = fit === "contain" ? (size.w - width * k) / 2 : 0;
154
+ const top = fit === "contain" ? (size.h - h * k) / 2 : 0;
155
+ layer = { width, height: h, transform: `translate(${left}px, ${top}px) scale(${k})` };
156
+ }
157
+ return /* @__PURE__ */ jsx("span", { ref: box, className: "crewlet-preview-scale", children: /* @__PURE__ */ jsx("span", { className: "crewlet-preview-scale__layer", style: layer, children }) });
158
+ }
159
+
160
+ export { LIVE_PREVIEW_DWELL_MS, LIVE_PREVIEW_MAX, LivePreview, LivePreviewBudgetProvider, PreviewScale, createLivePreviewBudget, immediately, perFrame, useLivePreview };
@@ -1,4 +1,4 @@
1
- import { ArtifactKindGlyph } from './chunk-DFZMYB5X.js';
1
+ import { ArtifactKindGlyph } from './chunk-XTOVADJU.js';
2
2
  import { ShieldGlyph, ActivityGlyph, GaugeGlyph, PackageGlyph, FolderGlyph, ClockGlyph, DatabaseGlyph, Settings2Glyph, BookOpenGlyph, FlagGlyph, StarGlyph, WandSparklesGlyph, BugGlyph, TargetGlyph, ImageGlyph, CodeGlyph, CoinsGlyph, UserGlyph, UsersGlyph, ChartNoAxesGanttGlyph, SquareKanbanGlyph, ListChecksGlyph, ListGlyph, GlobeGlyph, CompassGlyph, FileTextGlyph, TableGlyph, CalendarGlyph, LayoutDashboardGlyph, ChartPieGlyph, ChartColumnGlyph } from '@crewlethq/icons/glyphs';
3
3
  import { jsx } from 'react/jsx-runtime';
4
4
 
@@ -1,5 +1,5 @@
1
- import { changeText, sheetLabels } from './chunk-J6ZSH3F5.js';
2
- import { Tooltip } from './chunk-5FYPE5GL.js';
1
+ import { changeText, sheetLabels } from './chunk-VI5FWDZW.js';
2
+ import { Tooltip } from './chunk-6ZJ3N7QI.js';
3
3
  import { cx } from './chunk-ODEQTPFM.js';
4
4
  import './ChangeMark-3VMTW7MT.css';
5
5
  import { jsx } from 'react/jsx-runtime';