@crewlethq/ui 0.17.0 → 0.18.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 (146) hide show
  1. package/README.md +36 -1
  2. package/dist/AccessDeniedCard/AccessDeniedCard.d.ts +39 -0
  3. package/dist/AccessDeniedCard/index.d.ts +3 -0
  4. package/dist/AccessDeniedCard/index.js +8 -0
  5. package/dist/AccessDeniedCard-WXM5DBDZ.css +8 -0
  6. package/dist/ArtifactCard/ArtifactCard.d.ts +61 -0
  7. package/dist/ArtifactCard/index.d.ts +3 -0
  8. package/dist/ArtifactCard/index.js +12 -0
  9. package/dist/ArtifactCard-EXVAEBV6.css +109 -0
  10. package/dist/AvatarPicker/index.js +4 -4
  11. package/dist/ChangeMark/ChangeMark.d.ts +26 -0
  12. package/dist/ChangeMark/index.d.ts +3 -0
  13. package/dist/ChangeMark/index.js +8 -0
  14. package/dist/ChangeMark-3VMTW7MT.css +22 -0
  15. package/dist/Combobox/index.js +1 -1
  16. package/dist/CommandPalette/index.js +3 -3
  17. package/dist/CursorFlag/CursorFlag.d.ts +34 -0
  18. package/dist/CursorFlag/index.d.ts +3 -0
  19. package/dist/CursorFlag/index.js +3 -0
  20. package/dist/CursorFlag-AJQJBUGY.css +61 -0
  21. package/dist/DataTable/index.js +6 -6
  22. package/dist/DataView/index.js +12 -12
  23. package/dist/DateTimePicker/index.js +2 -2
  24. package/dist/FollowBanner/FollowBanner.d.ts +35 -0
  25. package/dist/FollowBanner/index.d.ts +2 -0
  26. package/dist/FollowBanner/index.js +7 -0
  27. package/dist/FrameProtocol/index.d.ts +2 -0
  28. package/dist/FrameProtocol/index.js +2 -0
  29. package/dist/FrameProtocol/protocol.d.ts +164 -0
  30. package/dist/HuePicker/index.js +2 -2
  31. package/dist/LinkInterstitial/LinkInterstitial.d.ts +41 -0
  32. package/dist/LinkInterstitial/index.d.ts +3 -0
  33. package/dist/LinkInterstitial/index.js +11 -0
  34. package/dist/LinkInterstitial-ET4MSVSV.css +34 -0
  35. package/dist/ListInput/index.js +1 -1
  36. package/dist/LiveCursor/LiveCursor.d.ts +34 -0
  37. package/dist/LiveCursor/index.d.ts +3 -0
  38. package/dist/LiveCursor/index.js +4 -0
  39. package/dist/LiveCursor-DZHGIYWE.css +47 -0
  40. package/dist/MarkdownEditor/index.js +2 -2
  41. package/dist/Modal/index.js +2 -2
  42. package/dist/OrgTable/index.js +2 -2
  43. package/dist/PresenceModel/index.d.ts +2 -0
  44. package/dist/PresenceModel/index.js +3 -0
  45. package/dist/PresenceModel/presence.d.ts +80 -0
  46. package/dist/PresenceRing/PresenceRing.d.ts +31 -0
  47. package/dist/PresenceRing/index.d.ts +3 -0
  48. package/dist/PresenceRing/index.js +2 -0
  49. package/dist/PresenceRing-M4C22Q6U.css +68 -0
  50. package/dist/PresenceStack/PresenceStack.d.ts +36 -0
  51. package/dist/PresenceStack/index.d.ts +3 -0
  52. package/dist/PresenceStack/index.js +9 -0
  53. package/dist/PresenceStack-QSXQJND5.css +59 -0
  54. package/dist/PricingCard/index.js +2 -2
  55. package/dist/Principal/Principal.d.ts +35 -0
  56. package/dist/Principal/index.d.ts +2 -0
  57. package/dist/Principal/index.js +4 -0
  58. package/dist/PrincipalPicker/PrincipalPicker.d.ts +45 -0
  59. package/dist/PrincipalPicker/index.d.ts +3 -0
  60. package/dist/PrincipalPicker/index.js +12 -0
  61. package/dist/PrincipalPicker-7NNZTGUP.css +41 -0
  62. package/dist/SandboxedFrame/SandboxedFrame.d.ts +142 -0
  63. package/dist/SandboxedFrame/index.d.ts +3 -0
  64. package/dist/SandboxedFrame/index.js +8 -0
  65. package/dist/SandboxedFrame-L6HBQZCD.css +50 -0
  66. package/dist/ShareDialog/ShareDialog.d.ts +101 -0
  67. package/dist/ShareDialog/index.d.ts +5 -0
  68. package/dist/ShareDialog/index.js +19 -0
  69. package/dist/ShareDialog/roles.d.ts +10 -0
  70. package/dist/ShareDialog-YZ4F37MW.css +146 -0
  71. package/dist/SheetGrid/SheetGrid.d.ts +78 -0
  72. package/dist/SheetGrid/cells.d.ts +117 -0
  73. package/dist/SheetGrid/index.d.ts +5 -0
  74. package/dist/SheetGrid/index.js +18 -0
  75. package/dist/SheetGrid-4HOXDVIB.css +595 -0
  76. package/dist/SheetModel/clipboard.d.ts +37 -0
  77. package/dist/SheetModel/edits.d.ts +68 -0
  78. package/dist/SheetModel/index.d.ts +14 -0
  79. package/dist/SheetModel/index.js +3 -0
  80. package/dist/SheetModel/labels.d.ts +71 -0
  81. package/dist/SheetModel/navigation.d.ts +79 -0
  82. package/dist/SheetModel/options.d.ts +28 -0
  83. package/dist/SheetModel/types.d.ts +126 -0
  84. package/dist/SheetModel/values.d.ts +76 -0
  85. package/dist/SheetModel/view.d.ts +32 -0
  86. package/dist/Switcher/index.js +5 -5
  87. package/dist/Tag/Tag.d.ts +13 -0
  88. package/dist/Tag/index.js +1 -1
  89. package/dist/{Tag-4BRQS6GN.css → Tag-SRGTK5KI.css} +42 -0
  90. package/dist/TagsInput/index.js +2 -2
  91. package/dist/ThemeSwitcher/apply.d.ts +43 -0
  92. package/dist/ThemeSwitcher/index.d.ts +4 -2
  93. package/dist/ThemeSwitcher/index.js +3 -2
  94. package/dist/ThemeSwitcher/preferences.d.ts +2 -19
  95. package/dist/ThemeToggle/index.js +3 -2
  96. package/dist/TimeWindowPicker/index.js +3 -3
  97. package/dist/Timeline/Timeline.d.ts +7 -2
  98. package/dist/Timeline/index.js +1 -1
  99. package/dist/VersionTimeline/VersionTimeline.d.ts +76 -0
  100. package/dist/VersionTimeline/index.d.ts +3 -0
  101. package/dist/VersionTimeline/index.js +10 -0
  102. package/dist/VersionTimeline-EBJ6MIPJ.css +108 -0
  103. package/dist/{chunk-SBRE7IML.js → chunk-2ONWEDQT.js} +1 -1
  104. package/dist/chunk-2YR5ZPQ3.js +216 -0
  105. package/dist/chunk-3BYIWOTV.js +29 -0
  106. package/dist/chunk-3UAA4IWJ.js +98 -0
  107. package/dist/chunk-4KP4RC3O.js +25 -0
  108. package/dist/chunk-4U3GQ7D2.js +99 -0
  109. package/dist/chunk-5XACKNYR.js +53 -0
  110. package/dist/{chunk-RF5O4FYL.js → chunk-76FXDZAZ.js} +2 -2
  111. package/dist/chunk-A7EBCHEG.js +49 -0
  112. package/dist/{chunk-JCO3SIST.js → chunk-AQCV62K6.js} +1 -1
  113. package/dist/{chunk-O67VF5MS.js → chunk-AUQPBL4O.js} +2 -2
  114. package/dist/{chunk-HENOTGUV.js → chunk-BOW2VUCH.js} +2 -2
  115. package/dist/{chunk-UCYIRDND.js → chunk-DQSXWQMJ.js} +1 -1
  116. package/dist/chunk-ESKP3TXM.js +1 -0
  117. package/dist/chunk-F6V2PTN6.js +142 -0
  118. package/dist/chunk-FW7HN6AP.js +40 -0
  119. package/dist/chunk-FYXRWBJJ.js +1235 -0
  120. package/dist/{chunk-LOECRMHA.js → chunk-GFV4LJHH.js} +4 -3
  121. package/dist/chunk-I6POAB4V.js +20 -0
  122. package/dist/{chunk-PQ63NLFC.js → chunk-IFKBDRNJ.js} +1 -1
  123. package/dist/{chunk-XU4H6JQB.js → chunk-KVT3TJVG.js} +4 -4
  124. package/dist/chunk-LFZ45HV2.js +46 -0
  125. package/dist/{chunk-6TSZCBID.js → chunk-NP2C4Y4E.js} +1 -1
  126. package/dist/{chunk-FA2V2VT6.js → chunk-NZY77ZDU.js} +3 -3
  127. package/dist/chunk-PPAQFGS2.js +12 -0
  128. package/dist/chunk-S35G6PNC.js +80 -0
  129. package/dist/chunk-VYDFB6PJ.js +243 -0
  130. package/dist/chunk-WXAULPBH.js +135 -0
  131. package/dist/chunk-YM3GJHGW.js +69 -0
  132. package/dist/chunk-YYZRLLLY.js +822 -0
  133. package/dist/{chunk-K3OWD7BJ.js → chunk-Z24ZUSTX.js} +4 -19
  134. package/dist/chunk-ZXXDZJQN.js +36 -0
  135. package/dist/generated/index.d.ts +18 -0
  136. package/dist/generated/index.js +82 -62
  137. package/dist/index.js +82 -62
  138. package/dist/styles.css +1578 -149
  139. package/dist/utils/index.js +1 -1
  140. package/package.json +6 -6
  141. package/dist/{chunk-DEEVA6TV.js → chunk-2TZATTIU.js} +1 -1
  142. package/dist/{chunk-2UU5IGZI.js → chunk-5NVKTB6E.js} +1 -1
  143. package/dist/{chunk-CRVETI5L.js → chunk-CENOOTAV.js} +0 -0
  144. package/dist/{chunk-JF7X673Z.js → chunk-ERV5BUS3.js} +1 -1
  145. package/dist/{chunk-NXDEPUVG.js → chunk-PA4CYTBY.js} +1 -1
  146. package/dist/{chunk-77IEHFY4.js → chunk-QQGWES46.js} +1 -1
