@viax.io/uxm 4.39.0 → 4.40.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 (122) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/LICENSE.md +21 -0
  3. package/README.md +33 -44
  4. package/dist/hooks/index.cjs.map +1 -1
  5. package/dist/hooks/index.d.ts +1 -1
  6. package/dist/hooks/index.js.map +1 -1
  7. package/dist/index.cjs.map +1 -1
  8. package/dist/index.d.ts +3 -3
  9. package/dist/index.js.map +1 -1
  10. package/dist/previews/index.cjs.map +1 -1
  11. package/dist/previews/index.d.ts +2 -2
  12. package/dist/previews/index.js.map +1 -1
  13. package/dist/studio/index.cjs.map +1 -1
  14. package/dist/studio/index.d.ts +2 -2
  15. package/dist/studio/index.js.map +1 -1
  16. package/dist/studio/lib/registry/buttons.cjs +2 -2
  17. package/dist/studio/lib/registry/buttons.cjs.map +1 -1
  18. package/dist/studio/lib/registry/buttons.js +2 -2
  19. package/dist/studio/lib/registry/buttons.js.map +1 -1
  20. package/dist/studio/lib/registry/composite/sidebar-nav-trigger.cjs +1 -1
  21. package/dist/studio/lib/registry/composite/sidebar-nav-trigger.cjs.map +1 -1
  22. package/dist/studio/lib/registry/composite/sidebar-nav-trigger.js +1 -1
  23. package/dist/studio/lib/registry/composite/sidebar-nav-trigger.js.map +1 -1
  24. package/dist/studio/lib/registry/diagram.cjs +1 -1
  25. package/dist/studio/lib/registry/diagram.cjs.map +1 -1
  26. package/dist/studio/lib/registry/diagram.js +1 -1
  27. package/dist/studio/lib/registry/diagram.js.map +1 -1
  28. package/dist/studio/lib/registry/display.cjs +2 -2
  29. package/dist/studio/lib/registry/display.cjs.map +1 -1
  30. package/dist/studio/lib/registry/display.js +2 -2
  31. package/dist/studio/lib/registry/display.js.map +1 -1
  32. package/dist/studio/lib/registry/feedback.cjs +3 -3
  33. package/dist/studio/lib/registry/feedback.cjs.map +1 -1
  34. package/dist/studio/lib/registry/feedback.js +3 -3
  35. package/dist/studio/lib/registry/feedback.js.map +1 -1
  36. package/dist/studio/lib/registry/inputs/color-input.cjs +1 -1
  37. package/dist/studio/lib/registry/inputs/color-input.cjs.map +1 -1
  38. package/dist/studio/lib/registry/inputs/color-input.js +1 -1
  39. package/dist/studio/lib/registry/inputs/color-input.js.map +1 -1
  40. package/dist/studio/lib/registry/inputs/currency-input.cjs +1 -1
  41. package/dist/studio/lib/registry/inputs/currency-input.cjs.map +1 -1
  42. package/dist/studio/lib/registry/inputs/currency-input.js +1 -1
  43. package/dist/studio/lib/registry/inputs/currency-input.js.map +1 -1
  44. package/dist/studio/lib/registry/inputs/editable-cell.cjs +1 -1
  45. package/dist/studio/lib/registry/inputs/editable-cell.cjs.map +1 -1
  46. package/dist/studio/lib/registry/inputs/editable-cell.js +1 -1
  47. package/dist/studio/lib/registry/inputs/editable-cell.js.map +1 -1
  48. package/dist/studio/lib/registry/inputs/file-upload.cjs +1 -1
  49. package/dist/studio/lib/registry/inputs/file-upload.cjs.map +1 -1
  50. package/dist/studio/lib/registry/inputs/file-upload.js +1 -1
  51. package/dist/studio/lib/registry/inputs/file-upload.js.map +1 -1
  52. package/dist/studio/lib/registry/inputs/input-text.cjs +1 -1
  53. package/dist/studio/lib/registry/inputs/input-text.cjs.map +1 -1
  54. package/dist/studio/lib/registry/inputs/input-text.js +1 -1
  55. package/dist/studio/lib/registry/inputs/input-text.js.map +1 -1
  56. package/dist/studio/lib/registry/inputs/listbox.cjs +1 -1
  57. package/dist/studio/lib/registry/inputs/listbox.cjs.map +1 -1
  58. package/dist/studio/lib/registry/inputs/listbox.js +1 -1
  59. package/dist/studio/lib/registry/inputs/listbox.js.map +1 -1
  60. package/dist/studio/lib/registry/inputs/menu.cjs +1 -1
  61. package/dist/studio/lib/registry/inputs/menu.cjs.map +1 -1
  62. package/dist/studio/lib/registry/inputs/menu.js +1 -1
  63. package/dist/studio/lib/registry/inputs/menu.js.map +1 -1
  64. package/dist/studio/lib/registry/inputs/number-input.cjs +1 -1
  65. package/dist/studio/lib/registry/inputs/number-input.cjs.map +1 -1
  66. package/dist/studio/lib/registry/inputs/number-input.js +1 -1
  67. package/dist/studio/lib/registry/inputs/number-input.js.map +1 -1
  68. package/dist/studio/lib/registry/inputs/password-input.cjs +1 -1
  69. package/dist/studio/lib/registry/inputs/password-input.cjs.map +1 -1
  70. package/dist/studio/lib/registry/inputs/password-input.js +1 -1
  71. package/dist/studio/lib/registry/inputs/password-input.js.map +1 -1
  72. package/dist/studio/lib/registry/inputs/phone-input.cjs +1 -1
  73. package/dist/studio/lib/registry/inputs/phone-input.cjs.map +1 -1
  74. package/dist/studio/lib/registry/inputs/phone-input.js +1 -1
  75. package/dist/studio/lib/registry/inputs/phone-input.js.map +1 -1
  76. package/dist/studio/lib/registry/inputs/slider.cjs +1 -1
  77. package/dist/studio/lib/registry/inputs/slider.cjs.map +1 -1
  78. package/dist/studio/lib/registry/inputs/slider.js +1 -1
  79. package/dist/studio/lib/registry/inputs/slider.js.map +1 -1
  80. package/dist/studio/lib/registry/inputs/time-input.cjs +1 -1
  81. package/dist/studio/lib/registry/inputs/time-input.cjs.map +1 -1
  82. package/dist/studio/lib/registry/inputs/time-input.js +1 -1
  83. package/dist/studio/lib/registry/inputs/time-input.js.map +1 -1
  84. package/dist/studio/lib/types.cjs.map +1 -1
  85. package/dist/studio/persistence/generate-css.cjs.map +1 -1
  86. package/dist/studio/persistence/generate-css.js.map +1 -1
  87. package/dist/ui/editable-cell/editable-cell-date.cjs +226 -0
  88. package/dist/ui/editable-cell/editable-cell-date.cjs.map +1 -0
  89. package/dist/ui/editable-cell/editable-cell-date.d.ts +4 -0
  90. package/dist/ui/editable-cell/editable-cell-date.d.ts.map +1 -0
  91. package/dist/ui/editable-cell/editable-cell-date.js +202 -0
  92. package/dist/ui/editable-cell/editable-cell-date.js.map +1 -0
  93. package/dist/ui/editable-cell/editable-cell-select.cjs +297 -0
  94. package/dist/ui/editable-cell/editable-cell-select.cjs.map +1 -0
  95. package/dist/ui/editable-cell/editable-cell-select.d.ts +4 -0
  96. package/dist/ui/editable-cell/editable-cell-select.d.ts.map +1 -0
  97. package/dist/ui/editable-cell/editable-cell-select.js +273 -0
  98. package/dist/ui/editable-cell/editable-cell-select.js.map +1 -0
  99. package/dist/ui/editable-cell/editable-cell-text.cjs +175 -0
  100. package/dist/ui/editable-cell/editable-cell-text.cjs.map +1 -0
  101. package/dist/ui/editable-cell/editable-cell-text.d.ts +4 -0
  102. package/dist/ui/editable-cell/editable-cell-text.d.ts.map +1 -0
  103. package/dist/ui/editable-cell/editable-cell-text.js +151 -0
  104. package/dist/ui/editable-cell/editable-cell-text.js.map +1 -0
  105. package/dist/ui/editable-cell/editable-cell.cjs +27 -705
  106. package/dist/ui/editable-cell/editable-cell.cjs.map +1 -1
  107. package/dist/ui/editable-cell/editable-cell.d.ts +1 -1
  108. package/dist/ui/editable-cell/editable-cell.d.ts.map +1 -1
  109. package/dist/ui/editable-cell/editable-cell.js +28 -706
  110. package/dist/ui/editable-cell/editable-cell.js.map +1 -1
  111. package/dist/ui/editable-cell/use-editable-cell.cjs +284 -0
  112. package/dist/ui/editable-cell/use-editable-cell.cjs.map +1 -0
  113. package/dist/ui/editable-cell/use-editable-cell.d.ts +51 -0
  114. package/dist/ui/editable-cell/use-editable-cell.d.ts.map +1 -0
  115. package/dist/ui/editable-cell/use-editable-cell.js +260 -0
  116. package/dist/ui/editable-cell/use-editable-cell.js.map +1 -0
  117. package/dist/ui/page-shell/page-shell.cjs.map +1 -1
  118. package/dist/ui/page-shell/page-shell.d.ts +1 -1
  119. package/dist/ui/page-shell/page-shell.d.ts.map +1 -1
  120. package/dist/ui/page-shell/page-shell.js.map +1 -1
  121. package/dist/ui/styles.css +1 -1
  122. package/package.json +5 -5