@@ -0,0 +1,1235 @@
1
+ import { Tag } from './chunk-GFV4LJHH.js';
2
+ import { StatusDot } from './chunk-WIATRXEJ.js';
3
+ import { CursorFlag } from './chunk-3BYIWOTV.js';
4
+ 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';
8
+ import { Button } from './chunk-OG6CL3EB.js';
9
+ import { isComposing } from './chunk-R25RGPUB.js';
10
+ import { Avatar } from './chunk-5OM447RW.js';
11
+ import { VisuallyHidden } from './chunk-BZFWDIV6.js';
12
+ import { cx } from './chunk-ODEQTPFM.js';
13
+ import './SheetGrid-4HOXDVIB.css';
14
+ import { useId, useRef, useMemo, useState, useLayoutEffect, useEffect, useCallback } from 'react';
15
+ import { CheckGlyph, StarGlyph, CircleAlertGlyph, ArrowUpGlyph, ArrowDownGlyph, RotateCcwClockGlyph, PlusGlyph } from '@crewlethq/icons/glyphs';
16
+ import { isCrewletCharacter } from '@crewlethq/icons/characters';
17
+ import { jsxs, jsx } from 'react/jsx-runtime';
18
+
19
+ function OptionChip({ column, id, labels }) {
20
+ const option = findOption(column, id);
21
+ if (!option) return /* @__PURE__ */ jsx(Tag, { size: "xs", children: id });
22
+ if (option.archived) {
23
+ return /* @__PURE__ */ jsx(Tag, { size: "xs", appearance: "outline", children: labels.archived(option.label) });
24
+ }
25
+ const hue = optionHue(option.color);
26
+ return /* @__PURE__ */ jsx(Tag, { size: "xs", hue: hue === "neutral" ? void 0 : hue, children: option.label });
27
+ }
28
+ function PersonBadge({ person, fallback }) {
29
+ if (!person) return /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-cell__muted", children: fallback });
30
+ return /* @__PURE__ */ jsxs("span", { className: "crewlet-sheet-cell__person", children: [
31
+ /* @__PURE__ */ jsx(
32
+ Avatar,
33
+ {
34
+ name: person.name,
35
+ kind: person.kind === "human" ? "human" : "agent",
36
+ src: person.src,
37
+ character: person.character !== void 0 && isCrewletCharacter(person.character) ? person.character : void 0,
38
+ hue: person.kind === "agent" ? person.hue : void 0,
39
+ size: "xs",
40
+ decorative: true
41
+ }
42
+ ),
43
+ /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-cell__text", children: person.name })
44
+ ] });
45
+ }
46
+ function CellDisplay({ column, value, context, onOpenLink }) {
47
+ const { labels } = context;
48
+ if (column.type === "checkbox") {
49
+ const on = value === true;
50
+ return /* @__PURE__ */ jsxs("span", { className: "crewlet-sheet-cell__checkbox", children: [
51
+ /* @__PURE__ */ jsx("span", { className: cx("crewlet-sheet-cell__check", on && "crewlet-sheet-cell__check--on"), "aria-hidden": "true", children: on ? /* @__PURE__ */ jsx(CheckGlyph, { size: "xs" }) : null }),
52
+ /* @__PURE__ */ jsx(VisuallyHidden, { children: on ? labels.checked : labels.unchecked })
53
+ ] });
54
+ }
55
+ if (isEmptyValue(value)) return /* @__PURE__ */ jsx(VisuallyHidden, { children: labels.empty });
56
+ switch (column.type) {
57
+ case "select":
58
+ return /* @__PURE__ */ jsx(OptionChip, { column, id: String(value), labels });
59
+ case "multiselect":
60
+ return /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-cell__chips", children: value.map((id) => /* @__PURE__ */ jsx(OptionChip, { column, id, labels }, id)) });
61
+ case "status": {
62
+ const option = findOption(column, String(value));
63
+ const words = displayText(column, value, context);
64
+ return /* @__PURE__ */ jsx(StatusDot, { tone: option && !option.archived ? statusTone(option) : "neutral", children: words });
65
+ }
66
+ case "person":
67
+ return /* @__PURE__ */ jsx(PersonBadge, { person: findPerson(context.people, String(value)), fallback: labels.unknownPerson });
68
+ case "rating": {
69
+ const max = ratingMax(column);
70
+ const stars = Number(value);
71
+ return /* @__PURE__ */ jsxs("span", { className: "crewlet-sheet-cell__rating", children: [
72
+ /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-cell__stars", "aria-hidden": "true", children: Array.from({ length: max }, (_, index) => /* @__PURE__ */ jsx(StarGlyph, { size: "sm", filled: index < stars, className: cx("crewlet-sheet-cell__star", index < stars && "crewlet-sheet-cell__star--on") }, index)) }),
73
+ /* @__PURE__ */ jsx(VisuallyHidden, { children: labels.rating(stars, max) })
74
+ ] });
75
+ }
76
+ case "url": {
77
+ const url = String(value);
78
+ return /* @__PURE__ */ jsx(
79
+ "a",
80
+ {
81
+ className: "crewlet-sheet-cell__link",
82
+ href: url,
83
+ target: "_blank",
84
+ rel: "noopener noreferrer",
85
+ tabIndex: -1,
86
+ onClick: onOpenLink ? (event) => {
87
+ event.preventDefault();
88
+ onOpenLink(url);
89
+ } : void 0,
90
+ children: url
91
+ }
92
+ );
93
+ }
94
+ case "number":
95
+ return /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-cell__text crewlet-sheet-cell__number", children: displayText(column, value, context) });
96
+ default:
97
+ return /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-cell__text", children: displayText(column, value, context) });
98
+ }
99
+ }
100
+ var LINE_TYPES = /* @__PURE__ */ new Set(["text", "number", "url", "date", "rating"]);
101
+ var LIST_TYPES = /* @__PURE__ */ new Set(["select", "multiselect", "status", "person"]);
102
+ var hasEditor = (type) => type !== "checkbox";
103
+ var takesTyping = (type) => LINE_TYPES.has(type) || LIST_TYPES.has(type) || type === "longtext";
104
+ function endingMove(event, multiline) {
105
+ if (event.key === "Tab") return event.shiftKey ? "left" : "right";
106
+ if (event.key === "Enter") {
107
+ if (multiline && !isMod(event)) return void 0;
108
+ return event.shiftKey ? "up" : "down";
109
+ }
110
+ return void 0;
111
+ }
112
+ function useLeave(end) {
113
+ const done = useRef(false);
114
+ return {
115
+ finish: () => {
116
+ done.current = true;
117
+ },
118
+ onBlur: (event) => {
119
+ if (done.current) return;
120
+ const next = event.relatedTarget;
121
+ if (next !== null && event.currentTarget.contains(next)) return;
122
+ done.current = true;
123
+ end();
124
+ }
125
+ };
126
+ }
127
+ function localDateTime(value) {
128
+ const at = new Date(value);
129
+ if (Number.isNaN(at.getTime())) return "";
130
+ const pad = (n) => String(n).padStart(2, "0");
131
+ return `${at.getFullYear()}-${pad(at.getMonth() + 1)}-${pad(at.getDate())}T${pad(at.getHours())}:${pad(at.getMinutes())}`;
132
+ }
133
+ function ConflictNote({ conflict, labels }) {
134
+ return /* @__PURE__ */ jsxs("div", { className: "crewlet-sheet-editor__conflict", role: "alert", children: [
135
+ /* @__PURE__ */ jsx("p", { className: "crewlet-sheet-editor__conflict-text", children: labels.conflict(conflict.writer, conflict.value) }),
136
+ /* @__PURE__ */ jsxs("div", { className: "crewlet-sheet-editor__conflict-actions", children: [
137
+ /* @__PURE__ */ jsx(Button, { size: "small", variant: "secondary", onClick: conflict.onKeepMine, children: labels.keepMine }),
138
+ /* @__PURE__ */ jsx(Button, { size: "small", variant: "ghost", onClick: conflict.onUseTheirs, children: labels.useTheirs(conflict.writer) })
139
+ ] })
140
+ ] });
141
+ }
142
+ function ErrorNote({ id, message }) {
143
+ return /* @__PURE__ */ jsxs("p", { id, className: "crewlet-sheet-editor__error", children: [
144
+ /* @__PURE__ */ jsx(CircleAlertGlyph, { size: "xs", "aria-hidden": "true" }),
145
+ /* @__PURE__ */ jsx("span", { children: message })
146
+ ] });
147
+ }
148
+ function CellEditor(props) {
149
+ const { column } = props;
150
+ if (LIST_TYPES.has(column.type)) return /* @__PURE__ */ jsx(ListEditor, { ...props });
151
+ if (column.type === "longtext") return /* @__PURE__ */ jsx(LongTextEditor, { ...props });
152
+ return /* @__PURE__ */ jsx(LineEditor, { ...props });
153
+ }
154
+ function LineEditor({ column, value, context, seed, label, onCommit, onCancel, conflict }) {
155
+ const { labels } = context;
156
+ const errorId = useId();
157
+ const input = useRef(null);
158
+ const isDate = column.type === "date";
159
+ const initial = useMemo(() => {
160
+ if (isDate) {
161
+ const text = typeof value === "string" ? value : "";
162
+ return column.time ? localDateTime(text) : text;
163
+ }
164
+ return seed ?? editText(column, value, context);
165
+ }, []);
166
+ const [draft, setDraft] = useState(initial);
167
+ const [error, setError] = useState(null);
168
+ useLayoutEffect(() => {
169
+ const el = input.current;
170
+ if (!el) return;
171
+ el.focus();
172
+ if (!isDate) el.setSelectionRange(el.value.length, el.value.length);
173
+ }, [isDate]);
174
+ const commit = (move) => {
175
+ const text = isDate && column.time && draft !== "" ? new Date(draft).toISOString() : draft;
176
+ const parsed = parseText(column, text, context);
177
+ if (!parsed.ok) {
178
+ setError(parsed.error);
179
+ return false;
180
+ }
181
+ leave.finish();
182
+ onCommit(parsed.value, move);
183
+ return true;
184
+ };
185
+ const leave = useLeave(() => {
186
+ if (!commit(null)) onCancel();
187
+ });
188
+ return /* @__PURE__ */ jsxs("div", { className: "crewlet-sheet-editor crewlet-sheet-editor--line", onBlur: leave.onBlur, children: [
189
+ /* @__PURE__ */ jsx(
190
+ "input",
191
+ {
192
+ ref: input,
193
+ className: "crewlet-sheet-editor__input",
194
+ type: isDate ? column.time ? "datetime-local" : "date" : column.type === "url" ? "url" : "text",
195
+ inputMode: column.type === "number" ? "decimal" : column.type === "rating" ? "numeric" : void 0,
196
+ "aria-label": label,
197
+ "aria-invalid": error !== null,
198
+ "aria-describedby": error !== null ? errorId : void 0,
199
+ maxLength: column.type === "text" ? textMax(column) : void 0,
200
+ value: draft,
201
+ onChange: (event) => {
202
+ setDraft(event.target.value);
203
+ setError(null);
204
+ },
205
+ onKeyDown: (event) => {
206
+ if (isComposing(event)) return;
207
+ if (event.key === "Escape") {
208
+ event.preventDefault();
209
+ event.stopPropagation();
210
+ leave.finish();
211
+ onCancel();
212
+ return;
213
+ }
214
+ if (event.key === "Tab" && conflict) return;
215
+ const move = endingMove(event, false);
216
+ if (move === void 0) return;
217
+ event.preventDefault();
218
+ event.stopPropagation();
219
+ commit(move);
220
+ }
221
+ }
222
+ ),
223
+ error !== null || conflict ? /* @__PURE__ */ jsxs("div", { className: cx("crewlet-sheet-editor__panel"), children: [
224
+ error !== null ? /* @__PURE__ */ jsx(ErrorNote, { id: errorId, message: error }) : null,
225
+ conflict ? /* @__PURE__ */ jsx(ConflictNote, { conflict, labels }) : null
226
+ ] }) : null
227
+ ] });
228
+ }
229
+ function LongTextEditor({ column, value, context, seed, label, onCommit, onCancel, side = "below", conflict }) {
230
+ const { labels } = context;
231
+ const errorId = useId();
232
+ const area = useRef(null);
233
+ const [draft, setDraft] = useState(() => seed ?? editText(column, value, context));
234
+ const [error, setError] = useState(null);
235
+ useLayoutEffect(() => {
236
+ const el = area.current;
237
+ if (!el) return;
238
+ el.focus();
239
+ el.setSelectionRange(el.value.length, el.value.length);
240
+ }, []);
241
+ const commit = (move) => {
242
+ const parsed = parseText(column, draft, context);
243
+ if (!parsed.ok) {
244
+ setError(parsed.error);
245
+ return false;
246
+ }
247
+ leave.finish();
248
+ onCommit(parsed.value, move);
249
+ return true;
250
+ };
251
+ const leave = useLeave(() => {
252
+ if (!commit(null)) onCancel();
253
+ });
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;
278
+ }
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
+ ] }) });
291
+ }
292
+ function choicesOf(column, context) {
293
+ if (column.type === "person") return (context.people ?? []).map((person) => ({ id: person.id, label: person.name, person }));
294
+ return liveOptions(column).map((option) => ({ id: option.id, label: option.label, option }));
295
+ }
296
+ function ChoiceFace({ column, choice, context }) {
297
+ if (choice.person) return /* @__PURE__ */ jsx(PersonBadge, { person: choice.person, fallback: choice.label });
298
+ if (column.type === "status" && choice.option) return /* @__PURE__ */ jsx(StatusDot, { tone: statusTone(choice.option), children: choice.label });
299
+ return /* @__PURE__ */ jsx(OptionChip, { column, id: choice.id, labels: context.labels });
300
+ }
301
+ function ListEditor({ column, value, context, seed, label, onCommit, onCancel, side = "below", conflict }) {
302
+ const { labels } = context;
303
+ const multiple = column.type === "multiselect";
304
+ const listId = useId();
305
+ const errorId = useId();
306
+ const filterRef = useRef(null);
307
+ const [filter, setFilter] = useState(seed ?? "");
308
+ const [chosen, setChosen] = useState(
309
+ () => multiple ? [...value ?? []] : typeof value === "string" ? [value] : []
310
+ );
311
+ const [error, setError] = useState(null);
312
+ const all = useMemo(() => choicesOf(column, context), [column, context]);
313
+ const clearable = !multiple && !column.required;
314
+ const shown = useMemo(() => {
315
+ const wanted = filter.trim().toLowerCase();
316
+ const matching = wanted === "" ? all : all.filter((choice) => choice.label.toLowerCase().includes(wanted));
317
+ return clearable && wanted === "" ? [{ id: "", label: labels.clear }, ...matching] : matching;
318
+ }, [all, filter, clearable, labels.clear]);
319
+ const [highlight, setHighlight] = useState(() => {
320
+ const at = shown.findIndex((choice) => choice.id !== "" && chosen.includes(choice.id));
321
+ return at >= 0 ? at : 0;
322
+ });
323
+ useEffect(() => {
324
+ setHighlight((at) => Math.min(at, Math.max(shown.length - 1, 0)));
325
+ }, [shown.length]);
326
+ useLayoutEffect(() => {
327
+ filterRef.current?.focus();
328
+ }, []);
329
+ const finish = (next, move) => {
330
+ const problem = checkValue(column, next, context);
331
+ if (problem !== null) {
332
+ setError(problem);
333
+ return false;
334
+ }
335
+ leave.finish();
336
+ onCommit(next, move);
337
+ return true;
338
+ };
339
+ const leave = useLeave(() => {
340
+ if (!multiple || !finish(chosen, null)) onCancel();
341
+ });
342
+ const pick = (choice, move) => {
343
+ if (!choice) return;
344
+ if (!multiple) {
345
+ finish(choice.id === "" ? null : choice.id, move);
346
+ return;
347
+ }
348
+ setError(null);
349
+ setChosen((current) => current.includes(choice.id) ? current.filter((id) => id !== choice.id) : [...current, choice.id]);
350
+ };
351
+ const optionId = (index) => `${listId}-${index}`;
352
+ 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(
354
+ "input",
355
+ {
356
+ ref: filterRef,
357
+ className: "crewlet-sheet-editor__filter",
358
+ role: "combobox",
359
+ "aria-label": label,
360
+ "aria-expanded": "true",
361
+ "aria-controls": listId,
362
+ "aria-autocomplete": "list",
363
+ "aria-activedescendant": shown.length > 0 ? optionId(highlight) : void 0,
364
+ "aria-invalid": error !== null,
365
+ "aria-describedby": error !== null ? errorId : void 0,
366
+ placeholder: labels.filter,
367
+ value: filter,
368
+ onChange: (event) => {
369
+ setFilter(event.target.value);
370
+ setHighlight(0);
371
+ },
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
+ }
415
+ }
416
+ ),
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
+ error !== null ? /* @__PURE__ */ jsx(ErrorNote, { id: errorId, message: error }) : null,
445
+ conflict ? /* @__PURE__ */ jsx(ConflictNote, { conflict, labels }) : null
446
+ ] }) });
447
+ }
448
+ function SheetCell({
449
+ label,
450
+ type,
451
+ value: raw,
452
+ onChange,
453
+ readOnly = false,
454
+ options,
455
+ people,
456
+ column: settings,
457
+ pending = false,
458
+ refused,
459
+ locale,
460
+ className,
461
+ labels: overrides
462
+ }) {
463
+ const labels = useMemo(() => sheetLabels(overrides), [overrides]);
464
+ const column = useMemo(() => ({ ...settings, field: label, label, type, options }), [settings, label, type, options]);
465
+ const context = useMemo(() => ({ people, locale, labels }), [people, locale, labels]);
466
+ const value = normalizeValue(column, raw);
467
+ const editable = onChange !== void 0 && !readOnly && !column.readOnly;
468
+ const [editing, setEditing] = useState(false);
469
+ const statusId = useId();
470
+ const face = useRef(null);
471
+ const returnFocus = useRef(false);
472
+ useEffect(() => {
473
+ if (!editing && returnFocus.current) {
474
+ returnFocus.current = false;
475
+ face.current?.focus();
476
+ }
477
+ }, [editing]);
478
+ const status = refused !== void 0 ? /* @__PURE__ */ jsxs("span", { className: "crewlet-sheet-cell__refusal", children: [
479
+ /* @__PURE__ */ jsx(CircleAlertGlyph, { size: "xs", "aria-hidden": "true" }),
480
+ /* @__PURE__ */ jsx("span", { children: refused })
481
+ ] }) : pending ? /* @__PURE__ */ jsx(VisuallyHidden, { children: labels.saving }) : null;
482
+ const classes = cx("crewlet-sheet-cell", pending && "crewlet-sheet-cell--pending", refused !== void 0 && "crewlet-sheet-cell--refused", className);
483
+ if (type === "checkbox") {
484
+ return /* @__PURE__ */ jsxs("span", { className: classes, children: [
485
+ /* @__PURE__ */ jsx(
486
+ "button",
487
+ {
488
+ type: "button",
489
+ role: "checkbox",
490
+ "aria-checked": value === true,
491
+ "aria-label": label,
492
+ "aria-readonly": !editable || void 0,
493
+ "aria-describedby": status !== null ? statusId : void 0,
494
+ className: "crewlet-sheet-cell__face",
495
+ onClick: () => {
496
+ if (editable) onChange?.(value !== true);
497
+ },
498
+ children: /* @__PURE__ */ jsx(CellDisplay, { column, value, context })
499
+ }
500
+ ),
501
+ status !== null ? /* @__PURE__ */ jsx("span", { id: statusId, children: status }) : null
502
+ ] });
503
+ }
504
+ if (editing) {
505
+ return /* @__PURE__ */ jsx("span", { className: cx(classes, "crewlet-sheet-cell--editing"), children: /* @__PURE__ */ jsx(
506
+ CellEditor,
507
+ {
508
+ column,
509
+ value,
510
+ context,
511
+ label,
512
+ onCommit: (next, _move) => {
513
+ returnFocus.current = true;
514
+ setEditing(false);
515
+ if (!sameValue(next, value)) onChange?.(next);
516
+ },
517
+ onCancel: () => {
518
+ returnFocus.current = true;
519
+ setEditing(false);
520
+ }
521
+ }
522
+ ) });
523
+ }
524
+ return /* @__PURE__ */ jsxs("span", { className: classes, children: [
525
+ /* @__PURE__ */ jsx(
526
+ "button",
527
+ {
528
+ ref: face,
529
+ type: "button",
530
+ className: "crewlet-sheet-cell__face",
531
+ "aria-label": `${label}: ${spokenText(column, value, context)}`,
532
+ "aria-describedby": status !== null ? statusId : void 0,
533
+ "aria-disabled": !editable || void 0,
534
+ onClick: () => {
535
+ if (editable) setEditing(true);
536
+ },
537
+ children: /* @__PURE__ */ jsx(CellDisplay, { column, value, context })
538
+ }
539
+ ),
540
+ status !== null ? /* @__PURE__ */ jsx("span", { id: statusId, children: status }) : null
541
+ ] });
542
+ }
543
+ var CheckboxCell = (props) => /* @__PURE__ */ jsx(SheetCell, { ...props, type: "checkbox" });
544
+ var TextCell = (props) => /* @__PURE__ */ jsx(SheetCell, { ...props, type: "text" });
545
+ var LongTextCell = (props) => /* @__PURE__ */ jsx(SheetCell, { ...props, type: "longtext" });
546
+ var NumberCell = (props) => /* @__PURE__ */ jsx(SheetCell, { ...props, type: "number" });
547
+ var SelectCell = (props) => /* @__PURE__ */ jsx(SheetCell, { ...props, type: "select" });
548
+ var MultiSelectCell = (props) => /* @__PURE__ */ jsx(SheetCell, { ...props, type: "multiselect" });
549
+ var PersonCell = (props) => /* @__PURE__ */ jsx(SheetCell, { ...props, type: "person" });
550
+ var StatusCell = (props) => /* @__PURE__ */ jsx(SheetCell, { ...props, type: "status" });
551
+ var DateCell = (props) => /* @__PURE__ */ jsx(SheetCell, { ...props, type: "date" });
552
+ var UrlCell = (props) => /* @__PURE__ */ jsx(SheetCell, { ...props, type: "url" });
553
+ var RatingCell = (props) => /* @__PURE__ */ jsx(SheetCell, { ...props, type: "rating" });
554
+ var HEADER = -1;
555
+ function SheetGrid({
556
+ label,
557
+ columns: declared,
558
+ rows: given,
559
+ people,
560
+ mode = "edit",
561
+ onEdit,
562
+ onAddRow,
563
+ onShowHistory,
564
+ onActiveCellChange,
565
+ onEditingChange,
566
+ onUndo,
567
+ onRedo,
568
+ onOpenLink,
569
+ presence,
570
+ sort,
571
+ onSortChange,
572
+ sync,
573
+ onRetry,
574
+ height,
575
+ locale,
576
+ className,
577
+ labels: labelOverrides,
578
+ presenceLabels: presenceOverrides
579
+ }) {
580
+ const id = useId();
581
+ const labels = useMemo(() => sheetLabels(labelOverrides), [labelOverrides]);
582
+ const said = useMemo(() => presenceLabels(presenceOverrides), [presenceOverrides]);
583
+ const context = useMemo(() => ({ people, locale, labels }), [people, locale, labels]);
584
+ const columns = useMemo(() => drawnColumns(declared), [declared]);
585
+ const rows = useMemo(() => sortRows(columns, given, sort, context), [columns, given, sort, context]);
586
+ const editable = mode === "edit" && onEdit !== void 0;
587
+ const scroller = useRef(null);
588
+ const probe = useRef(null);
589
+ const grid = useRef(null);
590
+ const [focusRequest, setFocusRequest] = useState(0);
591
+ const handledFocus = useRef(0);
592
+ const [rowHeight, setRowHeight] = useState(SHEET_ROW_HEIGHT);
593
+ const [headerHeight, setHeaderHeight] = useState(SHEET_ROW_HEIGHT);
594
+ const [viewport, setViewport] = useState(0);
595
+ const [scrollTop, setScrollTop] = useState(0);
596
+ const [active, setActive] = useState(null);
597
+ const [anchor, setAnchor] = useState(null);
598
+ const [editing, setEditing] = useState(null);
599
+ const [local, setLocal] = useState(/* @__PURE__ */ new Map());
600
+ const [announcement, setAnnouncement] = useState({ text: "", n: 0 });
601
+ const dragging = useRef(false);
602
+ const say = useCallback((text) => setAnnouncement((current2) => ({ text, n: current2.n + 1 })), []);
603
+ useLayoutEffect(() => {
604
+ const measure = () => {
605
+ const row = probe.current?.offsetHeight ?? 0;
606
+ if (row > 0) setRowHeight(row);
607
+ const head = grid.current?.querySelector(".crewlet-sheet-grid__row--head")?.offsetHeight ?? 0;
608
+ if (head > 0) setHeaderHeight(head);
609
+ setViewport(scroller.current?.clientHeight ?? 0);
610
+ };
611
+ measure();
612
+ if (typeof ResizeObserver !== "function") return void 0;
613
+ const observer = new ResizeObserver(measure);
614
+ if (scroller.current) observer.observe(scroller.current);
615
+ if (probe.current) observer.observe(probe.current);
616
+ return () => observer.disconnect();
617
+ }, []);
618
+ const offsets = useMemo(() => columnOffsets(columns), [columns]);
619
+ const template = useMemo(
620
+ () => [`${SHEET_ROW_NUMBER_WIDTH}px`, ...columns.map((column) => `${columnWidth(column)}px`)].join(" "),
621
+ [columns]
622
+ );
623
+ const totalWidth = useMemo(() => columns.reduce((sum, column) => sum + columnWidth(column), SHEET_ROW_NUMBER_WIDTH), [columns]);
624
+ const frozenEdge = useMemo(() => {
625
+ let edge = SHEET_ROW_NUMBER_WIDTH;
626
+ columns.forEach((column, index) => {
627
+ if (column.frozen) edge = (offsets[index] ?? edge) + columnWidth(column);
628
+ });
629
+ return edge;
630
+ }, [columns, offsets]);
631
+ const rowIndex = useMemo(() => new Map(rows.map((row, index) => [row.id, index])), [rows]);
632
+ const colIndex = useMemo(() => new Map(columns.map((column, index) => [column.field, index])), [columns]);
633
+ const positionOf = useCallback(
634
+ (cell) => {
635
+ if (!cell) return null;
636
+ const col = colIndex.get(cell.field);
637
+ if (col === void 0) return null;
638
+ if (cell.rowId === null) return { row: HEADER, col };
639
+ const row = rowIndex.get(cell.rowId);
640
+ return row === void 0 ? null : { row, col };
641
+ },
642
+ [colIndex, rowIndex]
643
+ );
644
+ const cellAt = useCallback(
645
+ (position) => {
646
+ const column = columns[position.col];
647
+ if (!column) return null;
648
+ if (position.row === HEADER) return { rowId: null, field: column.field };
649
+ const row = rows[position.row];
650
+ return row ? { rowId: row.id, field: column.field } : null;
651
+ },
652
+ [columns, rows]
653
+ );
654
+ const fallback = rows.length > 0 && columns.length > 0 ? { rowId: rows[0].id, field: columns[0].field } : columns.length > 0 ? { rowId: null, field: columns[0].field } : null;
655
+ const current = positionOf(active) ?? positionOf(fallback);
656
+ const currentCell = current ? cellAt(current) : null;
657
+ const anchorPosition = positionOf(anchor) ?? current;
658
+ const range = current && anchorPosition && current.row !== HEADER ? rangeOf(anchorPosition, current) : null;
659
+ const onCells = useMemo(() => presenceByCell(presence), [presence]);
660
+ const onRows = useMemo(() => presenceByRow(presence), [presence]);
661
+ const onColumns = useMemo(() => presenceByColumn(presence), [presence]);
662
+ const reportedActive = useRef(null);
663
+ useEffect(() => {
664
+ const cell = currentCell && currentCell.rowId !== null ? { rowId: currentCell.rowId, field: currentCell.field } : null;
665
+ const key = cell ? cellKey(cell.rowId, cell.field) : null;
666
+ if (key === reportedActive.current) return;
667
+ reportedActive.current = key;
668
+ onActiveCellChange?.(cell);
669
+ }, [currentCell, onActiveCellChange]);
670
+ const reportedEditing = useRef(null);
671
+ useEffect(() => {
672
+ const key = editing ? cellKey(editing.rowId, editing.field) : null;
673
+ if (key === reportedEditing.current) return;
674
+ reportedEditing.current = key;
675
+ onEditingChange?.(editing ? { rowId: editing.rowId, field: editing.field } : null);
676
+ }, [editing, onEditingChange]);
677
+ const refuseLocally = useCallback(
678
+ (refusals) => {
679
+ if (refusals.length === 0) return;
680
+ setLocal((before) => {
681
+ const next = new Map(before);
682
+ for (const refusal of refusals) next.set(cellKey(refusal.rowId, refusal.field), refusal.error);
683
+ return next;
684
+ });
685
+ say(refusals[0].error);
686
+ },
687
+ [say]
688
+ );
689
+ const clearLocal = useCallback((keys) => {
690
+ setLocal((before) => {
691
+ if (!keys.some((key) => before.has(key))) return before;
692
+ const next = new Map(before);
693
+ for (const key of keys) next.delete(key);
694
+ return next;
695
+ });
696
+ }, []);
697
+ const emit = useCallback(
698
+ (edits) => {
699
+ if (edits.length === 0 || !onEdit) return;
700
+ clearLocal(edits.map((edit) => cellKey(edit.rowId, edit.field)));
701
+ onEdit(edits);
702
+ },
703
+ [onEdit, clearLocal]
704
+ );
705
+ const columnOf = useCallback((field) => columns[colIndex.get(field) ?? -1], [columns, colIndex]);
706
+ const rowOf = useCallback((rowId) => rows[rowIndex.get(rowId) ?? -1], [rows, rowIndex]);
707
+ const cellEditable = useCallback(
708
+ (column) => editable && column !== void 0 && !column.readOnly,
709
+ [editable]
710
+ );
711
+ const focusCell = useCallback(() => setFocusRequest((n) => n + 1), []);
712
+ const moveTo = useCallback(
713
+ (cell, extend = false) => {
714
+ if (!cell) return;
715
+ setActive(cell);
716
+ if (!extend) setAnchor(cell);
717
+ focusCell();
718
+ },
719
+ [focusCell]
720
+ );
721
+ const onCellFocus = (cell) => {
722
+ if (active && active.rowId === cell.rowId && active.field === cell.field) return;
723
+ setActive(cell);
724
+ setAnchor(cell);
725
+ };
726
+ const activeRow = current?.row ?? null;
727
+ const activeCol = current?.col ?? null;
728
+ useLayoutEffect(() => {
729
+ if (activeRow === null || activeCol === null) return;
730
+ const box = scroller.current;
731
+ if (box && activeRow >= 0) {
732
+ const top = scrollToRow(activeRow, box.scrollTop, box.clientHeight, rowHeight, headerHeight);
733
+ if (top !== box.scrollTop) {
734
+ box.scrollTop = top;
735
+ setScrollTop(top);
736
+ }
737
+ const column = columns[activeCol];
738
+ if (column && !column.frozen) {
739
+ const left = offsets[activeCol] ?? 0;
740
+ const right = left + columnWidth(column);
741
+ if (left - frozenEdge < box.scrollLeft) box.scrollLeft = Math.max(left - frozenEdge, 0);
742
+ else if (right > box.scrollLeft + box.clientWidth) box.scrollLeft = right - box.clientWidth;
743
+ }
744
+ }
745
+ if (focusRequest === handledFocus.current || editing) return;
746
+ handledFocus.current = focusRequest;
747
+ document.getElementById(cellDomId(id, { row: activeRow, col: activeCol }))?.focus({ preventScroll: true });
748
+ }, [activeRow, activeCol, focusRequest, editing, id, rowHeight, headerHeight, columns, offsets, frozenEdge]);
749
+ const beginEdit = useCallback(
750
+ (cell, seed) => {
751
+ if (cell.rowId === null) return;
752
+ const column = columnOf(cell.field);
753
+ const row = rowOf(cell.rowId);
754
+ if (!row || !column) return;
755
+ if (!cellEditable(column)) {
756
+ say(editable ? labels.readOnly(column.label) : labels.forbidden);
757
+ return;
758
+ }
759
+ if (!hasEditor(column.type)) return;
760
+ clearLocal([cellKey(cell.rowId, cell.field)]);
761
+ setEditing({ rowId: cell.rowId, field: cell.field, seed, base: normalizeValue(column, row.values[column.field]) });
762
+ },
763
+ [cellEditable, clearLocal, columnOf, editable, labels, rowOf, say]
764
+ );
765
+ const toggle = useCallback(
766
+ (cell) => {
767
+ if (cell.rowId === null) return;
768
+ const column = columnOf(cell.field);
769
+ const row = rowOf(cell.rowId);
770
+ if (!row || !column || column.type !== "checkbox") return;
771
+ if (!cellEditable(column)) {
772
+ say(editable ? labels.readOnly(column.label) : labels.forbidden);
773
+ return;
774
+ }
775
+ emit([{ rowId: row.id, field: column.field, value: normalizeValue(column, row.values[column.field]) !== true }]);
776
+ },
777
+ [cellEditable, columnOf, editable, emit, labels, rowOf, say]
778
+ );
779
+ const stepFrom = useCallback(
780
+ (at, move) => {
781
+ if (move === null) return cellAt(at);
782
+ const next = {
783
+ down: { row: Math.min(at.row + 1, rows.length - 1), col: at.col },
784
+ up: { row: Math.max(at.row - 1, 0), col: at.col },
785
+ right: { row: at.row, col: Math.min(at.col + 1, columns.length - 1) },
786
+ left: { row: at.row, col: Math.max(at.col - 1, 0) }
787
+ }[move];
788
+ return cellAt(next);
789
+ },
790
+ [cellAt, columns.length, rows.length]
791
+ );
792
+ const endEdit = useCallback(
793
+ (value, move) => {
794
+ if (!editing) return;
795
+ const position = positionOf({ rowId: editing.rowId, field: editing.field });
796
+ const column = columnOf(editing.field);
797
+ const row = rowOf(editing.rowId);
798
+ setEditing(null);
799
+ if (value !== void 0 && column && row && !sameValue(value, normalizeValue(column, row.values[column.field]))) {
800
+ emit([{ rowId: row.id, field: column.field, value }]);
801
+ }
802
+ const holder = document.activeElement;
803
+ const editorHadFocus = holder !== null && holder !== document.body && Boolean(grid.current?.contains(holder));
804
+ if (position && editorHadFocus) moveTo(stepFrom(position, move));
805
+ },
806
+ [columnOf, editing, emit, moveTo, positionOf, rowOf, stepFrom]
807
+ );
808
+ useEffect(() => {
809
+ if (editing && !rowIndex.has(editing.rowId)) setEditing(null);
810
+ }, [editing, rowIndex]);
811
+ const editingRow = editing ? rowOf(editing.rowId) : void 0;
812
+ const editingColumn = editing ? columnOf(editing.field) : void 0;
813
+ const remote = editing && editingRow && editingColumn ? normalizeValue(editingColumn, editingRow.values[editingColumn.field]) : null;
814
+ const conflicted = Boolean(editing && editingRow && editingColumn && !sameValue(remote, editing.base));
815
+ const writer = conflicted && editing ? editingRow?.changed?.[editing.field]?.by ?? labels.someone : "";
816
+ const conflict = conflicted && editing && editingColumn ? {
817
+ writer,
818
+ value: displayText(editingColumn, remote, context) || labels.empty,
819
+ onKeepMine: () => setEditing((now) => now ? { ...now, base: remote } : now),
820
+ onUseTheirs: () => {
821
+ const position = positionOf({ rowId: editing.rowId, field: editing.field });
822
+ setEditing(null);
823
+ if (position) moveTo(cellAt(position));
824
+ }
825
+ } : null;
826
+ const conflictText = conflict ? labels.conflict(conflict.writer, conflict.value) : "";
827
+ useEffect(() => {
828
+ if (conflictText) say(conflictText);
829
+ }, [conflictText, say]);
830
+ const onCopy = (event) => {
831
+ if (editing || !range) return;
832
+ event.preventDefault();
833
+ event.clipboardData.setData("text/plain", toTSV(columns, rows, range, context));
834
+ };
835
+ const clearRange = useCallback(() => {
836
+ if (!range) return;
837
+ if (!editable) {
838
+ say(labels.forbidden);
839
+ return;
840
+ }
841
+ const plan = planClear(columns, rows, range, context);
842
+ emit(plan.edits);
843
+ refuseLocally(plan.refused);
844
+ }, [columns, context, editable, emit, labels.forbidden, range, refuseLocally, rows, say]);
845
+ const onCut = (event) => {
846
+ if (editing || !range) return;
847
+ onCopy(event);
848
+ clearRange();
849
+ };
850
+ const onPaste = (event) => {
851
+ if (editing || !range) return;
852
+ event.preventDefault();
853
+ if (!editable) {
854
+ say(labels.forbidden);
855
+ return;
856
+ }
857
+ const plan = planPaste(columns, rows, range, parseTSV(event.clipboardData.getData("text/plain")), context);
858
+ emit(plan.edits);
859
+ refuseLocally(plan.refused);
860
+ };
861
+ const page = Math.max(1, Math.floor(Math.max(viewport - headerHeight, rowHeight) / rowHeight) - 1);
862
+ const onKeyDown = (event) => {
863
+ if (editing || isComposing(event) || !current || !currentCell) return;
864
+ const column = columns[current.col];
865
+ const mod = isMod(event);
866
+ if (mod && event.altKey && event.key.toLowerCase() === "h") {
867
+ if (onShowHistory && currentCell.rowId !== null) {
868
+ event.preventDefault();
869
+ onShowHistory({ rowId: currentCell.rowId, field: currentCell.field });
870
+ }
871
+ return;
872
+ }
873
+ if (mod && !event.altKey && event.key.toLowerCase() === "z") {
874
+ const handler = event.shiftKey ? onRedo : onUndo;
875
+ if (handler) {
876
+ event.preventDefault();
877
+ handler();
878
+ }
879
+ return;
880
+ }
881
+ if (mod && !event.altKey && event.key.toLowerCase() === "y") {
882
+ if (onRedo) {
883
+ event.preventDefault();
884
+ onRedo();
885
+ }
886
+ return;
887
+ }
888
+ if (mod && !event.altKey && event.key.toLowerCase() === "a") {
889
+ event.preventDefault();
890
+ if (rows.length === 0) return;
891
+ setAnchor(cellAt({ row: 0, col: 0 }));
892
+ setActive(cellAt({ row: rows.length - 1, col: columns.length - 1 }));
893
+ focusCell();
894
+ return;
895
+ }
896
+ const moved = moveFrom(current, event, { rows: rows.length, cols: columns.length, page });
897
+ if (moved) {
898
+ event.preventDefault();
899
+ const extend = event.shiftKey && moved.row !== HEADER && current.row !== HEADER;
900
+ moveTo(cellAt(moved), extend);
901
+ return;
902
+ }
903
+ if (event.key === "Tab") {
904
+ const step = event.shiftKey ? -1 : 1;
905
+ const col = current.col + step;
906
+ if (col < 0 || col >= columns.length) return;
907
+ event.preventDefault();
908
+ moveTo(cellAt({ row: current.row, col }));
909
+ return;
910
+ }
911
+ if (current.row === HEADER) {
912
+ if ((event.key === "Enter" || event.key === " ") && column && onSortChange) {
913
+ event.preventDefault();
914
+ onSortChange(nextSort(sort, column.field));
915
+ }
916
+ return;
917
+ }
918
+ switch (event.key) {
919
+ case "Enter":
920
+ case "F2":
921
+ event.preventDefault();
922
+ if (event.key === "Enter" && mod && column?.type === "url") {
923
+ const row = rows[current.row];
924
+ const url = row ? normalizeValue(column, row.values[column.field]) : null;
925
+ if (typeof url === "string" && url) {
926
+ if (onOpenLink) onOpenLink(url);
927
+ else window.open(url, "_blank", "noopener,noreferrer");
928
+ }
929
+ return;
930
+ }
931
+ if (column?.type === "checkbox") toggle(currentCell);
932
+ else beginEdit(currentCell);
933
+ return;
934
+ case " ":
935
+ event.preventDefault();
936
+ if (column?.type === "checkbox") toggle(currentCell);
937
+ else if (column && takesTyping(column.type)) beginEdit(currentCell, " ");
938
+ return;
939
+ case "Delete":
940
+ case "Backspace":
941
+ event.preventDefault();
942
+ clearRange();
943
+ return;
944
+ case "Escape":
945
+ if (anchor && range && (range.top !== range.bottom || range.left !== range.right)) {
946
+ event.preventDefault();
947
+ setAnchor(currentCell);
948
+ }
949
+ return;
950
+ }
951
+ if (isTypingKey(event) && column) {
952
+ if (column.type === "rating" && /^\d$/.test(event.key)) {
953
+ event.preventDefault();
954
+ if (!cellEditable(column)) {
955
+ say(editable ? labels.readOnly(column.label) : labels.forbidden);
956
+ return;
957
+ }
958
+ const stars = Number(event.key);
959
+ const row = rows[current.row];
960
+ if (!row) return;
961
+ if (stars > ratingMax(column)) {
962
+ refuseLocally([{ rowId: row.id, field: column.field, error: labels.notARating(column.label, ratingMax(column)) }]);
963
+ return;
964
+ }
965
+ emit([{ rowId: row.id, field: column.field, value: stars === 0 ? null : stars }]);
966
+ return;
967
+ }
968
+ if (takesTyping(column.type)) {
969
+ event.preventDefault();
970
+ beginEdit(currentCell, column.type === "date" ? void 0 : event.key);
971
+ }
972
+ }
973
+ };
974
+ useEffect(() => {
975
+ const stop = () => {
976
+ dragging.current = false;
977
+ };
978
+ window.addEventListener("mouseup", stop);
979
+ return () => window.removeEventListener("mouseup", stop);
980
+ }, []);
981
+ const onCellMouseDown = (event, cell) => {
982
+ if (event.button !== 0) return;
983
+ const extend = event.shiftKey && cell.rowId !== null && currentCell?.rowId !== null;
984
+ if (!(editing && editing.rowId === cell.rowId && editing.field === cell.field)) moveTo(cell, extend);
985
+ dragging.current = cell.rowId !== null;
986
+ };
987
+ const onCellMouseEnter = (cell) => {
988
+ if (!dragging.current || cell.rowId === null) return;
989
+ setActive(cell);
990
+ };
991
+ const [first, last] = visibleRows(Math.max(scrollTop - headerHeight, 0), viewport, rowHeight, rows.length);
992
+ const drawn = [];
993
+ for (let index = first; index <= last; index += 1) drawn.push(index);
994
+ if (current && current.row >= 0 && (current.row < first || current.row > last)) drawn.push(current.row);
995
+ if (editing) {
996
+ const at = rowIndex.get(editing.rowId);
997
+ if (at !== void 0 && !drawn.includes(at)) drawn.push(at);
998
+ }
999
+ const describedId = `${id}-described`;
1000
+ const helpId = `${id}-help`;
1001
+ const activeColumn = current ? columns[current.col] : void 0;
1002
+ const activeRecord = current && current.row >= 0 ? rows[current.row] : void 0;
1003
+ const others = activeRecord && activeColumn ? onCells.get(cellKey(activeRecord.id, activeColumn.field)) ?? [] : [];
1004
+ const description = activeRecord && activeColumn ? cellDescription(activeColumn, withLocal(activeRecord, local, activeColumn.field), others, context, said) : "";
1005
+ const style = {
1006
+ "--crewlet-sheet-columns": template,
1007
+ "--crewlet-sheet-row-height": `${rowHeight}px`,
1008
+ width: `${totalWidth}px`
1009
+ };
1010
+ const side = (rowAt) => {
1011
+ const top = headerHeight + rowAt * rowHeight - scrollTop;
1012
+ const view = viewport > 0 ? viewport : rowHeight * 12;
1013
+ return editorSide(top, top + rowHeight, headerHeight, view, rowHeight * 8);
1014
+ };
1015
+ return /* @__PURE__ */ jsxs("div", { className: cx("crewlet-sheet-grid", mode === "read" && "crewlet-sheet-grid--read", className), children: [
1016
+ /* @__PURE__ */ jsx("div", { ref: probe, className: "crewlet-sheet-grid__probe", "aria-hidden": "true" }),
1017
+ /* @__PURE__ */ jsx(
1018
+ "div",
1019
+ {
1020
+ ref: scroller,
1021
+ className: "crewlet-sheet-grid__scroller",
1022
+ style: height !== void 0 ? { height: `${height}px` } : void 0,
1023
+ onScroll: (event) => setScrollTop(event.currentTarget.scrollTop),
1024
+ children: /* @__PURE__ */ jsxs(
1025
+ "div",
1026
+ {
1027
+ ref: grid,
1028
+ role: "grid",
1029
+ "aria-label": label,
1030
+ "aria-rowcount": rows.length + 1,
1031
+ "aria-colcount": columns.length + 1,
1032
+ "aria-readonly": !editable || void 0,
1033
+ "aria-multiselectable": "true",
1034
+ "aria-describedby": helpId,
1035
+ tabIndex: -1,
1036
+ className: "crewlet-sheet-grid__grid",
1037
+ style,
1038
+ onKeyDown,
1039
+ onCopy,
1040
+ onCut,
1041
+ onPaste,
1042
+ children: [
1043
+ /* @__PURE__ */ jsx("div", { role: "rowgroup", className: "crewlet-sheet-grid__head", children: /* @__PURE__ */ jsxs("div", { role: "row", "aria-rowindex": 1, className: "crewlet-sheet-grid__row crewlet-sheet-grid__row--head", children: [
1044
+ /* @__PURE__ */ jsx("div", { role: "columnheader", "aria-colindex": 1, className: "crewlet-sheet-grid__corner", children: /* @__PURE__ */ jsx(VisuallyHidden, { children: labels.rowHeader }) }),
1045
+ columns.map((column, col) => {
1046
+ const position = { row: HEADER, col };
1047
+ const isActive = current?.row === HEADER && current.col === col;
1048
+ const sorted = sort?.field === column.field ? sort.direction : void 0;
1049
+ const here = onColumns.get(column.field) ?? [];
1050
+ return (
1051
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events -- keys reach the grid's own handler, which sorts on Enter and Space
1052
+ /* @__PURE__ */ jsxs(
1053
+ "div",
1054
+ {
1055
+ id: cellDomId(id, position),
1056
+ role: "columnheader",
1057
+ "aria-colindex": col + 2,
1058
+ "aria-sort": sorted ?? (onSortChange ? "none" : void 0),
1059
+ tabIndex: isActive ? 0 : -1,
1060
+ className: cx("crewlet-sheet-grid__header", column.frozen && "crewlet-sheet-grid__cell--frozen", isActive && "crewlet-sheet-grid__header--active"),
1061
+ style: column.frozen ? { insetInlineStart: `${offsets[col]}px` } : void 0,
1062
+ onMouseDown: (event) => onCellMouseDown(event, { rowId: null, field: column.field }),
1063
+ onFocus: (event) => {
1064
+ if (event.target === event.currentTarget) onCellFocus({ rowId: null, field: column.field });
1065
+ },
1066
+ onClick: () => {
1067
+ if (onSortChange) onSortChange(nextSort(sort, column.field));
1068
+ },
1069
+ title: onSortChange ? labels.sortBy(column.label) : void 0,
1070
+ children: [
1071
+ /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-grid__header-label", children: column.label }),
1072
+ /* @__PURE__ */ jsx(VisuallyHidden, { children: `, ${labels.types[column.type]}` }),
1073
+ sorted ? /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-grid__sort", "aria-hidden": "true", children: sorted === "ascending" ? /* @__PURE__ */ jsx(ArrowUpGlyph, { size: "xs" }) : /* @__PURE__ */ jsx(ArrowDownGlyph, { size: "xs" }) }) : null,
1074
+ here.length > 0 ? /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-grid__header-presence", "aria-hidden": "true", children: here.map((entry) => /* @__PURE__ */ jsx(Avatar, { name: entry.name, kind: entry.kind, size: 16, decorative: true, className: cx("crewlet-sheet-grid__header-badge", presenceHueClass(entry.hue)) }, entry.id)) }) : null
1075
+ ]
1076
+ },
1077
+ column.field
1078
+ )
1079
+ );
1080
+ })
1081
+ ] }) }),
1082
+ /* @__PURE__ */ jsx("div", { role: "rowgroup", className: "crewlet-sheet-grid__body", style: { height: `${rows.length * rowHeight}px` }, children: drawn.map((index) => {
1083
+ const row = rows[index];
1084
+ const rowPresence = onRows.get(row.id);
1085
+ return /* @__PURE__ */ jsxs(
1086
+ "div",
1087
+ {
1088
+ role: "row",
1089
+ "aria-rowindex": index + 2,
1090
+ className: "crewlet-sheet-grid__row",
1091
+ style: { top: `${index * rowHeight}px` },
1092
+ children: [
1093
+ /* @__PURE__ */ jsxs(
1094
+ "div",
1095
+ {
1096
+ role: "rowheader",
1097
+ "aria-colindex": 1,
1098
+ className: cx("crewlet-sheet-grid__rownum", rowPresence && "crewlet-sheet-grid__rownum--present", rowPresence && presenceHueClass(rowPresence.hue)),
1099
+ children: [
1100
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: index + 1 }),
1101
+ /* @__PURE__ */ jsx(VisuallyHidden, { children: rowName(columns, row, index, context) })
1102
+ ]
1103
+ }
1104
+ ),
1105
+ columns.map((column, col) => {
1106
+ const position = { row: index, col };
1107
+ const key = cellKey(row.id, column.field);
1108
+ const isActive = current?.row === index && current.col === col;
1109
+ const selected = range ? inRange(range, index, col) : false;
1110
+ const pending = row.pending?.includes(column.field) ?? false;
1111
+ const refused = row.refused?.[column.field] ?? local.get(key);
1112
+ const change = row.changed?.[column.field];
1113
+ const value = normalizeValue(column, row.values[column.field]);
1114
+ const people2 = onCells.get(key);
1115
+ const lead = people2?.[0];
1116
+ const isEditing = editing?.rowId === row.id && editing.field === column.field;
1117
+ return (
1118
+ // eslint-disable-next-line jsx-a11y/click-events-have-key-events -- keys reach the grid's own handler, which toggles on Space and Enter
1119
+ /* @__PURE__ */ jsxs(
1120
+ "div",
1121
+ {
1122
+ id: cellDomId(id, position),
1123
+ role: "gridcell",
1124
+ "aria-colindex": col + 2,
1125
+ "aria-selected": selected,
1126
+ "aria-readonly": !cellEditable(column) || void 0,
1127
+ "aria-describedby": isActive ? describedId : void 0,
1128
+ tabIndex: isActive && !isEditing ? 0 : -1,
1129
+ className: cx(
1130
+ "crewlet-sheet-grid__cell",
1131
+ `crewlet-sheet-grid__cell--${column.type}`,
1132
+ column.frozen && "crewlet-sheet-grid__cell--frozen",
1133
+ selected && "crewlet-sheet-grid__cell--selected",
1134
+ isActive && "crewlet-sheet-grid__cell--active",
1135
+ pending && "crewlet-sheet-grid__cell--pending",
1136
+ refused !== void 0 && "crewlet-sheet-grid__cell--refused",
1137
+ isEditing && "crewlet-sheet-grid__cell--editing",
1138
+ lead && "crewlet-presence-ring",
1139
+ lead && presenceHueClass(lead.hue),
1140
+ lead && (lead.idle ? "crewlet-presence-ring--idle" : null)
1141
+ ),
1142
+ style: column.frozen ? { insetInlineStart: `${offsets[col]}px` } : void 0,
1143
+ onMouseDown: (event) => onCellMouseDown(event, { rowId: row.id, field: column.field }),
1144
+ onFocus: (event) => {
1145
+ if (event.target === event.currentTarget) onCellFocus({ rowId: row.id, field: column.field });
1146
+ },
1147
+ onMouseEnter: () => onCellMouseEnter({ rowId: row.id, field: column.field }),
1148
+ onDoubleClick: () => {
1149
+ if (column.type !== "checkbox") beginEdit({ rowId: row.id, field: column.field });
1150
+ },
1151
+ onClick: (event) => {
1152
+ if (column.type === "checkbox" && event.target.closest(".crewlet-sheet-cell__check")) {
1153
+ toggle({ rowId: row.id, field: column.field });
1154
+ }
1155
+ },
1156
+ children: [
1157
+ /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-grid__value", children: /* @__PURE__ */ jsx(CellDisplay, { column, value, context, onOpenLink }) }),
1158
+ refused !== void 0 ? /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-grid__refused", "aria-hidden": "true", children: /* @__PURE__ */ jsx(CircleAlertGlyph, { size: "xs" }) }) : null,
1159
+ change ? /* @__PURE__ */ jsx(
1160
+ ChangeMark,
1161
+ {
1162
+ by: change.by,
1163
+ at: change.at,
1164
+ previous: hasPrevious(change) ? displayText(column, normalizeValue(column, change.previous), context) || null : null,
1165
+ locale,
1166
+ labels
1167
+ }
1168
+ ) : null,
1169
+ people2 && lead ? /* @__PURE__ */ jsx(CursorFlag, { name: flagText(people2), hue: lead.hue, kind: lead.kind, editing: people2.some((entry) => entry.editing) }) : null,
1170
+ isActive && refused !== void 0 && !isEditing ? /* @__PURE__ */ jsx("span", { className: "crewlet-sheet-grid__message", "aria-hidden": "true", children: refused }) : null,
1171
+ isActive && onShowHistory && !isEditing ? /* @__PURE__ */ jsx(
1172
+ "button",
1173
+ {
1174
+ type: "button",
1175
+ tabIndex: -1,
1176
+ "aria-hidden": "true",
1177
+ className: "crewlet-sheet-grid__history",
1178
+ title: labels.history,
1179
+ onMouseDown: (event) => event.stopPropagation(),
1180
+ onClick: () => onShowHistory({ rowId: row.id, field: column.field }),
1181
+ children: /* @__PURE__ */ jsx(RotateCcwClockGlyph, { size: "xs" })
1182
+ }
1183
+ ) : null,
1184
+ isEditing ? /* @__PURE__ */ jsx(
1185
+ CellEditor,
1186
+ {
1187
+ column,
1188
+ value,
1189
+ context,
1190
+ seed: editing.seed,
1191
+ label: `${column.label}, ${rowName(columns, row, index, context)}`,
1192
+ side: side(index),
1193
+ conflict,
1194
+ onCommit: (next, move) => endEdit(next, move),
1195
+ onCancel: () => endEdit(void 0, null)
1196
+ }
1197
+ ) : null
1198
+ ]
1199
+ },
1200
+ column.field
1201
+ )
1202
+ );
1203
+ })
1204
+ ]
1205
+ },
1206
+ row.id
1207
+ );
1208
+ }) })
1209
+ ]
1210
+ }
1211
+ )
1212
+ }
1213
+ ),
1214
+ /* @__PURE__ */ jsx(VisuallyHidden, { id: describedId, children: description }),
1215
+ /* @__PURE__ */ jsx(VisuallyHidden, { id: helpId, children: labels.keyboardHelp }),
1216
+ /* @__PURE__ */ jsx(VisuallyHidden, { children: /* @__PURE__ */ jsx("span", { role: "status", "aria-live": "polite", children: /* @__PURE__ */ jsx("span", { children: announcement.text }, announcement.n) }) }),
1217
+ onAddRow || sync ? /* @__PURE__ */ jsxs("div", { className: "crewlet-sheet-grid__foot", children: [
1218
+ 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
1223
+ ] }) : null
1224
+ ] });
1225
+ }
1226
+ function cellDomId(grid, position) {
1227
+ return `${grid}-r${position.row < 0 ? "h" : position.row}-c${position.col}`;
1228
+ }
1229
+ function withLocal(row, local, field) {
1230
+ const refusal = local.get(cellKey(row.id, field));
1231
+ if (refusal === void 0 || row.refused?.[field] !== void 0) return row;
1232
+ return { ...row, refused: { ...row.refused, [field]: refusal } };
1233
+ }
1234
+
1235
+ export { CellDisplay, CellEditor, CheckboxCell, DateCell, LongTextCell, MultiSelectCell, NumberCell, PersonCell, RatingCell, SelectCell, SheetCell, SheetGrid, StatusCell, TextCell, UrlCell };