@@ -1,732 +1,54 @@
1
- import { jsx, jsxs } from "react/jsx-runtime";
2
- import { useCallback, useEffect, useMemo, useRef, useState } from "react";
3
- import { cn } from "../../helpers/index.js";
4
- import { Banner } from "../banner/index.js";
5
- import { ButtonGhost } from "../button/index.js";
6
- import { Calendar } from "../calendar/index.js";
7
- import { FORMAT_SPEC, formatDate as formatDateAs, invalidDateMessage, maskDate, parseDate as parseFormattedDate } from "../date-input/index.js";
8
- import { HoverTooltip } from "../hover-tooltip/index.js";
9
- import { Icon } from "../icon/index.js";
10
- import { Listbox, MultiListbox } from "../listbox/index.js";
11
- import { maskNumeric } from "../number-input/index.js";
12
- import { Popover } from "../popover/index.js";
13
- import { isEmptyValue, isoToFormatted, parseISODate, toISODate } from "./editable-cell-utils.js";
1
+ import { jsx } from "react/jsx-runtime";
2
+ import { EditableCellDateView } from "./editable-cell-date.js";
3
+ import { EditableCellSelectView } from "./editable-cell-select.js";
4
+ import { EditableCellTextView } from "./editable-cell-text.js";
5
+ import { useEditableCell } from "./use-editable-cell.js";
14
6
  function EditableCell({
15
- value,
16
- onCommit,
17
7
  type = "text",
18
8
  dateFormat = "ymd",
19
- options,
20
9
  searchable = "auto",
21
10
  clearable = true,
22
11
  align = "left",
23
12
  size = "small",
24
- format,
25
- validate,
26
13
  required = false,
27
- requiredMessage,
28
14
  saveErrorMessage = "Failed to save",
29
15
  invalidNumberMessage = "Enter a number",
30
- invalidDateMessage: invalidDateMessageProp,
31
16
  invalidValueMessage = "Enter a valid value",
32
- disabled,
33
- placeholder,
34
- className,
35
- style,
36
- "aria-label": ariaLabel,
37
17
  clearSelectionLabel = "Clear selection",
38
18
  clearDateLabel = "Clear date",
39
19
  clearValueLabel = "Clear value",
40
20
  openCalendarLabel = "Open calendar",
41
21
  calendarDialogLabel = "Choose date",
42
22
  addDateLabel = "Add date",
43
- forceMode
23
+ ...rest
44
24
  }) {
45
- const [isEditing, setIsEditing] = useState(false);
46
- const [draft, setDraft] = useState(() => {
47
- if (type === "date") return isoToFormatted(value, dateFormat);
48
- if (Array.isArray(value)) return "";
49
- return String(value ?? "");
50
- });
51
- const [submitting, setSubmitting] = useState(false);
52
- const [error, setError] = useState(null);
53
- const [open, setOpen] = useState(false);
54
- const inputRef = useRef(null);
55
- const wrapRef = useRef(null);
56
- const parsedValue = useMemo(
57
- () => type === "date" && typeof value === "string" && value ? parseISODate(value) ?? parseFormattedDate(value, dateFormat) : null,
58
- [type, value, dateFormat]
59
- );
60
- const displayFormatted = useMemo(
61
- () => type === "date" ? isoToFormatted(value, dateFormat) : "",
62
- [type, value, dateFormat]
63
- );
64
- useEffect(() => {
65
- if (!isEditing) {
66
- const next = Array.isArray(value) ? "" : type === "date" ? displayFormatted : String(value ?? "");
67
- setDraft(next);
68
- }
69
- }, [value, isEditing, type, displayFormatted]);
70
- useEffect(() => {
71
- if (isEditing && inputRef.current) {
72
- inputRef.current.focus();
73
- inputRef.current.select();
74
- }
75
- }, [isEditing]);
76
- useEffect(() => {
77
- if (error && type === "date") {
78
- setOpen(false);
79
- }
80
- }, [error, type]);
81
- const parseDraft = useCallback(() => {
82
- if (type === "number") return draft.trim() === "" ? Number.NaN : Number(draft);
83
- if (type === "date") return draft;
84
- return draft;
85
- }, [type, draft]);
86
- const requiredMsg = requiredMessage ?? (type === "select" ? "Select an option" : "Required");
87
- const commitValue = useCallback(
88
- async (next) => {
89
- if (submitting) return false;
90
- if (required && isEmptyValue(next)) {
91
- setError({ message: requiredMsg, severity: "warning" });
92
- return false;
93
- }
94
- const validationError = validate?.(next);
95
- if (validationError) {
96
- setError({ message: validationError, severity: "warning" });
97
- return false;
98
- }
99
- setError(null);
100
- setSubmitting(true);
101
- try {
102
- await onCommit(next);
103
- setIsEditing(false);
104
- setOpen(false);
105
- return true;
106
- } catch (err) {
107
- setError({
108
- message: err instanceof Error ? err.message : saveErrorMessage,
109
- severity: "error"
110
- });
111
- requestAnimationFrame(() => inputRef.current?.focus());
112
- return false;
113
- } finally {
114
- setSubmitting(false);
115
- }
116
- },
117
- [submitting, required, requiredMsg, saveErrorMessage, validate, onCommit]
118
- );
119
- const commitDate = useCallback(
120
- async (iso) => {
121
- if (submitting) return;
122
- const currentIso = parsedValue ? toISODate(parsedValue) : "";
123
- if (iso === currentIso) {
124
- const validationError = validate?.(iso);
125
- if (validationError) {
126
- setError({ message: validationError, severity: "warning" });
127
- return;
128
- }
129
- setIsEditing(false);
130
- setOpen(false);
131
- setError(null);
132
- return;
133
- }
134
- await commitValue(iso);
135
- },
136
- [submitting, parsedValue, validate, commitValue]
137
- );
138
- const handleCommit = useCallback(async () => {
139
- const next = parseDraft();
140
- if (type === "number" && Number.isNaN(next)) {
141
- if (draft.trim() === "") {
142
- if (String(value ?? "") === "") {
143
- setIsEditing(false);
144
- setError(null);
145
- return;
146
- }
147
- await commitValue("");
148
- return;
149
- }
150
- setError({ message: invalidNumberMessage, severity: "warning" });
151
- return;
152
- }
153
- if (type === "date") {
154
- const raw = String(next).trim();
155
- if (raw === "") {
156
- await commitDate("");
157
- return;
158
- }
159
- const parsed = parseFormattedDate(raw, dateFormat) ?? parseISODate(raw);
160
- if (!parsed) {
161
- setError({
162
- message: invalidDateMessageProp ?? invalidDateMessage(dateFormat),
163
- severity: "warning"
164
- });
165
- return;
166
- }
167
- await commitDate(toISODate(parsed));
168
- return;
169
- }
170
- const valueStr = Array.isArray(value) ? "" : String(value ?? "");
171
- if (String(next) === valueStr) {
172
- setIsEditing(false);
173
- setError(null);
174
- return;
175
- }
176
- await commitValue(next);
177
- }, [
178
- parseDraft,
25
+ const props = {
26
+ ...rest,
179
27
  type,
180
- draft,
181
28
  dateFormat,
182
- value,
183
- commitValue,
184
- commitDate,
29
+ searchable,
30
+ clearable,
31
+ align,
32
+ size,
33
+ required,
34
+ saveErrorMessage,
185
35
  invalidNumberMessage,
186
- invalidDateMessageProp
187
- ]);
188
- const handlePick = useCallback(
189
- async (calValue) => {
190
- if (!calValue.start || submitting) return;
191
- setDraft(formatDateAs(calValue.start, dateFormat));
192
- await commitDate(toISODate(calValue.start));
193
- },
194
- [dateFormat, submitting, commitDate]
195
- );
196
- const handleClearDraft = useCallback(() => {
197
- setDraft("");
198
- setError(null);
199
- }, []);
200
- const handleCancel = useCallback(() => {
201
- if (Array.isArray(value)) {
202
- setDraft("");
203
- } else if (type === "date") {
204
- setDraft(displayFormatted);
205
- } else {
206
- setDraft(String(value ?? ""));
207
- }
208
- setIsEditing(false);
209
- setOpen(false);
210
- setError(null);
211
- }, [value, type, displayFormatted]);
212
- const handleKeyDown = useCallback(
213
- (e) => {
214
- if (e.key === "Enter") {
215
- e.preventDefault();
216
- handleCommit();
217
- } else if (e.key === "Escape") {
218
- e.preventDefault();
219
- handleCancel();
220
- }
221
- },
222
- [handleCommit, handleCancel]
223
- );
224
- const editing = forceMode != null || isEditing;
225
- const shownError = forceMode === "error" ? error ?? { message: saveErrorMessage, severity: "error" } : forceMode === "warning" ? error ?? { message: invalidValueMessage, severity: "warning" } : error;
226
- const dateHint = FORMAT_SPEC[dateFormat].placeholder;
227
- const isEmpty = Array.isArray(value) ? value.length === 0 : value === "" || value === null || value === void 0;
228
- const display = (() => {
229
- if (isEmpty) return placeholder ?? (type === "date" ? dateHint : "");
230
- if (format) return format(value);
231
- if (Array.isArray(value)) {
232
- if (options) {
233
- return value.map((v) => options.find((o) => o.value === v)?.label ?? v).join(", ");
234
- }
235
- return value.join(", ");
236
- }
237
- if (type === "select" && options) {
238
- return options.find((o) => o.value === String(value))?.label ?? String(value);
239
- }
240
- return value;
241
- })();
242
- const isMulti = type === "multiselect";
243
- const isSelect = type === "select";
244
- const isDate = type === "date";
245
- const titleText = isEmpty ? placeholder ?? "" : isMulti ? Array.isArray(value) ? (options ?? []).filter((o) => value.includes(o.value)).map((o) => o.label).join(", ") : "" : isSelect ? options?.find((o) => o.value === String(value))?.label ?? String(value) : isDate ? displayFormatted || String(value) : String(value);
246
- if (type === "select" || type === "multiselect") {
247
- const optionItems = options ?? [];
248
- const getKey = (o) => o.value;
249
- const getLabel = (o) => o.label;
250
- if (type === "select") {
251
- const selectedItem = Array.isArray(value) ? null : optionItems.find((o) => o.value === String(value ?? "")) ?? null;
252
- return /* @__PURE__ */ jsxs(
253
- "span",
254
- {
255
- ref: wrapRef,
256
- className: cn(
257
- "uxm-editable-cell",
258
- `uxm-editable-cell--${size}`,
259
- `uxm-editable-cell--align-${align}`,
260
- `uxm-editable-cell--type-${type}`,
261
- clearable && "uxm-editable-cell--clearable",
262
- disabled && "uxm-editable-cell--disabled",
263
- isEmpty && "uxm-editable-cell--empty",
264
- open && "uxm-editable-cell--open",
265
- className
266
- ),
267
- style,
268
- children: [
269
- /* @__PURE__ */ jsx(
270
- Listbox,
271
- {
272
- items: optionItems,
273
- getKey,
274
- getLabel,
275
- value: selectedItem,
276
- onChange: async (next) => {
277
- if (next) await commitValue(next.value);
278
- },
279
- disabled,
280
- searchable,
281
- matchAnchorWidth: true,
282
- onOpenChange: setOpen,
283
- renderTrigger: ({ open: isOpen, triggerProps }) => /* @__PURE__ */ jsxs(
284
- "button",
285
- {
286
- type: "button",
287
- ...triggerProps,
288
- className: cn(
289
- "uxm-editable-cell__select-trigger",
290
- isOpen && "uxm-editable-cell__select-trigger--open"
291
- ),
292
- "aria-label": ariaLabel ?? `Edit ${String(value ?? "")}`,
293
- children: [
294
- /* @__PURE__ */ jsx(HoverTooltip, { content: titleText, disabled, children: /* @__PURE__ */ jsx("span", { className: "uxm-editable-cell__value uxm-editable-cell__option-label", children: display }) }),
295
- /* @__PURE__ */ jsx("span", { className: "uxm-editable-cell__chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Icon, { glyph: "chevron-down", size: 12 }) })
296
- ]
297
- }
298
- ),
299
- renderItem: (o) => /* @__PURE__ */ jsx("span", { className: "uxm-editable-cell__option-label", children: o.label }),
300
- footer: clearable && selectedItem ? ({ close }) => /* @__PURE__ */ jsxs(
301
- ButtonGhost,
302
- {
303
- className: "uxm-listbox__footer-clear-option",
304
- onClick: () => {
305
- commitValue("");
306
- close();
307
- },
308
- children: [
309
- /* @__PURE__ */ jsx(Icon, { glyph: "close", size: 12 }),
310
- "Clear"
311
- ]
312
- }
313
- ) : void 0
314
- }
315
- ),
316
- clearable && !disabled && !submitting && !isEmpty && /* Trigger ✕ — clear from the field itself (the input-family
317
- convention), on every size: commits the empty value directly,
318
- complementing the panel-footer Clear. Sibling of the trigger (a
319
- button can't nest a button), absolutely positioned into the
320
- reserved second gutter slot. Revealed only while the panel is
321
- open (see the `--open` gate in the stylesheet) — unlike the
322
- chevron, which also shows on hover. */
323
- /* @__PURE__ */ jsx(
324
- "button",
325
- {
326
- type: "button",
327
- className: "uxm-editable-cell__trigger-clear",
328
- tabIndex: -1,
329
- onMouseDown: (e) => e.stopPropagation(),
330
- onClick: () => void commitValue(""),
331
- "aria-label": clearSelectionLabel,
332
- children: /* @__PURE__ */ jsx(Icon, { glyph: "close", size: 12 })
333
- }
334
- ),
335
- /* @__PURE__ */ jsx(
336
- Popover,
337
- {
338
- open: Boolean(shownError),
339
- onOpenChange: (next) => {
340
- if (!next) setError(null);
341
- },
342
- anchor: wrapRef,
343
- placement: "bottom-start",
344
- closeOnEscape: false,
345
- closeOnOutsideClick: false,
346
- restoreFocus: false,
347
- role: "presentation",
348
- children: shownError && /* @__PURE__ */ jsx(
349
- Banner,
350
- {
351
- variant: shownError.severity,
352
- id: "uxm-editable-cell__error",
353
- className: "uxm-editable-cell__error-banner",
354
- children: shownError.message
355
- }
356
- )
357
- }
358
- )
359
- ]
360
- }
361
- );
362
- }
363
- const selectedItems = Array.isArray(value) ? optionItems.filter((o) => value.includes(o.value)) : [];
364
- return /* @__PURE__ */ jsxs(
365
- "span",
366
- {
367
- ref: wrapRef,
368
- className: cn(
369
- "uxm-editable-cell",
370
- `uxm-editable-cell--${size}`,
371
- `uxm-editable-cell--align-${align}`,
372
- `uxm-editable-cell--type-${type}`,
373
- clearable && "uxm-editable-cell--clearable",
374
- disabled && "uxm-editable-cell--disabled",
375
- isEmpty && "uxm-editable-cell--empty",
376
- open && "uxm-editable-cell--open",
377
- className
378
- ),
379
- style,
380
- children: [
381
- /* @__PURE__ */ jsx(
382
- MultiListbox,
383
- {
384
- items: optionItems,
385
- getKey,
386
- getLabel,
387
- value: selectedItems,
388
- onChange: (items) => void commitValue(items.map((o) => o.value)),
389
- commitMode: "close",
390
- required,
391
- requiredMessage,
392
- onRequiredViolation: (message) => setError({ message, severity: "warning" }),
393
- disabled,
394
- searchable,
395
- matchAnchorWidth: true,
396
- onOpenChange: (next) => {
397
- setOpen(next);
398
- if (next) setError(null);
399
- },
400
- renderTrigger: ({ open: isOpen, triggerProps }) => /* @__PURE__ */ jsxs(
401
- "button",
402
- {
403
- type: "button",
404
- ...triggerProps,
405
- className: cn(
406
- "uxm-editable-cell__select-trigger",
407
- isOpen && "uxm-editable-cell__select-trigger--open"
408
- ),
409
- "aria-label": ariaLabel ?? `Edit ${String(value ?? "")}`,
410
- children: [
411
- /* @__PURE__ */ jsx(HoverTooltip, { content: titleText, disabled, children: /* @__PURE__ */ jsx("span", { className: "uxm-editable-cell__value uxm-editable-cell__option-label", children: display }) }),
412
- /* @__PURE__ */ jsx("span", { className: "uxm-editable-cell__chevron", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Icon, { glyph: "chevron-down", size: 12 }) })
413
- ]
414
- }
415
- ),
416
- renderItem: (o) => /* @__PURE__ */ jsx("span", { className: "uxm-editable-cell__option-label", children: o.label }),
417
- footer: clearable ? ({ clear, selected }) => selected.length > 0 ? /* @__PURE__ */ jsxs(
418
- ButtonGhost,
419
- {
420
- className: "uxm-listbox__footer-clear-option",
421
- onClick: clear,
422
- children: [
423
- /* @__PURE__ */ jsx(Icon, { glyph: "close", size: 12 }),
424
- "Clear all"
425
- ]
426
- }
427
- ) : null : void 0
428
- }
429
- ),
430
- clearable && !disabled && !submitting && !isEmpty && /* Trigger ✕ — clear from the field itself (the input-family
431
- convention), on every size: commits the empty value directly,
432
- complementing the panel-footer Clear. Sibling of the trigger (a
433
- button can't nest a button), absolutely positioned into the
434
- reserved second gutter slot. Revealed only while the panel is
435
- open (see the `--open` gate in the stylesheet) — unlike the
436
- chevron, which also shows on hover. */
437
- /* @__PURE__ */ jsx(
438
- "button",
439
- {
440
- type: "button",
441
- className: "uxm-editable-cell__trigger-clear",
442
- tabIndex: -1,
443
- onMouseDown: (e) => e.stopPropagation(),
444
- onClick: () => void commitValue([]),
445
- "aria-label": clearSelectionLabel,
446
- children: /* @__PURE__ */ jsx(Icon, { glyph: "close", size: 12 })
447
- }
448
- ),
449
- /* @__PURE__ */ jsx(
450
- Popover,
451
- {
452
- open: Boolean(shownError),
453
- onOpenChange: (next) => {
454
- if (!next) setError(null);
455
- },
456
- anchor: wrapRef,
457
- placement: "bottom-start",
458
- closeOnEscape: false,
459
- closeOnOutsideClick: false,
460
- restoreFocus: false,
461
- role: "presentation",
462
- children: shownError && /* @__PURE__ */ jsx(
463
- Banner,
464
- {
465
- variant: shownError.severity,
466
- id: "uxm-editable-cell__error",
467
- className: "uxm-editable-cell__error-banner",
468
- children: shownError.message
469
- }
470
- )
471
- }
472
- )
473
- ]
474
- }
475
- );
36
+ invalidValueMessage,
37
+ clearSelectionLabel,
38
+ clearDateLabel,
39
+ clearValueLabel,
40
+ openCalendarLabel,
41
+ calendarDialogLabel,
42
+ addDateLabel
43
+ };
44
+ const cell = useEditableCell(props);
45
+ if (props.type === "select" || props.type === "multiselect") {
46
+ return /* @__PURE__ */ jsx(EditableCellSelectView, { props, cell });
476
47
  }
477
- if (type === "date") {
478
- const calendarValue = { start: parsedValue, end: parsedValue };
479
- const spec = FORMAT_SPEC[dateFormat];
480
- if (!editing) {
481
- const displayNode = isEmpty ? placeholder ?? dateHint : format ? format(value) : displayFormatted || value;
482
- return /* @__PURE__ */ jsxs(
483
- "button",
484
- {
485
- type: "button",
486
- className: cn(
487
- "uxm-editable-cell",
488
- `uxm-editable-cell--${size}`,
489
- `uxm-editable-cell--align-${align}`,
490
- `uxm-editable-cell--type-date`,
491
- disabled && "uxm-editable-cell--disabled",
492
- isEmpty && "uxm-editable-cell--empty",
493
- className
494
- ),
495
- style,
496
- onClick: () => {
497
- if (disabled) return;
498
- setIsEditing(true);
499
- setOpen(true);
500
- },
501
- disabled,
502
- "aria-label": ariaLabel ?? (isEmpty ? addDateLabel : `Edit date ${displayFormatted || String(value)}`),
503
- children: [
504
- /* @__PURE__ */ jsx(HoverTooltip, { content: titleText, disabled, children: /* @__PURE__ */ jsx("span", { className: "uxm-editable-cell__value", children: displayNode }) }),
505
- !disabled && /* @__PURE__ */ jsx("span", { className: "uxm-editable-cell__pencil", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Icon, { glyph: "calendar", size: 14 }) })
506
- ]
507
- }
508
- );
509
- }
510
- return /* @__PURE__ */ jsxs(
511
- "span",
512
- {
513
- ref: wrapRef,
514
- className: cn(
515
- "uxm-editable-cell uxm-editable-cell--editing",
516
- `uxm-editable-cell--${size}`,
517
- `uxm-editable-cell--align-${align}`,
518
- `uxm-editable-cell--type-date`,
519
- clearable && "uxm-editable-cell--clearable",
520
- shownError?.severity === "error" && "uxm-editable-cell--error",
521
- shownError?.severity === "warning" && "uxm-editable-cell--warning",
522
- className
523
- ),
524
- style,
525
- children: [
526
- /* @__PURE__ */ jsx("span", { className: "uxm-editable-cell__ghost", "aria-hidden": "true", children: display }),
527
- /* @__PURE__ */ jsx("span", { className: "uxm-editable-cell__ghost", "aria-hidden": "true", children: draft || spec.placeholder }),
528
- /* @__PURE__ */ jsx(
529
- "input",
530
- {
531
- ref: inputRef,
532
- type: "text",
533
- inputMode: "numeric",
534
- className: "uxm-editable-cell__input",
535
- value: draft,
536
- placeholder: spec.placeholder,
537
- onChange: (e) => {
538
- setDraft(maskDate(e.target.value, dateFormat));
539
- if (error) setError(null);
540
- },
541
- onBlur: () => {
542
- if (!submitting) handleCommit();
543
- },
544
- onKeyDown: handleKeyDown,
545
- disabled: submitting,
546
- "aria-invalid": Boolean(shownError),
547
- "aria-describedby": shownError ? "uxm-editable-cell__error" : void 0
548
- }
549
- ),
550
- clearable && !submitting && draft !== "" && /* @__PURE__ */ jsx(
551
- "button",
552
- {
553
- type: "button",
554
- className: "uxm-editable-cell__clear",
555
- tabIndex: -1,
556
- onMouseDown: (e) => e.preventDefault(),
557
- onClick: handleClearDraft,
558
- "aria-label": clearDateLabel,
559
- children: /* @__PURE__ */ jsx(Icon, { glyph: "close", size: 12 })
560
- }
561
- ),
562
- /* @__PURE__ */ jsx(
563
- "button",
564
- {
565
- type: "button",
566
- className: "uxm-editable-cell__date-icon",
567
- tabIndex: -1,
568
- onMouseDown: (e) => e.preventDefault(),
569
- onClick: () => {
570
- if (submitting) return;
571
- setError(null);
572
- setOpen((o) => !o);
573
- },
574
- disabled: submitting,
575
- "aria-label": openCalendarLabel,
576
- "aria-expanded": open && !shownError,
577
- children: /* @__PURE__ */ jsx(Icon, { glyph: "calendar", size: 14 })
578
- }
579
- ),
580
- /* @__PURE__ */ jsx(
581
- Popover,
582
- {
583
- open: open && !shownError,
584
- onOpenChange: setOpen,
585
- anchor: wrapRef,
586
- placement: "bottom-start",
587
- closeOnEscape: true,
588
- closeOnOutsideClick: true,
589
- restoreFocus: false,
590
- role: "dialog",
591
- "aria-label": calendarDialogLabel,
592
- children: /* @__PURE__ */ jsx("div", { onMouseDown: (e) => e.preventDefault(), children: /* @__PURE__ */ jsx(Calendar, { value: calendarValue, onChange: handlePick }) })
593
- }
594
- ),
595
- /* @__PURE__ */ jsx(
596
- Popover,
597
- {
598
- open: Boolean(shownError),
599
- onOpenChange: (next) => {
600
- if (!next) setError(null);
601
- },
602
- anchor: wrapRef,
603
- placement: "bottom-start",
604
- closeOnEscape: false,
605
- closeOnOutsideClick: false,
606
- restoreFocus: false,
607
- role: "presentation",
608
- children: shownError && /* @__PURE__ */ jsx(
609
- Banner,
610
- {
611
- variant: shownError.severity,
612
- id: "uxm-editable-cell__error",
613
- className: "uxm-editable-cell__error-banner",
614
- children: shownError.message
615
- }
616
- )
617
- }
618
- )
619
- ]
620
- }
621
- );
48
+ if (props.type === "date") {
49
+ return /* @__PURE__ */ jsx(EditableCellDateView, { props, cell });
622
50
  }
623
- if (!editing) {
624
- return /* @__PURE__ */ jsxs(
625
- "button",
626
- {
627
- type: "button",
628
- className: cn(
629
- "uxm-editable-cell",
630
- `uxm-editable-cell--${size}`,
631
- `uxm-editable-cell--align-${align}`,
632
- disabled && "uxm-editable-cell--disabled",
633
- isEmpty && "uxm-editable-cell--empty",
634
- className
635
- ),
636
- style,
637
- onClick: () => {
638
- if (!disabled) setIsEditing(true);
639
- },
640
- disabled,
641
- "aria-label": ariaLabel ?? `Edit ${String(value ?? "")}`,
642
- children: [
643
- /* @__PURE__ */ jsx(HoverTooltip, { content: titleText, disabled, children: /* @__PURE__ */ jsx("span", { className: "uxm-editable-cell__value", children: display }) }),
644
- !disabled && /* @__PURE__ */ jsx("span", { className: "uxm-editable-cell__pencil", "aria-hidden": "true", children: /* @__PURE__ */ jsx(Icon, { glyph: "pencil", size: 16, strokeWidth: 1.75 }) })
645
- ]
646
- }
647
- );
648
- }
649
- return /* @__PURE__ */ jsxs(
650
- "span",
651
- {
652
- ref: wrapRef,
653
- className: cn(
654
- "uxm-editable-cell uxm-editable-cell--editing",
655
- `uxm-editable-cell--${size}`,
656
- `uxm-editable-cell--align-${align}`,
657
- clearable && "uxm-editable-cell--clearable",
658
- shownError?.severity === "error" && "uxm-editable-cell--error",
659
- shownError?.severity === "warning" && "uxm-editable-cell--warning",
660
- className
661
- ),
662
- style,
663
- children: [
664
- /* @__PURE__ */ jsx("span", { className: "uxm-editable-cell__ghost", "aria-hidden": "true", children: display }),
665
- /* @__PURE__ */ jsx("span", { className: "uxm-editable-cell__ghost", "aria-hidden": "true", children: draft }),
666
- /* @__PURE__ */ jsx(
667
- "input",
668
- {
669
- ref: inputRef,
670
- type: "text",
671
- inputMode: type === "number" ? "decimal" : void 0,
672
- autoComplete: "off",
673
- className: "uxm-editable-cell__input",
674
- value: draft,
675
- onChange: (e) => {
676
- setDraft(
677
- type === "number" ? maskNumeric(e.target.value, true, true, Number.POSITIVE_INFINITY) : e.target.value
678
- );
679
- if (error) setError(null);
680
- },
681
- onBlur: () => {
682
- if (!submitting) handleCommit();
683
- },
684
- onKeyDown: handleKeyDown,
685
- disabled: submitting,
686
- placeholder,
687
- "aria-invalid": Boolean(shownError),
688
- "aria-describedby": shownError ? "uxm-editable-cell__error" : void 0
689
- }
690
- ),
691
- clearable && !submitting && draft !== "" && /* @__PURE__ */ jsx(
692
- "button",
693
- {
694
- type: "button",
695
- className: "uxm-editable-cell__clear",
696
- tabIndex: -1,
697
- onMouseDown: (e) => e.preventDefault(),
698
- onClick: handleClearDraft,
699
- "aria-label": clearValueLabel,
700
- children: /* @__PURE__ */ jsx(Icon, { glyph: "close", size: 12 })
701
- }
702
- ),
703
- /* @__PURE__ */ jsx(
704
- Popover,
705
- {
706
- open: Boolean(shownError),
707
- onOpenChange: (next) => {
708
- if (!next) setError(null);
709
- },
710
- anchor: wrapRef,
711
- placement: "bottom-start",
712
- closeOnEscape: false,
713
- closeOnOutsideClick: false,
714
- restoreFocus: false,
715
- role: "presentation",
716
- children: shownError && /* @__PURE__ */ jsx(
717
- Banner,
718
- {
719
- variant: shownError.severity,
720
- id: "uxm-editable-cell__error",
721
- className: "uxm-editable-cell__error-banner",
722
- children: shownError.message
723
- }
724
- )
725
- }
726
- )
727
- ]
728
- }
729
- );
51
+ return /* @__PURE__ */ jsx(EditableCellTextView, { props, cell });
730
52
  }
731
53
  export {
732
54
  EditableCell