@viax.io/uxm 4.36.0 → 4.37.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +14 -0
- package/README.md +74 -31
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/index.js.map +1 -1
- package/dist/previews/index.cjs +3 -0
- package/dist/previews/index.cjs.map +1 -1
- package/dist/previews/index.d.ts +3 -2
- package/dist/previews/index.d.ts.map +1 -1
- package/dist/previews/index.js +2 -0
- package/dist/previews/index.js.map +1 -1
- package/dist/studio/index.cjs.map +1 -1
- package/dist/studio/index.d.ts +2 -2
- package/dist/studio/index.js.map +1 -1
- package/dist/studio/lib/registry/app.cjs +52 -0
- package/dist/studio/lib/registry/app.cjs.map +1 -1
- package/dist/studio/lib/registry/app.d.ts.map +1 -1
- package/dist/studio/lib/registry/app.js +52 -0
- package/dist/studio/lib/registry/app.js.map +1 -1
- package/dist/studio/lib/registry/buttons.cjs +2 -2
- package/dist/studio/lib/registry/buttons.cjs.map +1 -1
- package/dist/studio/lib/registry/buttons.js +2 -2
- package/dist/studio/lib/registry/buttons.js.map +1 -1
- package/dist/studio/lib/registry/composite.cjs +1 -1
- package/dist/studio/lib/registry/composite.cjs.map +1 -1
- package/dist/studio/lib/registry/composite.js +1 -1
- package/dist/studio/lib/registry/composite.js.map +1 -1
- package/dist/studio/lib/registry/diagram.cjs +1 -1
- package/dist/studio/lib/registry/diagram.cjs.map +1 -1
- package/dist/studio/lib/registry/diagram.js +1 -1
- package/dist/studio/lib/registry/diagram.js.map +1 -1
- package/dist/studio/lib/registry/display.cjs +2 -2
- package/dist/studio/lib/registry/display.cjs.map +1 -1
- package/dist/studio/lib/registry/display.js +2 -2
- package/dist/studio/lib/registry/display.js.map +1 -1
- package/dist/studio/lib/registry/feedback.cjs +3 -3
- package/dist/studio/lib/registry/feedback.cjs.map +1 -1
- package/dist/studio/lib/registry/feedback.js +3 -3
- package/dist/studio/lib/registry/feedback.js.map +1 -1
- package/dist/studio/lib/registry/inputs.cjs +12 -12
- package/dist/studio/lib/registry/inputs.cjs.map +1 -1
- package/dist/studio/lib/registry/inputs.js +12 -12
- package/dist/studio/lib/registry/inputs.js.map +1 -1
- package/dist/studio/lib/types.cjs.map +1 -1
- package/dist/studio/persistence/generate-css.cjs +15 -0
- package/dist/studio/persistence/generate-css.cjs.map +1 -1
- package/dist/studio/persistence/generate-css.d.ts.map +1 -1
- package/dist/studio/persistence/generate-css.js +15 -0
- package/dist/studio/persistence/generate-css.js.map +1 -1
- package/dist/studio/shell/canvas.cjs +1 -0
- package/dist/studio/shell/canvas.cjs.map +1 -1
- package/dist/studio/shell/canvas.d.ts.map +1 -1
- package/dist/studio/shell/canvas.js +2 -0
- package/dist/studio/shell/canvas.js.map +1 -1
- package/dist/ui/bulk-action-bar/bulk-action-bar.css +3 -0
- package/dist/ui/calendar/calendar.cjs +3 -1
- package/dist/ui/calendar/calendar.cjs.map +1 -1
- package/dist/ui/calendar/calendar.css +2 -1
- package/dist/ui/calendar/calendar.d.ts +6 -2
- package/dist/ui/calendar/calendar.d.ts.map +1 -1
- package/dist/ui/calendar/calendar.js +3 -1
- package/dist/ui/calendar/calendar.js.map +1 -1
- package/dist/ui/currency-input/currency-input.cjs +3 -1
- package/dist/ui/currency-input/currency-input.cjs.map +1 -1
- package/dist/ui/currency-input/currency-input.d.ts +4 -2
- package/dist/ui/currency-input/currency-input.d.ts.map +1 -1
- package/dist/ui/currency-input/currency-input.js +3 -1
- package/dist/ui/currency-input/currency-input.js.map +1 -1
- package/dist/ui/date-input/date-input.cjs +2 -1
- package/dist/ui/date-input/date-input.cjs.map +1 -1
- package/dist/ui/date-input/date-input.css +1 -1
- package/dist/ui/date-input/date-input.d.ts +11 -1
- package/dist/ui/date-input/date-input.d.ts.map +1 -1
- package/dist/ui/date-input/date-input.js +2 -1
- package/dist/ui/date-input/date-input.js.map +1 -1
- package/dist/ui/editable-cell/editable-cell.cjs +20 -6
- package/dist/ui/editable-cell/editable-cell.cjs.map +1 -1
- package/dist/ui/editable-cell/editable-cell.d.ts +24 -1
- package/dist/ui/editable-cell/editable-cell.d.ts.map +1 -1
- package/dist/ui/editable-cell/editable-cell.js +20 -6
- package/dist/ui/editable-cell/editable-cell.js.map +1 -1
- package/dist/ui/file-upload/file-upload.cjs +36 -9
- package/dist/ui/file-upload/file-upload.cjs.map +1 -1
- package/dist/ui/file-upload/file-upload.d.ts +46 -4
- package/dist/ui/file-upload/file-upload.d.ts.map +1 -1
- package/dist/ui/file-upload/file-upload.js +36 -9
- package/dist/ui/file-upload/file-upload.js.map +1 -1
- package/dist/ui/index.cjs +12 -2
- package/dist/ui/index.cjs.map +1 -1
- package/dist/ui/index.d.ts +5 -1
- package/dist/ui/index.d.ts.map +1 -1
- package/dist/ui/index.js +7 -1
- package/dist/ui/index.js.map +1 -1
- package/dist/ui/input/input.cjs +8 -4
- package/dist/ui/input/input.cjs.map +1 -1
- package/dist/ui/input/input.css +4 -0
- package/dist/ui/input/input.d.ts.map +1 -1
- package/dist/ui/input/input.js +8 -4
- package/dist/ui/input/input.js.map +1 -1
- package/dist/ui/language-switcher/index.cjs +29 -0
- package/dist/ui/language-switcher/index.cjs.map +1 -0
- package/dist/ui/language-switcher/index.d.ts +3 -0
- package/dist/ui/language-switcher/index.d.ts.map +1 -0
- package/dist/ui/language-switcher/index.js +5 -0
- package/dist/ui/language-switcher/index.js.map +1 -0
- package/dist/ui/language-switcher/language-switcher-preview.cjs +137 -0
- package/dist/ui/language-switcher/language-switcher-preview.cjs.map +1 -0
- package/dist/ui/language-switcher/language-switcher-preview.d.ts +3 -0
- package/dist/ui/language-switcher/language-switcher-preview.d.ts.map +1 -0
- package/dist/ui/language-switcher/language-switcher-preview.js +113 -0
- package/dist/ui/language-switcher/language-switcher-preview.js.map +1 -0
- package/dist/ui/language-switcher/language-switcher.cjs +127 -0
- package/dist/ui/language-switcher/language-switcher.cjs.map +1 -0
- package/dist/ui/language-switcher/language-switcher.css +95 -0
- package/dist/ui/language-switcher/language-switcher.d.ts +73 -0
- package/dist/ui/language-switcher/language-switcher.d.ts.map +1 -0
- package/dist/ui/language-switcher/language-switcher.js +103 -0
- package/dist/ui/language-switcher/language-switcher.js.map +1 -0
- package/dist/ui/locale/index.cjs +33 -0
- package/dist/ui/locale/index.cjs.map +1 -0
- package/dist/ui/locale/index.d.ts +3 -0
- package/dist/ui/locale/index.d.ts.map +1 -0
- package/dist/ui/locale/index.js +7 -0
- package/dist/ui/locale/index.js.map +1 -0
- package/dist/ui/locale/locale.cjs +43 -0
- package/dist/ui/locale/locale.cjs.map +1 -0
- package/dist/ui/locale/locale.d.ts +49 -0
- package/dist/ui/locale/locale.d.ts.map +1 -0
- package/dist/ui/locale/locale.js +17 -0
- package/dist/ui/locale/locale.js.map +1 -0
- package/dist/ui/page-shell/page-shell.cjs.map +1 -1
- package/dist/ui/page-shell/page-shell.d.ts +1 -1
- package/dist/ui/page-shell/page-shell.d.ts.map +1 -1
- package/dist/ui/page-shell/page-shell.js.map +1 -1
- package/dist/ui/range-slider/range-slider.cjs +7 -3
- package/dist/ui/range-slider/range-slider.cjs.map +1 -1
- package/dist/ui/range-slider/range-slider.d.ts +17 -1
- package/dist/ui/range-slider/range-slider.d.ts.map +1 -1
- package/dist/ui/range-slider/range-slider.js +7 -3
- package/dist/ui/range-slider/range-slider.js.map +1 -1
- package/dist/ui/stat-card/stat-card.cjs +1 -1
- package/dist/ui/stat-card/stat-card.cjs.map +1 -1
- package/dist/ui/stat-card/stat-card.css +4 -0
- package/dist/ui/stat-card/stat-card.js +1 -1
- package/dist/ui/stat-card/stat-card.js.map +1 -1
- package/dist/ui/styles.css +2 -1
- package/dist/ui/toast/toast.css +3 -1
- package/package.json +4 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/ui/date-input/date-input.tsx"],"sourcesContent":["import { useCallback, useEffect, useId, useRef, useState } from 'react';\n\nimport { cn } from '@/helpers';\n\nimport { useFocusOnMount } from '../../hooks/use-focus-on-mount';\nimport { Calendar, type CalendarValue } from '../calendar';\nimport { FieldError } from '../field-error';\nimport { Icon } from '../icon';\nimport { IconButton } from '../icon-button';\n\nimport type { ChangeEvent, FocusEvent, InputHTMLAttributes } from 'react';\n\nexport type DateInputFormat = 'mdy' | 'dmy' | 'ymd';\nexport type DateInputMode = 'single' | 'range';\n\nconst RANGE_SEPARATOR = ' – ';\n\nexport interface DateInputProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value' | 'defaultValue' | 'type' | 'style'> {\n /** Format mask. Drives placeholder, the digit-segment layout, and Calendar ↔ input parsing. */\n format?: DateInputFormat;\n /**\n * Selection mode. `single` picks one date and closes after one click; `range`\n * keeps the popover open after the first click and shows both dates joined by\n * `\" – \"` once the second click commits the end. Range mode makes the input\n * read-only — the `\"MM/DD/YYYY – MM/DD/YYYY\"` shape isn't practical to type.\n */\n mode?: DateInputMode;\n /** Formatted string value (controlled). In range mode use `\"start – end\"`. */\n value?: string;\n /** Initial value for uncontrolled usage. */\n defaultValue?: string;\n /** Called with the masked, formatted value after each keystroke or calendar pick. */\n onChange?: (formatted: string) => void;\n /**\n * Render the calendar icon button and enable the click-to-open popover.\n * Defaults to true. Pass false for a typing-only date field (no icon, no popover).\n */\n calendar?: boolean;\n /**\n * Show a clear (✕) button when the field has a value. On by default (opt out\n * with `clearable={false}`). The ✕ sits just left of the calendar icon (or\n * at the trailing edge when `calendar={false}`). The component owns the reset\n * — it wipes its own uncontrolled state and fires `onChange(\"\")` — so no\n * separate `onClear` is needed. Works in both single and range mode.\n */\n clearable?: boolean;\n /** Accessible name for the clear button. Default `\"Clear\"`. */\n clearLabel?: string;\n /** Accessible name for the calendar trigger. Default `\"Open calendar\"`. */\n openCalendarLabel?: string;\n /** Accessible name for the calendar popover dialog. Default `\"Choose date\"`. */\n calendarDialogLabel?: string;\n /**\n * Inline style applied to the WRAPPER (not the inner <input>). CSS custom\n * properties set here cascade to every descendant — the input AND the\n * popover (a sibling of the input) — so theming knobs reach all parts.\n */\n style?: React.CSSProperties;\n /**\n * When set to a non-empty string, the field renders in its error state:\n * red border (`.uxm-date-input--error`), `aria-invalid` on the input,\n * and the message rendered below the field. Omit (or pass an empty\n * string) for the normal state.\n */\n error?: string;\n}\n\nexport const FORMAT_SPEC: Record<DateInputFormat, { segments: number[]; sep: string; placeholder: string }> = {\n mdy: { segments: [2, 2, 4], sep: '/', placeholder: 'MM/DD/YYYY' },\n dmy: { segments: [2, 2, 4], sep: '/', placeholder: 'DD/MM/YYYY' },\n ymd: { segments: [4, 2, 2], sep: '-', placeholder: 'YYYY-MM-DD' },\n};\n\n/** Auto-format raw input per the chosen format. Strips non-digits, caps at the format's total digit count, inserts separators. */\nexport function maskDate(raw: string, format: DateInputFormat): string {\n const spec = FORMAT_SPEC[format];\n const maxDigits = spec.segments.reduce((sum, n) => sum + n, 0);\n const digits = raw.replace(/\\D/g, '').slice(0, maxDigits);\n let out = '';\n let consumed = 0;\n for (let i = 0; i < spec.segments.length; i++) {\n const segDigits = digits.slice(consumed, consumed + spec.segments[i]);\n if (!segDigits) break;\n if (i > 0) out += spec.sep;\n out += segDigits;\n consumed += spec.segments[i];\n }\n return out;\n}\n\n/**\n * Parse a formatted date string back to a Date so the Calendar can highlight\n * it. Returns null until enough digits are typed for a complete date AND the\n * date is real — a round-trip check rejects impossible dates like Feb 30 or\n * Apr 31, which JS `Date` would otherwise silently roll into the next month.\n * A non-null result is therefore always the exact date the digits spell.\n */\nexport function parseDate(formatted: string, format: DateInputFormat): Date | null {\n const digits = formatted.replace(/\\D/g, '');\n if (digits.length !== 8) return null;\n let y: number, m: number, d: number;\n if (format === 'ymd') {\n y = Number(digits.slice(0, 4));\n m = Number(digits.slice(4, 6));\n d = Number(digits.slice(6, 8));\n } else if (format === 'mdy') {\n m = Number(digits.slice(0, 2));\n d = Number(digits.slice(2, 4));\n y = Number(digits.slice(4, 8));\n } else {\n d = Number(digits.slice(0, 2));\n m = Number(digits.slice(2, 4));\n y = Number(digits.slice(4, 8));\n }\n if (!m || !d || !y || m < 1 || m > 12 || d < 1 || d > 31) return null;\n const date = new Date(y, m - 1, d);\n // Round-trip guard: `new Date(2024, 1, 30)` silently rolls Feb 30 over to\n // Mar 1 rather than failing. Require the constructed date to spell back the\n // exact digits so callers can trust a non-null result is a real date.\n if (date.getFullYear() !== y || date.getMonth() !== m - 1 || date.getDate() !== d) {\n return null;\n }\n return date;\n}\n\n/** Format a Date back into the chosen mask. Inverse of `parseDate`. */\nexport function formatDate(date: Date, format: DateInputFormat): string {\n const m = String(date.getMonth() + 1).padStart(2, '0');\n const d = String(date.getDate()).padStart(2, '0');\n const y = String(date.getFullYear()).padStart(4, '0');\n switch (format) {\n case 'mdy': return `${m}/${d}/${y}`;\n case 'dmy': return `${d}/${m}/${y}`;\n case 'ymd': return `${y}-${m}-${d}`;\n }\n}\n\n/** Range-aware value formatter: `\"<start>\"` or `\"<start> – <end>\"`. */\nfunction formatRangeValue(value: CalendarValue, format: DateInputFormat): string {\n if (!value.start) return '';\n const startStr = formatDate(value.start, format);\n if (!value.end) return startStr;\n return `${startStr}${RANGE_SEPARATOR}${formatDate(value.end, format)}`;\n}\n\n/** Range-aware value parser. Splits on ` – ` and parses each half independently. */\nfunction parseRangeValue(formatted: string, format: DateInputFormat): CalendarValue {\n const parts = formatted.split(RANGE_SEPARATOR);\n return {\n start: parts[0] ? parseDate(parts[0], format) : null,\n end: parts[1] ? parseDate(parts[1], format) : null,\n };\n}\n\n/** Build a placeholder that doubles up the mask for range mode. */\nfunction placeholderFor(format: DateInputFormat, mode: DateInputMode): string {\n const single = FORMAT_SPEC[format].placeholder;\n return mode === 'range' ? `${single}${RANGE_SEPARATOR}${single}` : single;\n}\n\n/**\n * Inline-validation message for an unparseable date, naming the expected mask.\n * Shared with EditableCell's date field so both surface the identical wording.\n */\nexport function invalidDateMessage(format: DateInputFormat): string {\n return `Enter a valid date (${FORMAT_SPEC[format].placeholder})`;\n}\n\nexport function DateInput({\n format = 'mdy',\n mode = 'single',\n className,\n placeholder,\n value,\n defaultValue,\n onChange,\n calendar = true,\n clearable = true,\n clearLabel = 'Clear',\n openCalendarLabel = 'Open calendar',\n calendarDialogLabel = 'Choose date',\n style,\n onFocus,\n onBlur,\n disabled,\n error,\n 'aria-describedby': ariaDescribedBy,\n ...rest\n}: DateInputProps) {\n // Initial state — same masker for single, plain string for range (typing\n // disabled in range mode so no need to mask).\n const [internal, setInternal] = useState<string>(() =>\n mode === 'single' ? maskDate(defaultValue ?? '', format) : defaultValue ?? '',\n );\n const isControlled = value !== undefined;\n const current = isControlled ? value : internal;\n\n const [isOpen, setIsOpen] = useState(false);\n // Inline validity: flipped true on blur when the typed value is non-empty\n // but doesn't resolve to a real date (out-of-range like 06/36, or impossible\n // like Feb 30). Cleared the moment the user edits again and re-checked on the\n // next blur — mirrors EditableCell's date field so the two behave the same.\n const [invalid, setInvalid] = useState(false);\n const errorId = useId();\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Mode OR format switch resets the internal value — a range string is\n // invalid input in single mode (and vice versa), and an existing digit\n // string would otherwise be mis-parsed under a new format's segment\n // boundaries (e.g. \"mdy\" digits re-sliced as \"ymd\"). Also close the\n // popover so the user starts a fresh pick under the new mode/format.\n // Only touches uncontrolled state; if the consumer is driving `value`\n // they own the reset themselves.\n //\n // The ref guard skips the FIRST run of this effect so the `useState`\n // initializer (which masks `defaultValue`) doesn't get clobbered on mount.\n // Without the guard, `<DateInput defaultValue=\"01/01/2020\" />` renders\n // empty because the effect fires once at mount and overwrites the\n // masked initial value with \"\".\n const isFirstModeEffect = useRef(true);\n useEffect(() => {\n if (isFirstModeEffect.current) {\n isFirstModeEffect.current = false;\n return;\n }\n setInternal('');\n setIsOpen(false);\n setInvalid(false);\n }, [mode, format]);\n\n // Close on click outside or Escape. Mounting only when `isOpen` is true\n // keeps the global listeners off when the popover is closed.\n useEffect(() => {\n if (!isOpen) return;\n const handleClick = (e: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setIsOpen(false);\n }\n };\n const handleKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') setIsOpen(false);\n };\n document.addEventListener('mousedown', handleClick);\n document.addEventListener('keydown', handleKey);\n return () => {\n document.removeEventListener('mousedown', handleClick);\n document.removeEventListener('keydown', handleKey);\n };\n }, [isOpen]);\n\n // No focus restoration on close: the field opens the popover on input\n // `onFocus`, so restoring focus to the input after an outside-click close\n // would immediately re-fire `onFocus` and reopen the popover — the same\n // reopen loop TimeInput opts out of. Dismissal paths (outside click on\n // another surface, Escape while the input keeps focus) don't strand focus\n // on the removed panel because the panel itself never takes focus.\n useFocusOnMount({ active: isOpen, returnFocus: false });\n\n const handleInputChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>) => {\n // Range mode is read-only; this guard prevents any sneaky programmatic edits.\n if (mode === 'range') return;\n const formatted = maskDate(e.target.value, format);\n if (!isControlled) setInternal(formatted);\n // Stop showing the error as soon as the user starts fixing the value;\n // it's re-evaluated on the next blur. (No-op re-render when already false.)\n setInvalid(false);\n onChange?.(formatted);\n },\n [mode, isControlled, onChange, format],\n );\n\n const handleCalendarChange = useCallback(\n (calValue: CalendarValue) => {\n if (!calValue.start) return;\n // A calendar pick is always a real date — clear any lingering typed-error.\n setInvalid(false);\n if (mode === 'range') {\n // Calendar fires after every click: end=null after first, end set after second.\n // Update the input either way (so the user sees the in-flight start) but only\n // close when the range is fully committed.\n const formatted = formatRangeValue(calValue, format);\n if (!isControlled) setInternal(formatted);\n onChange?.(formatted);\n if (calValue.end) setIsOpen(false);\n } else {\n // Single mode — first click commits and closes.\n const formatted = formatDate(calValue.start, format);\n if (!isControlled) setInternal(formatted);\n onChange?.(formatted);\n setIsOpen(false);\n }\n },\n [mode, isControlled, onChange, format],\n );\n\n // Clear owns its own reset: wipe the value in both single and range mode\n // and notify via onChange(\"\"). Mirrors NumberInput / CurrencyInput — the\n // component holds its own state, so no onClear prop is needed.\n const handleClear = useCallback(() => {\n if (!isControlled) setInternal('');\n setInvalid(false);\n onChange?.('');\n }, [isControlled, onChange]);\n const showClear = clearable && (current ?? '').length > 0 && !disabled;\n\n // Validate when focus leaves the field. Empty is always fine; a non-empty\n // value that doesn't parse to a real date flips into the error state. Range\n // mode is read-only (filled only via the calendar, which never yields an\n // impossible date), so it never validates here. Chains the consumer's own\n // `onBlur` afterward so wiring it up stays possible.\n const handleBlur = useCallback(\n (e: FocusEvent<HTMLInputElement>) => {\n if (mode !== 'range') {\n const v = current ?? '';\n setInvalid(v !== '' && parseDate(v, format) === null);\n }\n onBlur?.(e);\n },\n [mode, current, format, onBlur],\n );\n\n // Mirror the input's current value into the Calendar so it highlights what\n // the user has selected. Range mode parses two halves; single mode parses one.\n // In single mode we set `end = start` (same-day \"range\") so Calendar's click\n // logic sees a *committed* selection, not an in-flight first-click. Otherwise\n // the next click would be treated as the second click of a range — exactly\n // the bug where switching back to single still drew range styling.\n const calendarValue: CalendarValue = (() => {\n if (mode === 'range') return parseRangeValue(current ?? '', format);\n const date = parseDate(current ?? '', format);\n return { start: date, end: date };\n })();\n\n // A consumer-supplied `error` wins; otherwise surface the internal\n // invalid-date message. Both drive the same visual: red border,\n // `aria-invalid`, and the message below the field.\n const shownError = error || (invalid ? invalidDateMessage(format) : undefined);\n\n return (\n <>\n <div\n className={cn(\n 'uxm-date-input',\n calendar && 'uxm-date-input--has-calendar',\n clearable && 'uxm-date-input--clearable',\n shownError && 'uxm-date-input--error',\n className,\n )}\n ref={containerRef}\n style={style}\n >\n <input\n // `{...rest}` is spread FIRST so the managed props below always win —\n // notably `aria-describedby`, which must stay linked to our error\n // message and not be clobbered by a caller-supplied value.\n {...rest}\n type=\"text\"\n inputMode=\"numeric\"\n readOnly={mode === 'range'}\n className=\"uxm-date-input__input\"\n placeholder={placeholder ?? placeholderFor(format, mode)}\n value={current}\n onChange={handleInputChange}\n onFocus={(e) => {\n if (calendar) setIsOpen(true);\n onFocus?.(e);\n }}\n onBlur={handleBlur}\n aria-invalid={shownError ? true : undefined}\n // Point at our error message while one shows AND keep any\n // caller-supplied `aria-describedby` (e.g. unrelated help text) —\n // both ids are announced, ours never clobbers theirs.\n aria-describedby={[shownError && errorId, ariaDescribedBy].filter(Boolean).join(' ') || undefined}\n disabled={disabled}\n />\n {showClear && (\n <IconButton\n className=\"uxm-field-clear uxm-date-input__clear\"\n aria-label={clearLabel}\n // Prevent the button from stealing focus away from whatever's\n // currently focused — clearing must not re-open the calendar\n // popover via the input's onFocus handler.\n onMouseDown={(e) => e.preventDefault()}\n onClick={handleClear}\n >\n <Icon glyph=\"close\" />\n </IconButton>\n )}\n {calendar && (\n <button\n type=\"button\"\n className=\"uxm-date-input__icon\"\n onClick={() => setIsOpen((o) => !o)}\n aria-label={openCalendarLabel}\n aria-expanded={isOpen}\n disabled={disabled}\n >\n <Icon glyph=\"calendar\" />\n </button>\n )}\n {calendar && isOpen && (\n <div className=\"uxm-date-input__popover\" role=\"dialog\" aria-label={calendarDialogLabel}>\n {/* Swallow mousedown so a calendar click never blurs the input: blur\n now runs validation (handleBlur), and a partially-typed value\n would flash the error state for a frame before the pick lands.\n Focus-retention only — the interactive controls are the\n Calendar's own buttons. */}\n {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- mousedown swallow for focus retention only; nothing interactive lives on this div */}\n <div onMouseDown={(e) => e.preventDefault()}>\n <Calendar value={calendarValue} onChange={handleCalendarChange} />\n </div>\n </div>\n )}\n </div>\n {shownError && (\n <FieldError id={errorId} className=\"uxm-date-input__error-message\">{shownError}</FieldError>\n )}\n </>\n );\n}\n// Static marker so FormField only forwards its `error` prop into children\n// that accept one (avoids React unknown-prop warnings on non-input children).\nDateInput.hasError = true;\n"],"mappings":"AAqVI,mBAYE,KAXF,YADA;AArVJ,SAAS,aAAa,WAAW,OAAO,QAAQ,gBAAgB;AAEhE,SAAS,UAAU;AAEnB,SAAS,uBAAuB;AAChC,SAAS,gBAAoC;AAC7C,SAAS,kBAAkB;AAC3B,SAAS,YAAY;AACrB,SAAS,kBAAkB;AAO3B,MAAM,kBAAkB;AAqDjB,MAAM,cAAiG;AAAA,EAC5G,KAAK,EAAE,UAAU,CAAC,GAAG,GAAG,CAAC,GAAG,KAAK,KAAK,aAAa,aAAa;AAAA,EAChE,KAAK,EAAE,UAAU,CAAC,GAAG,GAAG,CAAC,GAAG,KAAK,KAAK,aAAa,aAAa;AAAA,EAChE,KAAK,EAAE,UAAU,CAAC,GAAG,GAAG,CAAC,GAAG,KAAK,KAAK,aAAa,aAAa;AAClE;AAGO,SAAS,SAAS,KAAa,QAAiC;AACrE,QAAM,OAAO,YAAY,MAAM;AAC/B,QAAM,YAAY,KAAK,SAAS,OAAO,CAAC,KAAK,MAAM,MAAM,GAAG,CAAC;AAC7D,QAAM,SAAS,IAAI,QAAQ,OAAO,EAAE,EAAE,MAAM,GAAG,SAAS;AACxD,MAAI,MAAM;AACV,MAAI,WAAW;AACf,WAAS,IAAI,GAAG,IAAI,KAAK,SAAS,QAAQ,KAAK;AAC7C,UAAM,YAAY,OAAO,MAAM,UAAU,WAAW,KAAK,SAAS,CAAC,CAAC;AACpE,QAAI,CAAC,UAAW;AAChB,QAAI,IAAI,EAAG,QAAO,KAAK;AACvB,WAAO;AACP,gBAAY,KAAK,SAAS,CAAC;AAAA,EAC7B;AACA,SAAO;AACT;AASO,SAAS,UAAU,WAAmB,QAAsC;AACjF,QAAM,SAAS,UAAU,QAAQ,OAAO,EAAE;AAC1C,MAAI,OAAO,WAAW,EAAG,QAAO;AAChC,MAAI,GAAW,GAAW;AAC1B,MAAI,WAAW,OAAO;AACpB,QAAI,OAAO,OAAO,MAAM,GAAG,CAAC,CAAC;AAC7B,QAAI,OAAO,OAAO,MAAM,GAAG,CAAC,CAAC;AAC7B,QAAI,OAAO,OAAO,MAAM,GAAG,CAAC,CAAC;AAAA,EAC/B,WAAW,WAAW,OAAO;AAC3B,QAAI,OAAO,OAAO,MAAM,GAAG,CAAC,CAAC;AAC7B,QAAI,OAAO,OAAO,MAAM,GAAG,CAAC,CAAC;AAC7B,QAAI,OAAO,OAAO,MAAM,GAAG,CAAC,CAAC;AAAA,EAC/B,OAAO;AACL,QAAI,OAAO,OAAO,MAAM,GAAG,CAAC,CAAC;AAC7B,QAAI,OAAO,OAAO,MAAM,GAAG,CAAC,CAAC;AAC7B,QAAI,OAAO,OAAO,MAAM,GAAG,CAAC,CAAC;AAAA,EAC/B;AACA,MAAI,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,IAAI,KAAK,IAAI,MAAM,IAAI,KAAK,IAAI,GAAI,QAAO;AACjE,QAAM,OAAO,IAAI,KAAK,GAAG,IAAI,GAAG,CAAC;AAIjC,MAAI,KAAK,YAAY,MAAM,KAAK,KAAK,SAAS,MAAM,IAAI,KAAK,KAAK,QAAQ,MAAM,GAAG;AACjF,WAAO;AAAA,EACT;AACA,SAAO;AACT;AAGO,SAAS,WAAW,MAAY,QAAiC;AACtE,QAAM,IAAI,OAAO,KAAK,SAAS,IAAI,CAAC,EAAE,SAAS,GAAG,GAAG;AACrD,QAAM,IAAI,OAAO,KAAK,QAAQ,CAAC,EAAE,SAAS,GAAG,GAAG;AAChD,QAAM,IAAI,OAAO,KAAK,YAAY,CAAC,EAAE,SAAS,GAAG,GAAG;AACpD,UAAQ,QAAQ;AAAA,IACd,KAAK;AAAO,aAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;AAAA,IACjC,KAAK;AAAO,aAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;AAAA,IACjC,KAAK;AAAO,aAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;AAAA,EACnC;AACF;AAGA,SAAS,iBAAiB,OAAsB,QAAiC;AAC/E,MAAI,CAAC,MAAM,MAAO,QAAO;AACzB,QAAM,WAAW,WAAW,MAAM,OAAO,MAAM;AAC/C,MAAI,CAAC,MAAM,IAAK,QAAO;AACvB,SAAO,GAAG,QAAQ,GAAG,eAAe,GAAG,WAAW,MAAM,KAAK,MAAM,CAAC;AACtE;AAGA,SAAS,gBAAgB,WAAmB,QAAwC;AAClF,QAAM,QAAQ,UAAU,MAAM,eAAe;AAC7C,SAAO;AAAA,IACL,OAAO,MAAM,CAAC,IAAI,UAAU,MAAM,CAAC,GAAG,MAAM,IAAI;AAAA,IAChD,KAAK,MAAM,CAAC,IAAI,UAAU,MAAM,CAAC,GAAG,MAAM,IAAI;AAAA,EAChD;AACF;AAGA,SAAS,eAAe,QAAyB,MAA6B;AAC5E,QAAM,SAAS,YAAY,MAAM,EAAE;AACnC,SAAO,SAAS,UAAU,GAAG,MAAM,GAAG,eAAe,GAAG,MAAM,KAAK;AACrE;AAMO,SAAS,mBAAmB,QAAiC;AAClE,SAAO,uBAAuB,YAAY,MAAM,EAAE,WAAW;AAC/D;AAEO,SAAS,UAAU;AAAA,EACxB,SAAS;AAAA,EACT,OAAO;AAAA,EACP;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,WAAW;AAAA,EACX,YAAY;AAAA,EACZ,aAAa;AAAA,EACb,oBAAoB;AAAA,EACpB,sBAAsB;AAAA,EACtB;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,oBAAoB;AAAA,EACpB,GAAG;AACL,GAAmB;AAGjB,QAAM,CAAC,UAAU,WAAW,IAAI;AAAA,IAAiB,MAC/C,SAAS,WAAW,SAAS,gBAAgB,IAAI,MAAM,IAAI,gBAAgB;AAAA,EAC7E;AACA,QAAM,eAAe,UAAU;AAC/B,QAAM,UAAU,eAAe,QAAQ;AAEvC,QAAM,CAAC,QAAQ,SAAS,IAAI,SAAS,KAAK;AAK1C,QAAM,CAAC,SAAS,UAAU,IAAI,SAAS,KAAK;AAC5C,QAAM,UAAU,MAAM;AACtB,QAAM,eAAe,OAAuB,IAAI;AAehD,QAAM,oBAAoB,OAAO,IAAI;AACrC,YAAU,MAAM;AACd,QAAI,kBAAkB,SAAS;AAC7B,wBAAkB,UAAU;AAC5B;AAAA,IACF;AACA,gBAAY,EAAE;AACd,cAAU,KAAK;AACf,eAAW,KAAK;AAAA,EAClB,GAAG,CAAC,MAAM,MAAM,CAAC;AAIjB,YAAU,MAAM;AACd,QAAI,CAAC,OAAQ;AACb,UAAM,cAAc,CAAC,MAAkB;AACrC,UAAI,aAAa,WAAW,CAAC,aAAa,QAAQ,SAAS,EAAE,MAAc,GAAG;AAC5E,kBAAU,KAAK;AAAA,MACjB;AAAA,IACF;AACA,UAAM,YAAY,CAAC,MAAqB;AACtC,UAAI,EAAE,QAAQ,SAAU,WAAU,KAAK;AAAA,IACzC;AACA,aAAS,iBAAiB,aAAa,WAAW;AAClD,aAAS,iBAAiB,WAAW,SAAS;AAC9C,WAAO,MAAM;AACX,eAAS,oBAAoB,aAAa,WAAW;AACrD,eAAS,oBAAoB,WAAW,SAAS;AAAA,IACnD;AAAA,EACF,GAAG,CAAC,MAAM,CAAC;AAQX,kBAAgB,EAAE,QAAQ,QAAQ,aAAa,MAAM,CAAC;AAEtD,QAAM,oBAAoB;AAAA,IACxB,CAAC,MAAqC;AAEpC,UAAI,SAAS,QAAS;AACtB,YAAM,YAAY,SAAS,EAAE,OAAO,OAAO,MAAM;AACjD,UAAI,CAAC,aAAc,aAAY,SAAS;AAGxC,iBAAW,KAAK;AAChB,iBAAW,SAAS;AAAA,IACtB;AAAA,IACA,CAAC,MAAM,cAAc,UAAU,MAAM;AAAA,EACvC;AAEA,QAAM,uBAAuB;AAAA,IAC3B,CAAC,aAA4B;AAC3B,UAAI,CAAC,SAAS,MAAO;AAErB,iBAAW,KAAK;AAChB,UAAI,SAAS,SAAS;AAIpB,cAAM,YAAY,iBAAiB,UAAU,MAAM;AACnD,YAAI,CAAC,aAAc,aAAY,SAAS;AACxC,mBAAW,SAAS;AACpB,YAAI,SAAS,IAAK,WAAU,KAAK;AAAA,MACnC,OAAO;AAEL,cAAM,YAAY,WAAW,SAAS,OAAO,MAAM;AACnD,YAAI,CAAC,aAAc,aAAY,SAAS;AACxC,mBAAW,SAAS;AACpB,kBAAU,KAAK;AAAA,MACjB;AAAA,IACF;AAAA,IACA,CAAC,MAAM,cAAc,UAAU,MAAM;AAAA,EACvC;AAKA,QAAM,cAAc,YAAY,MAAM;AACpC,QAAI,CAAC,aAAc,aAAY,EAAE;AACjC,eAAW,KAAK;AAChB,eAAW,EAAE;AAAA,EACf,GAAG,CAAC,cAAc,QAAQ,CAAC;AAC3B,QAAM,YAAY,cAAc,WAAW,IAAI,SAAS,KAAK,CAAC;AAO9D,QAAM,aAAa;AAAA,IACjB,CAAC,MAAoC;AACnC,UAAI,SAAS,SAAS;AACpB,cAAM,IAAI,WAAW;AACrB,mBAAW,MAAM,MAAM,UAAU,GAAG,MAAM,MAAM,IAAI;AAAA,MACtD;AACA,eAAS,CAAC;AAAA,IACZ;AAAA,IACA,CAAC,MAAM,SAAS,QAAQ,MAAM;AAAA,EAChC;AAQA,QAAM,iBAAgC,MAAM;AAC1C,QAAI,SAAS,QAAS,QAAO,gBAAgB,WAAW,IAAI,MAAM;AAClE,UAAM,OAAO,UAAU,WAAW,IAAI,MAAM;AAC5C,WAAO,EAAE,OAAO,MAAM,KAAK,KAAK;AAAA,EAClC,GAAG;AAKH,QAAM,aAAa,UAAU,UAAU,mBAAmB,MAAM,IAAI;AAEpE,SACE,iCACA;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,YAAY;AAAA,UACZ,aAAa;AAAA,UACb,cAAc;AAAA,UACd;AAAA,QACF;AAAA,QACA,KAAK;AAAA,QACL;AAAA,QAEA;AAAA;AAAA,YAAC;AAAA;AAAA,cAIE,GAAG;AAAA,cACJ,MAAK;AAAA,cACL,WAAU;AAAA,cACV,UAAU,SAAS;AAAA,cACnB,WAAU;AAAA,cACV,aAAa,eAAe,eAAe,QAAQ,IAAI;AAAA,cACvD,OAAO;AAAA,cACP,UAAU;AAAA,cACV,SAAS,CAAC,MAAM;AACd,oBAAI,SAAU,WAAU,IAAI;AAC5B,0BAAU,CAAC;AAAA,cACb;AAAA,cACA,QAAQ;AAAA,cACR,gBAAc,aAAa,OAAO;AAAA,cAIlC,oBAAkB,CAAC,cAAc,SAAS,eAAe,EAAE,OAAO,OAAO,EAAE,KAAK,GAAG,KAAK;AAAA,cACxF;AAAA;AAAA,UACF;AAAA,UACC,aACC;AAAA,YAAC;AAAA;AAAA,cACC,WAAU;AAAA,cACV,cAAY;AAAA,cAIZ,aAAa,CAAC,MAAM,EAAE,eAAe;AAAA,cACrC,SAAS;AAAA,cAET,8BAAC,QAAK,OAAM,SAAQ;AAAA;AAAA,UACtB;AAAA,UAED,YACC;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,WAAU;AAAA,cACV,SAAS,MAAM,UAAU,CAAC,MAAM,CAAC,CAAC;AAAA,cAClC,cAAY;AAAA,cACZ,iBAAe;AAAA,cACf;AAAA,cAEA,8BAAC,QAAK,OAAM,YAAW;AAAA;AAAA,UACzB;AAAA,UAED,YAAY,UACX,oBAAC,SAAI,WAAU,2BAA0B,MAAK,UAAS,cAAY,qBAOjE,8BAAC,SAAI,aAAa,CAAC,MAAM,EAAE,eAAe,GACxC,8BAAC,YAAS,OAAO,eAAe,UAAU,sBAAsB,GAClE,GACF;AAAA;AAAA;AAAA,IAEJ;AAAA,IACC,cACC,oBAAC,cAAW,IAAI,SAAS,WAAU,iCAAiC,sBAAW;AAAA,KAEjF;AAEJ;AAGA,UAAU,WAAW;","names":[]}
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/date-input/date-input.tsx"],"sourcesContent":["import { useCallback, useEffect, useId, useRef, useState } from 'react';\n\nimport { cn } from '@/helpers';\n\nimport { useFocusOnMount } from '../../hooks/use-focus-on-mount';\nimport { Calendar, type CalendarValue } from '../calendar';\nimport { FieldError } from '../field-error';\nimport { Icon } from '../icon';\nimport { IconButton } from '../icon-button';\n\nimport type { ChangeEvent, FocusEvent, InputHTMLAttributes } from 'react';\n\nexport type DateInputFormat = 'mdy' | 'dmy' | 'ymd';\nexport type DateInputMode = 'single' | 'range';\n\nconst RANGE_SEPARATOR = ' – ';\n\nexport interface DateInputProps\n extends Omit<InputHTMLAttributes<HTMLInputElement>, 'onChange' | 'value' | 'defaultValue' | 'type' | 'style'> {\n /** Format mask. Drives placeholder, the digit-segment layout, and Calendar ↔ input parsing. */\n format?: DateInputFormat;\n /**\n * Selection mode. `single` picks one date and closes after one click; `range`\n * keeps the popover open after the first click and shows both dates joined by\n * `\" – \"` once the second click commits the end. Range mode makes the input\n * read-only — the `\"MM/DD/YYYY – MM/DD/YYYY\"` shape isn't practical to type.\n */\n mode?: DateInputMode;\n /** Formatted string value (controlled). In range mode use `\"start – end\"`. */\n value?: string;\n /** Initial value for uncontrolled usage. */\n defaultValue?: string;\n /** Called with the masked, formatted value after each keystroke or calendar pick. */\n onChange?: (formatted: string) => void;\n /**\n * Render the calendar icon button and enable the click-to-open popover.\n * Defaults to true. Pass false for a typing-only date field (no icon, no popover).\n */\n calendar?: boolean;\n /**\n * Show a clear (✕) button when the field has a value. On by default (opt out\n * with `clearable={false}`). The ✕ sits just left of the calendar icon (or\n * at the trailing edge when `calendar={false}`). The component owns the reset\n * — it wipes its own uncontrolled state and fires `onChange(\"\")` — so no\n * separate `onClear` is needed. Works in both single and range mode.\n */\n clearable?: boolean;\n /** Accessible name for the clear button. Default `\"Clear\"`. */\n clearLabel?: string;\n /** Accessible name for the calendar trigger. Default `\"Open calendar\"`. */\n openCalendarLabel?: string;\n /** Accessible name for the calendar popover dialog. Default `\"Choose date\"`. */\n calendarDialogLabel?: string;\n /**\n * Replaces the built-in \"unparseable date\" message raised on blur. Defaults\n * to `invalidDateMessage(format)` — `` `Enter a valid date (MM/DD/YYYY)` ``.\n * Unrelated to `error`, which is the consumer's own message and always wins.\n */\n invalidMessage?: string;\n /**\n * Inline style applied to the WRAPPER (not the inner <input>). CSS custom\n * properties set here cascade to every descendant — the input AND the\n * popover (a sibling of the input) — so theming knobs reach all parts.\n */\n style?: React.CSSProperties;\n /**\n * When set to a non-empty string, the field renders in its error state:\n * red border (`.uxm-date-input--error`), `aria-invalid` on the input,\n * and the message rendered below the field. Omit (or pass an empty\n * string) for the normal state.\n */\n error?: string;\n}\n\nexport const FORMAT_SPEC: Record<DateInputFormat, { segments: number[]; sep: string; placeholder: string }> = {\n mdy: { segments: [2, 2, 4], sep: '/', placeholder: 'MM/DD/YYYY' },\n dmy: { segments: [2, 2, 4], sep: '/', placeholder: 'DD/MM/YYYY' },\n ymd: { segments: [4, 2, 2], sep: '-', placeholder: 'YYYY-MM-DD' },\n};\n\n/** Auto-format raw input per the chosen format. Strips non-digits, caps at the format's total digit count, inserts separators. */\nexport function maskDate(raw: string, format: DateInputFormat): string {\n const spec = FORMAT_SPEC[format];\n const maxDigits = spec.segments.reduce((sum, n) => sum + n, 0);\n const digits = raw.replace(/\\D/g, '').slice(0, maxDigits);\n let out = '';\n let consumed = 0;\n for (let i = 0; i < spec.segments.length; i++) {\n const segDigits = digits.slice(consumed, consumed + spec.segments[i]);\n if (!segDigits) break;\n if (i > 0) out += spec.sep;\n out += segDigits;\n consumed += spec.segments[i];\n }\n return out;\n}\n\n/**\n * Parse a formatted date string back to a Date so the Calendar can highlight\n * it. Returns null until enough digits are typed for a complete date AND the\n * date is real — a round-trip check rejects impossible dates like Feb 30 or\n * Apr 31, which JS `Date` would otherwise silently roll into the next month.\n * A non-null result is therefore always the exact date the digits spell.\n */\nexport function parseDate(formatted: string, format: DateInputFormat): Date | null {\n const digits = formatted.replace(/\\D/g, '');\n if (digits.length !== 8) return null;\n let y: number, m: number, d: number;\n if (format === 'ymd') {\n y = Number(digits.slice(0, 4));\n m = Number(digits.slice(4, 6));\n d = Number(digits.slice(6, 8));\n } else if (format === 'mdy') {\n m = Number(digits.slice(0, 2));\n d = Number(digits.slice(2, 4));\n y = Number(digits.slice(4, 8));\n } else {\n d = Number(digits.slice(0, 2));\n m = Number(digits.slice(2, 4));\n y = Number(digits.slice(4, 8));\n }\n if (!m || !d || !y || m < 1 || m > 12 || d < 1 || d > 31) return null;\n const date = new Date(y, m - 1, d);\n // Round-trip guard: `new Date(2024, 1, 30)` silently rolls Feb 30 over to\n // Mar 1 rather than failing. Require the constructed date to spell back the\n // exact digits so callers can trust a non-null result is a real date.\n if (date.getFullYear() !== y || date.getMonth() !== m - 1 || date.getDate() !== d) {\n return null;\n }\n return date;\n}\n\n/** Format a Date back into the chosen mask. Inverse of `parseDate`. */\nexport function formatDate(date: Date, format: DateInputFormat): string {\n const m = String(date.getMonth() + 1).padStart(2, '0');\n const d = String(date.getDate()).padStart(2, '0');\n const y = String(date.getFullYear()).padStart(4, '0');\n switch (format) {\n case 'mdy': return `${m}/${d}/${y}`;\n case 'dmy': return `${d}/${m}/${y}`;\n case 'ymd': return `${y}-${m}-${d}`;\n }\n}\n\n/** Range-aware value formatter: `\"<start>\"` or `\"<start> – <end>\"`. */\nfunction formatRangeValue(value: CalendarValue, format: DateInputFormat): string {\n if (!value.start) return '';\n const startStr = formatDate(value.start, format);\n if (!value.end) return startStr;\n return `${startStr}${RANGE_SEPARATOR}${formatDate(value.end, format)}`;\n}\n\n/** Range-aware value parser. Splits on ` – ` and parses each half independently. */\nfunction parseRangeValue(formatted: string, format: DateInputFormat): CalendarValue {\n const parts = formatted.split(RANGE_SEPARATOR);\n return {\n start: parts[0] ? parseDate(parts[0], format) : null,\n end: parts[1] ? parseDate(parts[1], format) : null,\n };\n}\n\n/** Build a placeholder that doubles up the mask for range mode. */\nfunction placeholderFor(format: DateInputFormat, mode: DateInputMode): string {\n const single = FORMAT_SPEC[format].placeholder;\n return mode === 'range' ? `${single}${RANGE_SEPARATOR}${single}` : single;\n}\n\n/**\n * Inline-validation message for an unparseable date, naming the expected mask.\n * Shared with EditableCell's date field so both surface the identical wording.\n *\n * English-only by design: it is the *default*, not the contract. Localise via\n * `DateInput`'s `invalidMessage` prop (or `EditableCell`'s `invalidDateMessage`),\n * both of which take precedence over this.\n */\nexport function invalidDateMessage(format: DateInputFormat): string {\n return `Enter a valid date (${FORMAT_SPEC[format].placeholder})`;\n}\n\nexport function DateInput({\n format = 'mdy',\n mode = 'single',\n className,\n placeholder,\n value,\n defaultValue,\n onChange,\n calendar = true,\n clearable = true,\n clearLabel = 'Clear',\n openCalendarLabel = 'Open calendar',\n calendarDialogLabel = 'Choose date',\n invalidMessage,\n style,\n onFocus,\n onBlur,\n disabled,\n error,\n 'aria-describedby': ariaDescribedBy,\n ...rest\n}: DateInputProps) {\n // Initial state — same masker for single, plain string for range (typing\n // disabled in range mode so no need to mask).\n const [internal, setInternal] = useState<string>(() =>\n mode === 'single' ? maskDate(defaultValue ?? '', format) : defaultValue ?? '',\n );\n const isControlled = value !== undefined;\n const current = isControlled ? value : internal;\n\n const [isOpen, setIsOpen] = useState(false);\n // Inline validity: flipped true on blur when the typed value is non-empty\n // but doesn't resolve to a real date (out-of-range like 06/36, or impossible\n // like Feb 30). Cleared the moment the user edits again and re-checked on the\n // next blur — mirrors EditableCell's date field so the two behave the same.\n const [invalid, setInvalid] = useState(false);\n const errorId = useId();\n const containerRef = useRef<HTMLDivElement>(null);\n\n // Mode OR format switch resets the internal value — a range string is\n // invalid input in single mode (and vice versa), and an existing digit\n // string would otherwise be mis-parsed under a new format's segment\n // boundaries (e.g. \"mdy\" digits re-sliced as \"ymd\"). Also close the\n // popover so the user starts a fresh pick under the new mode/format.\n // Only touches uncontrolled state; if the consumer is driving `value`\n // they own the reset themselves.\n //\n // The ref guard skips the FIRST run of this effect so the `useState`\n // initializer (which masks `defaultValue`) doesn't get clobbered on mount.\n // Without the guard, `<DateInput defaultValue=\"01/01/2020\" />` renders\n // empty because the effect fires once at mount and overwrites the\n // masked initial value with \"\".\n const isFirstModeEffect = useRef(true);\n useEffect(() => {\n if (isFirstModeEffect.current) {\n isFirstModeEffect.current = false;\n return;\n }\n setInternal('');\n setIsOpen(false);\n setInvalid(false);\n }, [mode, format]);\n\n // Close on click outside or Escape. Mounting only when `isOpen` is true\n // keeps the global listeners off when the popover is closed.\n useEffect(() => {\n if (!isOpen) return;\n const handleClick = (e: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setIsOpen(false);\n }\n };\n const handleKey = (e: KeyboardEvent) => {\n if (e.key === 'Escape') setIsOpen(false);\n };\n document.addEventListener('mousedown', handleClick);\n document.addEventListener('keydown', handleKey);\n return () => {\n document.removeEventListener('mousedown', handleClick);\n document.removeEventListener('keydown', handleKey);\n };\n }, [isOpen]);\n\n // No focus restoration on close: the field opens the popover on input\n // `onFocus`, so restoring focus to the input after an outside-click close\n // would immediately re-fire `onFocus` and reopen the popover — the same\n // reopen loop TimeInput opts out of. Dismissal paths (outside click on\n // another surface, Escape while the input keeps focus) don't strand focus\n // on the removed panel because the panel itself never takes focus.\n useFocusOnMount({ active: isOpen, returnFocus: false });\n\n const handleInputChange = useCallback(\n (e: ChangeEvent<HTMLInputElement>) => {\n // Range mode is read-only; this guard prevents any sneaky programmatic edits.\n if (mode === 'range') return;\n const formatted = maskDate(e.target.value, format);\n if (!isControlled) setInternal(formatted);\n // Stop showing the error as soon as the user starts fixing the value;\n // it's re-evaluated on the next blur. (No-op re-render when already false.)\n setInvalid(false);\n onChange?.(formatted);\n },\n [mode, isControlled, onChange, format],\n );\n\n const handleCalendarChange = useCallback(\n (calValue: CalendarValue) => {\n if (!calValue.start) return;\n // A calendar pick is always a real date — clear any lingering typed-error.\n setInvalid(false);\n if (mode === 'range') {\n // Calendar fires after every click: end=null after first, end set after second.\n // Update the input either way (so the user sees the in-flight start) but only\n // close when the range is fully committed.\n const formatted = formatRangeValue(calValue, format);\n if (!isControlled) setInternal(formatted);\n onChange?.(formatted);\n if (calValue.end) setIsOpen(false);\n } else {\n // Single mode — first click commits and closes.\n const formatted = formatDate(calValue.start, format);\n if (!isControlled) setInternal(formatted);\n onChange?.(formatted);\n setIsOpen(false);\n }\n },\n [mode, isControlled, onChange, format],\n );\n\n // Clear owns its own reset: wipe the value in both single and range mode\n // and notify via onChange(\"\"). Mirrors NumberInput / CurrencyInput — the\n // component holds its own state, so no onClear prop is needed.\n const handleClear = useCallback(() => {\n if (!isControlled) setInternal('');\n setInvalid(false);\n onChange?.('');\n }, [isControlled, onChange]);\n const showClear = clearable && (current ?? '').length > 0 && !disabled;\n\n // Validate when focus leaves the field. Empty is always fine; a non-empty\n // value that doesn't parse to a real date flips into the error state. Range\n // mode is read-only (filled only via the calendar, which never yields an\n // impossible date), so it never validates here. Chains the consumer's own\n // `onBlur` afterward so wiring it up stays possible.\n const handleBlur = useCallback(\n (e: FocusEvent<HTMLInputElement>) => {\n if (mode !== 'range') {\n const v = current ?? '';\n setInvalid(v !== '' && parseDate(v, format) === null);\n }\n onBlur?.(e);\n },\n [mode, current, format, onBlur],\n );\n\n // Mirror the input's current value into the Calendar so it highlights what\n // the user has selected. Range mode parses two halves; single mode parses one.\n // In single mode we set `end = start` (same-day \"range\") so Calendar's click\n // logic sees a *committed* selection, not an in-flight first-click. Otherwise\n // the next click would be treated as the second click of a range — exactly\n // the bug where switching back to single still drew range styling.\n const calendarValue: CalendarValue = (() => {\n if (mode === 'range') return parseRangeValue(current ?? '', format);\n const date = parseDate(current ?? '', format);\n return { start: date, end: date };\n })();\n\n // A consumer-supplied `error` wins; otherwise surface the internal\n // invalid-date message. Both drive the same visual: red border,\n // `aria-invalid`, and the message below the field.\n const shownError =\n error || (invalid ? invalidMessage ?? invalidDateMessage(format) : undefined);\n\n return (\n <>\n <div\n className={cn(\n 'uxm-date-input',\n calendar && 'uxm-date-input--has-calendar',\n clearable && 'uxm-date-input--clearable',\n shownError && 'uxm-date-input--error',\n className,\n )}\n ref={containerRef}\n style={style}\n >\n <input\n // `{...rest}` is spread FIRST so the managed props below always win —\n // notably `aria-describedby`, which must stay linked to our error\n // message and not be clobbered by a caller-supplied value.\n {...rest}\n type=\"text\"\n inputMode=\"numeric\"\n readOnly={mode === 'range'}\n className=\"uxm-date-input__input\"\n placeholder={placeholder ?? placeholderFor(format, mode)}\n value={current}\n onChange={handleInputChange}\n onFocus={(e) => {\n if (calendar) setIsOpen(true);\n onFocus?.(e);\n }}\n onBlur={handleBlur}\n aria-invalid={shownError ? true : undefined}\n // Point at our error message while one shows AND keep any\n // caller-supplied `aria-describedby` (e.g. unrelated help text) —\n // both ids are announced, ours never clobbers theirs.\n aria-describedby={[shownError && errorId, ariaDescribedBy].filter(Boolean).join(' ') || undefined}\n disabled={disabled}\n />\n {showClear && (\n <IconButton\n className=\"uxm-field-clear uxm-date-input__clear\"\n aria-label={clearLabel}\n // Prevent the button from stealing focus away from whatever's\n // currently focused — clearing must not re-open the calendar\n // popover via the input's onFocus handler.\n onMouseDown={(e) => e.preventDefault()}\n onClick={handleClear}\n >\n <Icon glyph=\"close\" />\n </IconButton>\n )}\n {calendar && (\n <button\n type=\"button\"\n className=\"uxm-date-input__icon\"\n onClick={() => setIsOpen((o) => !o)}\n aria-label={openCalendarLabel}\n aria-expanded={isOpen}\n disabled={disabled}\n >\n <Icon glyph=\"calendar\" />\n </button>\n )}\n {calendar && isOpen && (\n <div className=\"uxm-date-input__popover\" role=\"dialog\" aria-label={calendarDialogLabel}>\n {/* Swallow mousedown so a calendar click never blurs the input: blur\n now runs validation (handleBlur), and a partially-typed value\n would flash the error state for a frame before the pick lands.\n Focus-retention only — the interactive controls are the\n Calendar's own buttons. */}\n {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- mousedown swallow for focus retention only; nothing interactive lives on this div */}\n <div onMouseDown={(e) => e.preventDefault()}>\n <Calendar value={calendarValue} onChange={handleCalendarChange} />\n </div>\n </div>\n )}\n </div>\n {shownError && (\n <FieldError id={errorId} className=\"uxm-date-input__error-message\">{shownError}</FieldError>\n )}\n </>\n );\n}\n// Static marker so FormField only forwards its `error` prop into children\n// that accept one (avoids React unknown-prop warnings on non-input children).\nDateInput.hasError = true;\n"],"mappings":"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","names":[]}
|
|
@@ -68,6 +68,10 @@ function EditableCell({
|
|
|
68
68
|
validate,
|
|
69
69
|
required = false,
|
|
70
70
|
requiredMessage,
|
|
71
|
+
saveErrorMessage = "Failed to save",
|
|
72
|
+
invalidNumberMessage = "Enter a number",
|
|
73
|
+
invalidDateMessage: invalidDateMessageProp,
|
|
74
|
+
invalidValueMessage = "Enter a valid value",
|
|
71
75
|
disabled,
|
|
72
76
|
placeholder,
|
|
73
77
|
className,
|
|
@@ -144,7 +148,7 @@ function EditableCell({
|
|
|
144
148
|
return true;
|
|
145
149
|
} catch (err) {
|
|
146
150
|
setError({
|
|
147
|
-
message: err instanceof Error ? err.message :
|
|
151
|
+
message: err instanceof Error ? err.message : saveErrorMessage,
|
|
148
152
|
severity: "error"
|
|
149
153
|
});
|
|
150
154
|
requestAnimationFrame(() => inputRef.current?.focus());
|
|
@@ -153,7 +157,7 @@ function EditableCell({
|
|
|
153
157
|
setSubmitting(false);
|
|
154
158
|
}
|
|
155
159
|
},
|
|
156
|
-
[submitting, required, requiredMsg, validate, onCommit]
|
|
160
|
+
[submitting, required, requiredMsg, saveErrorMessage, validate, onCommit]
|
|
157
161
|
);
|
|
158
162
|
const commitDate = (0, import_react.useCallback)(
|
|
159
163
|
async (iso) => {
|
|
@@ -186,7 +190,7 @@ function EditableCell({
|
|
|
186
190
|
await commitValue("");
|
|
187
191
|
return;
|
|
188
192
|
}
|
|
189
|
-
setError({ message:
|
|
193
|
+
setError({ message: invalidNumberMessage, severity: "warning" });
|
|
190
194
|
return;
|
|
191
195
|
}
|
|
192
196
|
if (type === "date") {
|
|
@@ -198,7 +202,7 @@ function EditableCell({
|
|
|
198
202
|
const parsed = (0, import_date_input.parseDate)(raw, dateFormat) ?? parseISODate(raw);
|
|
199
203
|
if (!parsed) {
|
|
200
204
|
setError({
|
|
201
|
-
message: (0, import_date_input.invalidDateMessage)(dateFormat),
|
|
205
|
+
message: invalidDateMessageProp ?? (0, import_date_input.invalidDateMessage)(dateFormat),
|
|
202
206
|
severity: "warning"
|
|
203
207
|
});
|
|
204
208
|
return;
|
|
@@ -213,7 +217,17 @@ function EditableCell({
|
|
|
213
217
|
return;
|
|
214
218
|
}
|
|
215
219
|
await commitValue(next);
|
|
216
|
-
}, [
|
|
220
|
+
}, [
|
|
221
|
+
parseDraft,
|
|
222
|
+
type,
|
|
223
|
+
draft,
|
|
224
|
+
dateFormat,
|
|
225
|
+
value,
|
|
226
|
+
commitValue,
|
|
227
|
+
commitDate,
|
|
228
|
+
invalidNumberMessage,
|
|
229
|
+
invalidDateMessageProp
|
|
230
|
+
]);
|
|
217
231
|
const handlePick = (0, import_react.useCallback)(
|
|
218
232
|
async (calValue) => {
|
|
219
233
|
if (!calValue.start || submitting) return;
|
|
@@ -251,7 +265,7 @@ function EditableCell({
|
|
|
251
265
|
[handleCommit, handleCancel]
|
|
252
266
|
);
|
|
253
267
|
const editing = forceMode != null || isEditing;
|
|
254
|
-
const shownError = forceMode === "error" ? error ?? { message:
|
|
268
|
+
const shownError = forceMode === "error" ? error ?? { message: saveErrorMessage, severity: "error" } : forceMode === "warning" ? error ?? { message: invalidValueMessage, severity: "warning" } : error;
|
|
255
269
|
const dateHint = import_date_input.FORMAT_SPEC[dateFormat].placeholder;
|
|
256
270
|
const isEmpty = Array.isArray(value) ? value.length === 0 : value === "" || value === null || value === void 0;
|
|
257
271
|
const display = (() => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../src/ui/editable-cell/editable-cell.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type ReactNode,\n} from 'react';\n\nimport { cn } from '@/helpers';\n\nimport { Banner } from '../banner';\nimport { ButtonGhost } from '../button';\nimport { Calendar, type CalendarValue } from '../calendar';\nimport {\n FORMAT_SPEC,\n formatDate as formatDateAs,\n invalidDateMessage,\n maskDate,\n parseDate as parseFormattedDate,\n type DateInputFormat,\n} from '../date-input';\nimport { HoverTooltip } from '../hover-tooltip';\nimport { Icon } from '../icon';\nimport { Listbox, MultiListbox, type ListboxRenderTriggerState } from '../listbox';\nimport { maskNumeric } from '../number-input';\nimport { Popover } from '../popover';\n\nexport type EditableCellType = 'text' | 'number' | 'date' | 'select' | 'multiselect';\nexport type EditableCellAlign = 'left' | 'right' | 'center';\nexport type EditableCellSize = 'small' | 'medium';\nexport type EditableCellValue = string | number | string[];\n\n/** Strict `YYYY-MM-DD` → local Date (no TZ surprises), null if not parseable. */\nfunction parseISODate(s: string): Date | null {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(s);\n if (!m) return null;\n const y = Number(m[1]);\n const mo = Number(m[2]);\n const day = Number(m[3]);\n const d = new Date(y, mo - 1, day);\n // Reject regex-valid but out-of-range dates: `new Date(2024, 1, 30)` silently\n // rolls over to Mar 1 rather than producing NaN, which would later read as a\n // value change and trigger an unprompted rewrite. Require a clean round-trip.\n if (d.getFullYear() !== y || d.getMonth() !== mo - 1 || d.getDate() !== day) return null;\n return d;\n}\n\n/** Serialize a Date to ISO (`YYYY-MM-DD`) — reuses the shared `ymd` formatter. */\nconst toISODate = (d: Date): string => formatDateAs(d, 'ymd');\n\n/**\n * Committed dates are stored ISO (`YYYY-MM-DD`); the input and display show\n * the cell's `dateFormat` mask. Convert ISO → mask so `dmy`/`mdy` cells read\n * and edit in their own shape. Values that aren't ISO (e.g. a consumer's\n * already-formatted seed) pass through untouched.\n */\nfunction isoToFormatted(value: EditableCellValue, format: DateInputFormat): string {\n if (typeof value !== 'string' || !value) return '';\n const d = parseISODate(value);\n return d ? formatDateAs(d, format) : value;\n}\n\n/**\n * Emptiness rule for the `required` check — the single definition shared by\n * every editor type: an empty array (multiselect), a NaN number (blank numeric\n * draft), or a blank/whitespace string (text/select/date).\n */\nfunction isEmptyValue(v: EditableCellValue): boolean {\n if (Array.isArray(v)) return v.length === 0;\n if (typeof v === 'number') return Number.isNaN(v);\n return String(v ?? '').trim() === '';\n}\n\nexport interface EditableCellOption {\n value: string;\n label: string;\n}\n\n/**\n * Two severity levels, mapped to Banner variants:\n * - `warning` — recoverable input problems the user can fix in place\n * (sync `validate` failures, unparseable number drafts).\n * - `error` — the commit itself failed (`onCommit` rejected): the value\n * was fine but saving didn't happen.\n */\ninterface CellError {\n message: string;\n severity: 'warning' | 'error';\n}\n\nexport interface EditableCellProps {\n /** Current committed value. The atom keeps a draft internally while editing. */\n value: EditableCellValue;\n /**\n * Called when the user commits the change (Enter, or blur with no\n * validation error). The promise's settlement controls error display:\n * resolve → exit edit mode, reject → stay in edit mode with the error\n * surfaced inline so the user can correct.\n */\n onCommit: (next: EditableCellValue) => void | Promise<void>;\n /** Editor type. Text uses a string input; number coerces to Number on commit (an emptied draft commits `\"\"` — the uniform \"cleared\" value). Defaults to \"text\". */\n type?: EditableCellType;\n /** Date format — only used when `type=\"date\"`. Defaults to `\"mdy\"`. */\n dateFormat?: DateInputFormat;\n /** Options — required for `type=\"select\"` and `type=\"multiselect\"`. */\n options?: EditableCellOption[];\n /**\n * Whether the select panel includes a search box. Defaults to `\"auto\"` —\n * the box appears only once the option count passes the shared Listbox\n * threshold (6) — so a select/multiselect cell matches the `Select` atom's\n * behavior instead of inheriting the raw Listbox `true` default.\n */\n searchable?: boolean | 'auto';\n /**\n * Clear affordance — the surface differs per editor type:\n * - `select` / `multiselect` — a \"Clear\" / \"Clear all\" action in the\n * dropdown footer (commits \"\" / empties the draft).\n * - `text` / `number` / `date` — a ✕ inside the EDITING input (mirrors\n * TextInput/NumberInput/DateInput) that empties the draft and keeps\n * focus; nothing commits until Enter/blur, so `required`/`validate`\n * still guard, and Esc still restores the old value. Available on\n * required cells too — \"wipe it and type the right value\" is the\n * point. Display mode never shows a ✕ — the pencil owns that gutter,\n * and a one-click destroy on a static table cell invites accidents.\n *\n * `required` never hides the affordance — it guards the OUTCOME instead:\n * clearing a required cell surfaces the required warning (the value stays),\n * clearing an optional one empties it back to the placeholder.\n *\n * Defaults to `true` — the input-family default (TextInput, NumberInput,\n * DateInput …), so consumers like DataTable get the affordance uniformly\n * without per-column wiring. Pass `false` to opt out.\n */\n clearable?: boolean;\n /** Text alignment — pass through from a DataTable column's `align` so the editing input matches the display alignment. */\n align?: EditableCellAlign;\n /**\n * Size preset. `small` (default) is the dense table scale — the cell reads\n * like a static text cell inside a DataTable row (its font falls back to\n * the inherited size). `medium` steps the cell up to the input-family\n * scale (12/6 padding, 14px font) for standalone use in side panels /\n * detail views, where a table-dense cell looks undersized next to real\n * inputs. Each size owns a symmetric\n * `--uxm-editable-cell-{size}-{padding-x,padding-y,font-size}` knob set;\n * height always derives from font-size + padding (a `1lh` floor keeps\n * empty cells clickable).\n */\n size?: EditableCellSize;\n /**\n * Display-mode formatter. Receives the raw value, returns the React node\n * to render in display mode. Never called for an empty value — a cleared\n * cell renders its `placeholder` instead (so a Tag/Badge formatter can't\n * paint an empty pill).\n */\n format?: (value: EditableCellValue) => ReactNode;\n /** Synchronous validation. Return an error message to block commit; return null/undefined to accept. */\n validate?: (next: EditableCellValue) => string | null | undefined;\n /**\n * Mark the cell required — an empty value blocks commit and surfaces a\n * warning, checked BEFORE `validate` so you don't hand-write the empty rule.\n * Uniform across every editor type: `multiselect` → non-empty array (the\n * rule lives in MultiListbox, shared with every other picker), `select` →\n * a chosen option, `text`/`number`/`date` → a non-blank value.\n */\n required?: boolean;\n /** Override the default required message (per-type: \"Select at least one option\" / \"Select an option\" / \"Required\"). */\n requiredMessage?: string;\n /** Read-only — clicking does nothing, no edit affordance. */\n disabled?: boolean;\n /** Shown when value is empty/blank. */\n placeholder?: string;\n className?: string;\n style?: CSSProperties;\n 'aria-label'?: string;\n /**\n * Accessible names for the editors' icon-only controls. Which ones apply\n * depends on the editor type. The composed \"Edit {value}\" trigger names\n * are not here — pass `aria-label` to replace those wholesale.\n */\n /** Clear button, select + multiselect editors. Default `\"Clear selection\"`. */\n clearSelectionLabel?: string;\n /** Clear button, date editor. Default `\"Clear date\"`. */\n clearDateLabel?: string;\n /** Clear button, text / number editors. Default `\"Clear value\"`. */\n clearValueLabel?: string;\n /** Calendar trigger, date editor. Default `\"Open calendar\"`. */\n openCalendarLabel?: string;\n /** Calendar popover dialog, date editor. Default `\"Choose date\"`. */\n calendarDialogLabel?: string;\n /** Trigger name when the date editor is empty. Default `\"Add date\"`. */\n addDateLabel?: string;\n /**\n * Preview-only branch override. UXM's canvas passes this to render the\n * input branch without a real click — `editing` shows the input, `warning`\n * / `error` also surface a sample message at that severity so the matching\n * knobs paint. It does NOT focus the input (the canvas must never steal\n * focus from the editor panel); the preview mirrors the focus visuals via\n * a forced CSS class instead. Production consumers leave it unset.\n */\n forceMode?: 'editing' | 'warning' | 'error';\n}\n\n/**\n * Inline-editable cell. Three modes:\n *\n * - **Display** (default) — renders the formatted value as a button-styled\n * text run. Click (or Enter/Space when focused) to enter edit mode.\n * - **Editing** — replaces the display with a focused, select-all'd input.\n * Enter commits, Esc cancels, blur commits.\n * - **Submitting** — input disabled while an async `onCommit` is pending.\n *\n * Problems surface in a Popover anchored under the cell (never inline — a\n * table row must not change height) carrying a compact Banner: `warning`\n * (yellow) for sync validate failures the user can fix in place, `error`\n * (red) for a rejected `onCommit`. Either way the user stays in edit mode\n * to correct or retry.\n *\n * Designed primarily for DataTable cells, but works anywhere an inline\n * commit semantic is needed (PageHeader rename, StatCard label, etc.).\n */\nexport function EditableCell({\n value,\n onCommit,\n type = 'text',\n dateFormat = 'ymd',\n options,\n searchable = 'auto',\n clearable = true,\n align = 'left',\n size = 'small',\n format,\n validate,\n required = false,\n requiredMessage,\n disabled,\n placeholder,\n className,\n style,\n 'aria-label': ariaLabel,\n clearSelectionLabel = 'Clear selection',\n clearDateLabel = 'Clear date',\n clearValueLabel = 'Clear value',\n openCalendarLabel = 'Open calendar',\n calendarDialogLabel = 'Choose date',\n addDateLabel = 'Add date',\n forceMode,\n}: EditableCellProps) {\n const [isEditing, setIsEditing] = useState(false);\n const [draft, setDraft] = useState<string>(() => {\n if (type === 'date') return isoToFormatted(value, dateFormat);\n if (Array.isArray(value)) return '';\n return String(value ?? '');\n });\n const [submitting, setSubmitting] = useState(false);\n const [error, setError] = useState<CellError | null>(null);\n const [open, setOpen] = useState(false);\n const inputRef = useRef<HTMLInputElement | null>(null);\n // Anchor for the error popover — the editing wrapper, so the banner hangs\n // under the cell without affecting the table row's layout.\n const wrapRef = useRef<HTMLSpanElement | null>(null);\n\n // Committed date as a Date and in the cell's mask, memoized so the display,\n // tooltip, aria-label, calendar highlight, draft sync, and cancel-reset all\n // share one parse/format instead of each re-running it. Cheap `null`/`''`\n // no-ops for non-date types. Declared before the effects so they can reuse it.\n const parsedValue = useMemo(\n () =>\n type === 'date' && typeof value === 'string' && value\n ? parseISODate(value) ?? parseFormattedDate(value, dateFormat)\n : null,\n [type, value, dateFormat],\n );\n const displayFormatted = useMemo(\n () => (type === 'date' ? isoToFormatted(value, dateFormat) : ''),\n [type, value, dateFormat],\n );\n\n // When the externally-committed value changes (parent state updated, or\n // someone else edited the row), sync the draft — but only when we're\n // NOT mid-edit. Mid-edit syncs would clobber what the user is typing.\n useEffect(() => {\n if (!isEditing) {\n const next = Array.isArray(value)\n ? ''\n : type === 'date'\n ? displayFormatted\n : String(value ?? '');\n // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional external-value → draft sync, gated to non-editing mode\n setDraft(next);\n }\n }, [value, isEditing, type, displayFormatted]);\n\n // Focus + select-all when entering edit mode. Select-all means typing\n // immediately replaces the value — the most common edit intent — while\n // keyboard navigation (Home/End/arrows) still works to refine.\n useEffect(() => {\n if (isEditing && inputRef.current) {\n inputRef.current.focus();\n inputRef.current.select();\n }\n }, [isEditing]);\n\n // A surfaced problem and the date calendar can't share the anchor, so the\n // calendar is hidden while an error shows. Close it outright (not just via\n // the render gate) so clearing the error later — e.g. the next keystroke's\n // `setError(null)` — doesn't silently reopen a calendar the user dismissed.\n useEffect(() => {\n if (error && type === 'date') {\n // eslint-disable-next-line react-hooks/set-state-in-effect -- close the calendar exactly when a date error appears, so it can't auto-reopen on clear\n setOpen(false);\n }\n }, [error, type]);\n\n const parseDraft = useCallback((): EditableCellValue => {\n // `Number('')` is 0, not NaN — map an empty/whitespace draft to NaN so\n // blur-with-empty hits the \"Enter a number\" boundary instead of silently\n // committing 0.\n if (type === 'number') return draft.trim() === '' ? Number.NaN : Number(draft);\n if (type === 'date') return draft;\n return draft;\n }, [type, draft]);\n\n // Per-type default required message; overridable via `requiredMessage`.\n // (multiselect's own default lives in MultiListbox, which owns that rule.)\n const requiredMsg =\n requiredMessage ?? (type === 'select' ? 'Select an option' : 'Required');\n\n const commitValue = useCallback(\n async (next: EditableCellValue): Promise<boolean> => {\n // Re-entrancy guard lives HERE, not only in each caller's render\n // condition: every commit path funnels through this function, so a new\n // call site can't forget to gate itself (`commitDate` keeps its own\n // early return for the same reason).\n if (submitting) return false;\n // Required is checked before `validate` so consumers get the empty rule\n // for free. Multiselect empties are already blocked upstream by\n // MultiListbox (they never reach here), so this covers the scalar types.\n if (required && isEmptyValue(next)) {\n setError({ message: requiredMsg, severity: 'warning' });\n return false;\n }\n const validationError = validate?.(next);\n if (validationError) {\n setError({ message: validationError, severity: 'warning' });\n return false;\n }\n\n setError(null);\n setSubmitting(true);\n try {\n await onCommit(next);\n setIsEditing(false);\n setOpen(false);\n return true;\n } catch (err) {\n // Error severity: the value was acceptable but saving failed.\n setError({\n message: err instanceof Error ? err.message : 'Failed to save',\n severity: 'error',\n });\n // Refocus the input so the user can retry without re-entering edit mode.\n requestAnimationFrame(() => inputRef.current?.focus());\n return false;\n } finally {\n setSubmitting(false);\n }\n },\n [submitting, required, requiredMsg, validate, onCommit],\n );\n\n // Shared commit path for both the typed input (handleCommit) and the calendar\n // (handlePick). Guards a re-entrant commit while one is in flight, and skips\n // the consumer's onCommit when nothing changed — comparing normalized ISO on\n // BOTH sides so a formatted seed (or a re-picked day) isn't mistaken for a\n // change — while still running `validate` on the no-op so a required /\n // row-dependent rule still surfaces.\n const commitDate = useCallback(\n async (iso: string) => {\n if (submitting) return;\n const currentIso = parsedValue ? toISODate(parsedValue) : '';\n if (iso === currentIso) {\n const validationError = validate?.(iso);\n if (validationError) {\n setError({ message: validationError, severity: 'warning' });\n return;\n }\n setIsEditing(false);\n setOpen(false);\n setError(null);\n return;\n }\n await commitValue(iso);\n },\n [submitting, parsedValue, validate, commitValue],\n );\n\n const handleCommit = useCallback(async () => {\n const next = parseDraft();\n\n // Number type with a NaN draft is rejected at the parse boundary —\n // EXCEPT when the draft was emptied outright (the ✕, or select-all +\n // Delete): that's an intentional clear, not a typo, so it routes\n // through `commitValue('')` like every other type — `required` blocks\n // it with its own message, an optional cell commits '' and shows its\n // placeholder. Half-typed non-numbers (\"-\", \".\") stay input errors.\n // Warning severity: the input is the problem and the user can fix it.\n if (type === 'number' && Number.isNaN(next)) {\n if (draft.trim() === '') {\n if (String(value ?? '') === '') {\n // Already empty — pure exit, no spurious onCommit (mirrors the\n // unchanged-value no-op below).\n setIsEditing(false);\n setError(null);\n return;\n }\n await commitValue('');\n return;\n }\n setError({ message: 'Enter a number', severity: 'warning' });\n return;\n }\n\n // Typed dates parse at the boundary: per the cell's `dateFormat` mask\n // first, canonical ISO as fallback. An emptied draft clears the date\n // (commits \"\"). Unchanged-value / submitting / validation handling all\n // live in the shared `commitDate` so typing and calendar picking behave\n // identically.\n if (type === 'date') {\n const raw = String(next).trim();\n if (raw === '') {\n await commitDate('');\n return;\n }\n const parsed = parseFormattedDate(raw, dateFormat) ?? parseISODate(raw);\n if (!parsed) {\n setError({\n message: invalidDateMessage(dateFormat),\n severity: 'warning',\n });\n return;\n }\n await commitDate(toISODate(parsed));\n return;\n }\n\n // No-op when value didn't change — exit edit mode without firing\n // the consumer's handler (avoids spurious \"save\" calls on blur).\n // NOTE: unlike the date path in `commitDate` (which re-runs `validate`\n // on an unchanged value so a required / row-dependent rule still\n // surfaces on blur), text/number intentionally skip `validate` here.\n // These cells have no cross-row validity story today, so an unchanged\n // blur is treated as a pure exit; keep the two paths in sync if that\n // assumption ever changes.\n const valueStr = Array.isArray(value) ? '' : String(value ?? '');\n if (String(next) === valueStr) {\n setIsEditing(false);\n setError(null);\n return;\n }\n\n await commitValue(next);\n }, [parseDraft, type, draft, dateFormat, value, commitValue, commitDate]);\n\n const handlePick = useCallback(\n async (calValue: CalendarValue) => {\n if (!calValue.start || submitting) return;\n setDraft(formatDateAs(calValue.start, dateFormat));\n // Routes through the shared guard: re-picking the already-selected day is\n // a no-op (no spurious onCommit), and a pick can't fire mid-commit.\n await commitDate(toISODate(calValue.start));\n },\n [dateFormat, submitting, commitDate],\n );\n\n // ✕ inside the editing input (text/number/date, gated by `clearable`).\n // Empties the DRAFT only — focus stays in the input (the button swallows\n // its mousedown so the field never blurs) and nothing commits until\n // Enter/blur, so required/validate still guard and Esc still restores.\n const handleClearDraft = useCallback(() => {\n setDraft('');\n setError(null);\n }, []);\n\n const handleCancel = useCallback(() => {\n if (Array.isArray(value)) {\n setDraft('');\n } else if (type === 'date') {\n setDraft(displayFormatted);\n } else {\n setDraft(String(value ?? ''));\n }\n setIsEditing(false);\n setOpen(false);\n setError(null);\n }, [value, type, displayFormatted]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleCommit();\n } else if (e.key === 'Escape') {\n e.preventDefault();\n handleCancel();\n }\n },\n [handleCommit, handleCancel],\n );\n\n // `forceMode` (preview-only) renders the input branch without a real\n // click. `warning` / `error` also surface a sample message at that\n // severity so the matching knobs paint. Derived in the render body —\n // deliberately NOT fed into the focus effect above, so a forced preview\n // never steals focus from the rest of the page. Production leaves\n // forceMode unset, so `editing` collapses to the internal `isEditing`.\n const editing = forceMode != null || isEditing;\n const shownError: CellError | null =\n forceMode === 'error'\n ? error ?? { message: 'Failed to save', severity: 'error' }\n : forceMode === 'warning'\n ? error ?? { message: 'Enter a valid value', severity: 'warning' }\n : error;\n\n // Hint shown in the date input — the canonical format pattern for the\n // chosen `dateFormat`. Used as the placeholder when the cell is empty\n // and as the input placeholder in edit mode.\n const dateHint = FORMAT_SPEC[dateFormat].placeholder;\n\n // Compute display value\n const isEmpty = Array.isArray(value)\n ? value.length === 0\n : value === '' || value === null || value === undefined;\n\n const display = (() => {\n // Empty wins over `format`: a cleared cell must read as its placeholder,\n // not as the formatter's rendering of \"\" (e.g. a Tag formatter would\n // paint an empty pill). `format` only ever sees real values.\n if (isEmpty) return placeholder ?? (type === 'date' ? dateHint : '');\n if (format) return format(value);\n if (Array.isArray(value)) {\n // For multiselect: show labels from options if available\n if (options) {\n return value\n .map((v) => options.find((o) => o.value === v)?.label ?? v)\n .join(', ');\n }\n return value.join(', ');\n }\n if (type === 'select' && options) {\n return options.find((o) => o.value === String(value))?.label ?? String(value);\n }\n return value;\n })();\n\n // Plain-text form of the value, fed to the HoverTooltip so a value that's\n // clipped at the cell's max-width is still readable on hover. Best-effort\n // for custom `format` cells — those render arbitrary nodes, so the tooltip\n // falls back to the underlying value rather than extracting text from JSX.\n const isMulti = type === 'multiselect';\n const isSelect = type === 'select';\n const isDate = type === 'date';\n const titleText = isEmpty\n ? placeholder ?? ''\n : isMulti\n ? Array.isArray(value)\n ? (options ?? [])\n .filter((o) => (value as string[]).includes(o.value))\n .map((o) => o.label)\n .join(', ')\n : ''\n : isSelect\n ? (options?.find((o) => o.value === String(value))?.label ?? String(value))\n : isDate\n ? displayFormatted || String(value)\n : String(value);\n\n // For select/multiselect in open state, we render a Listbox-driven UI\n if (type === 'select' || type === 'multiselect') {\n const optionItems = options ?? [];\n const getKey = (o: EditableCellOption) => o.value;\n const getLabel = (o: EditableCellOption) => o.label;\n\n if (type === 'select') {\n const selectedItem = Array.isArray(value)\n ? null\n : optionItems.find((o) => o.value === String(value ?? '')) ?? null;\n\n return (\n <span\n ref={wrapRef}\n className={cn(\n 'uxm-editable-cell',\n `uxm-editable-cell--${size}`,\n `uxm-editable-cell--align-${align}`,\n `uxm-editable-cell--type-${type}`,\n clearable && 'uxm-editable-cell--clearable',\n disabled && 'uxm-editable-cell--disabled',\n isEmpty && 'uxm-editable-cell--empty',\n open && 'uxm-editable-cell--open',\n className,\n )}\n style={style}\n >\n <Listbox<EditableCellOption>\n items={optionItems}\n getKey={getKey}\n getLabel={getLabel}\n value={selectedItem}\n onChange={async (next) => {\n if (next) await commitValue(next.value);\n }}\n disabled={disabled}\n searchable={searchable}\n matchAnchorWidth\n onOpenChange={setOpen}\n renderTrigger={({ open: isOpen, triggerProps }: ListboxRenderTriggerState<EditableCellOption>) => (\n <button\n type=\"button\"\n {...triggerProps}\n className={cn(\n 'uxm-editable-cell__select-trigger',\n isOpen && 'uxm-editable-cell__select-trigger--open',\n )}\n aria-label={ariaLabel ?? `Edit ${String(value ?? '')}`}\n >\n <HoverTooltip content={titleText} disabled={disabled}>\n <span className=\"uxm-editable-cell__value uxm-editable-cell__option-label\">\n {display}\n </span>\n </HoverTooltip>\n <span className=\"uxm-editable-cell__chevron\" aria-hidden=\"true\">\n <Icon glyph=\"chevron-down\" size={12} />\n </span>\n </button>\n )}\n renderItem={(o) => (\n <span className=\"uxm-editable-cell__option-label\">{o.label}</span>\n )}\n footer={clearable && selectedItem ? ({ close }: { close: () => void }) => (\n <ButtonGhost\n className=\"uxm-listbox__footer-clear-option\"\n onClick={() => {\n commitValue('');\n close();\n }}\n >\n <Icon glyph=\"close\" size={12} />\n Clear\n </ButtonGhost>\n ) : undefined}\n />\n {clearable && !disabled && !submitting && !isEmpty && (\n /* Trigger ✕ — clear from the field itself (the input-family\n convention), on every size: commits the empty value directly,\n complementing the panel-footer Clear. Sibling of the trigger (a\n button can't nest a button), absolutely positioned into the\n reserved second gutter slot. Revealed only while the panel is\n open (see the `--open` gate in the stylesheet) — unlike the\n chevron, which also shows on hover. */\n <button\n type=\"button\"\n className=\"uxm-editable-cell__trigger-clear\"\n // Out of the tab order, same as every in-field clear here.\n tabIndex={-1}\n // This button is a SIBLING of the Listbox trigger, so it sits\n // outside the refs `useDismiss` treats as \"inside\" — and dismiss\n // runs on mousedown, i.e. BEFORE this click. Left alone, pressing\n // ✕ would first dismiss the panel and only then clear. Stop the\n // mousedown here so the clear below is the single thing that acts.\n onMouseDown={(e) => e.stopPropagation()}\n onClick={() => void commitValue('')}\n aria-label={clearSelectionLabel}\n >\n <Icon glyph=\"close\" size={12} />\n </button>\n )}\n <Popover\n open={Boolean(shownError)}\n onOpenChange={(next) => {\n if (!next) setError(null);\n }}\n anchor={wrapRef}\n placement=\"bottom-start\"\n closeOnEscape={false}\n closeOnOutsideClick={false}\n restoreFocus={false}\n role=\"presentation\"\n >\n {shownError && (\n <Banner\n variant={shownError.severity}\n id=\"uxm-editable-cell__error\"\n className=\"uxm-editable-cell__error-banner\"\n >\n {shownError.message}\n </Banner>\n )}\n </Popover>\n </span>\n );\n }\n\n // multiselect. A cell is a commit boundary, so this branch opts into\n // MultiListbox's staged mode (`commitMode=\"close\"`: draft while open, one\n // commit on close) — a required cell then survives \"clear all → pick one\"\n // and N picks are one `onCommit`. It just hands over the committed\n // selection and commits the final array back.\n const selectedItems = Array.isArray(value)\n ? optionItems.filter((o) => value.includes(o.value))\n : [];\n\n return (\n <span\n ref={wrapRef}\n className={cn(\n 'uxm-editable-cell',\n `uxm-editable-cell--${size}`,\n `uxm-editable-cell--align-${align}`,\n `uxm-editable-cell--type-${type}`,\n clearable && 'uxm-editable-cell--clearable',\n disabled && 'uxm-editable-cell--disabled',\n isEmpty && 'uxm-editable-cell--empty',\n open && 'uxm-editable-cell--open',\n className,\n )}\n style={style}\n >\n <MultiListbox<EditableCellOption>\n items={optionItems}\n getKey={getKey}\n getLabel={getLabel}\n value={selectedItems}\n onChange={(items) => void commitValue(items.map((o) => o.value))}\n // Commit on close, not per toggle — see the note above.\n commitMode=\"close\"\n required={required}\n requiredMessage={requiredMessage}\n // MultiListbox owns the empty rule for multi (shared with every\n // picker); route its violation into this cell's error Banner.\n onRequiredViolation={(message) => setError({ message, severity: 'warning' })}\n disabled={disabled}\n searchable={searchable}\n matchAnchorWidth\n // Clear a stale required warning when the panel reopens for a fresh edit.\n onOpenChange={(next) => {\n setOpen(next);\n if (next) setError(null);\n }}\n renderTrigger={({ open: isOpen, triggerProps }: ListboxRenderTriggerState<EditableCellOption>) => (\n <button\n type=\"button\"\n {...triggerProps}\n className={cn(\n 'uxm-editable-cell__select-trigger',\n isOpen && 'uxm-editable-cell__select-trigger--open',\n )}\n aria-label={ariaLabel ?? `Edit ${String(value ?? '')}`}\n >\n <HoverTooltip content={titleText} disabled={disabled}>\n <span className=\"uxm-editable-cell__value uxm-editable-cell__option-label\">\n {display}\n </span>\n </HoverTooltip>\n <span className=\"uxm-editable-cell__chevron\" aria-hidden=\"true\">\n <Icon glyph=\"chevron-down\" size={12} />\n </span>\n </button>\n )}\n renderItem={(o) => (\n <span className=\"uxm-editable-cell__option-label\">{o.label}</span>\n )}\n footer={clearable ? ({ clear, selected }: { clear: () => void; selected: EditableCellOption[] }) =>\n selected.length > 0 ? (\n <ButtonGhost\n className=\"uxm-listbox__footer-clear-option\"\n // `clear` (from MultiListbox) empties the draft and keeps the\n // panel open, so \"clear all then pick one\" works even on a\n // required cell — the empty state is transient and never\n // reaches `validate` / `onCommit`. `selected` is the live\n // draft, so the button hides itself once nothing is picked.\n onClick={clear}\n >\n <Icon glyph=\"close\" size={12} />\n Clear all\n </ButtonGhost>\n ) : null\n : undefined}\n />\n {clearable && !disabled && !submitting && !isEmpty && (\n /* Trigger ✕ — clear from the field itself (the input-family\n convention), on every size: commits the empty value directly,\n complementing the panel-footer Clear. Sibling of the trigger (a\n button can't nest a button), absolutely positioned into the\n reserved second gutter slot. Revealed only while the panel is\n open (see the `--open` gate in the stylesheet) — unlike the\n chevron, which also shows on hover. */\n <button\n type=\"button\"\n className=\"uxm-editable-cell__trigger-clear\"\n // Out of the tab order, same as every in-field clear here.\n tabIndex={-1}\n // Same sibling-of-the-trigger problem as the select ✕ above, but\n // costlier here: `useDismiss` fires on mousedown, and this panel\n // runs `commitMode=\"close\"`, so an unswallowed mousedown would\n // commit the STAGED DRAFT first and only then clear — two commits\n // for one press, and if the first is still in flight the\n // re-entrancy guard in `commitValue` drops the clear entirely.\n // Stopping it leaves the panel open, which is deliberate and\n // matches the footer's \"Clear all\": clear, then pick afresh.\n // MultiListbox re-seeds its draft from `value` under an open panel,\n // so the later close commits the cleared set, not the stale draft.\n onMouseDown={(e) => e.stopPropagation()}\n onClick={() => void commitValue([])}\n aria-label={clearSelectionLabel}\n >\n <Icon glyph=\"close\" size={12} />\n </button>\n )}\n <Popover\n open={Boolean(shownError)}\n onOpenChange={(next) => {\n if (!next) setError(null);\n }}\n anchor={wrapRef}\n placement=\"bottom-start\"\n closeOnEscape={false}\n closeOnOutsideClick={false}\n restoreFocus={false}\n role=\"presentation\"\n >\n {shownError && (\n <Banner\n variant={shownError.severity}\n id=\"uxm-editable-cell__error\"\n className=\"uxm-editable-cell__error-banner\"\n >\n {shownError.message}\n </Banner>\n )}\n </Popover>\n </span>\n );\n }\n\n // Date type — click-to-edit like text/number, but the editing surface is a\n // DateInput-style masked field: type the date OR pick it from the calendar\n // that opens on focus. Committed values are ISO; the input shows the mask.\n if (type === 'date') {\n // `parsedValue` (memoized above) already parses ISO-first with a mask\n // fallback, so the calendar highlights the right day for every `dateFormat`.\n const calendarValue: CalendarValue = { start: parsedValue, end: parsedValue };\n const spec = FORMAT_SPEC[dateFormat];\n\n if (!editing) {\n const displayNode = isEmpty\n // Fall back to the format hint (MM/DD/YYYY etc.) so an empty date cell\n // shows the mask as a placeholder — same as the shared `display` and\n // the edit-mode input, rather than rendering blank.\n ? placeholder ?? dateHint\n : format\n ? format(value)\n : displayFormatted || value;\n\n // Single button root — same shape as the text/number display so the\n // value's left edge and padding match the editing input exactly (no\n // horizontal jump on enter-edit). A wrapper + inner trigger would\n // double the padding; the display has no popover to anchor, so the\n // wrapper span is only needed in edit mode (for the calendar/error\n // popovers).\n return (\n <button\n type=\"button\"\n className={cn(\n 'uxm-editable-cell',\n `uxm-editable-cell--${size}`,\n `uxm-editable-cell--align-${align}`,\n `uxm-editable-cell--type-date`,\n disabled && 'uxm-editable-cell--disabled',\n isEmpty && 'uxm-editable-cell--empty',\n className,\n )}\n style={style}\n onClick={() => {\n if (disabled) return;\n // Enter edit AND open the calendar in one deterministic step —\n // mirrors DateInput's \"focus reveals the calendar\". Opening here\n // (rather than on the input's onFocus) avoids the focus-after-\n // remount race: the display button and editing input are\n // different elements, so the auto-focus that mounts the input\n // doesn't reliably deliver a focus event the calendar can hook.\n setIsEditing(true);\n setOpen(true);\n }}\n disabled={disabled}\n // Empty cell has no value to edit — \"Add date\" reads better than the\n // trailing-space \"Edit date \" and signals the fill affordance.\n aria-label={ariaLabel ?? (isEmpty ? addDateLabel : `Edit date ${displayFormatted || String(value)}`)}\n >\n <HoverTooltip content={titleText} disabled={disabled}>\n <span className=\"uxm-editable-cell__value\">{displayNode}</span>\n </HoverTooltip>\n {!disabled && (\n <span className=\"uxm-editable-cell__pencil\" aria-hidden=\"true\">\n <Icon glyph=\"calendar\" size={14} />\n </span>\n )}\n </button>\n );\n }\n\n // Date editing — DateInput-style masked input plus a calendar popover.\n return (\n <span\n ref={wrapRef}\n className={cn(\n 'uxm-editable-cell uxm-editable-cell--editing',\n `uxm-editable-cell--${size}`,\n `uxm-editable-cell--align-${align}`,\n `uxm-editable-cell--type-date`,\n clearable && 'uxm-editable-cell--clearable',\n shownError?.severity === 'error' && 'uxm-editable-cell--error',\n shownError?.severity === 'warning' && 'uxm-editable-cell--warning',\n className,\n )}\n style={style}\n >\n <span className=\"uxm-editable-cell__ghost\" aria-hidden=\"true\">\n {display}\n </span>\n <span className=\"uxm-editable-cell__ghost\" aria-hidden=\"true\">\n {draft || spec.placeholder}\n </span>\n <input\n ref={inputRef}\n type=\"text\"\n inputMode=\"numeric\"\n className=\"uxm-editable-cell__input\"\n value={draft}\n placeholder={spec.placeholder}\n onChange={(e) => {\n setDraft(maskDate(e.target.value, dateFormat));\n if (error) setError(null);\n }}\n onBlur={() => {\n if (!submitting) handleCommit();\n }}\n onKeyDown={handleKeyDown}\n disabled={submitting}\n aria-invalid={Boolean(shownError)}\n aria-describedby={shownError ? 'uxm-editable-cell__error' : undefined}\n />\n {/* ✕ sits inboard of the calendar toggle (mirrors DateInput's layout);\n hidden while the draft is empty — the reserved gutter stays, so\n typing never shifts the text. */}\n {clearable && !submitting && draft !== '' && (\n <button\n type=\"button\"\n className=\"uxm-editable-cell__clear\"\n // Out of the tab order (the in-field-clear convention). Tab must\n // keep moving to the next real control: the blur it fires commits\n // and leaves edit mode, which unmounts this button mid-focus-shift\n // and would drop focus to <body>. Pointer-only; select-all +\n // Delete is the keyboard path to the same result.\n tabIndex={-1}\n onMouseDown={(e) => e.preventDefault()}\n onClick={handleClearDraft}\n aria-label={clearDateLabel}\n >\n <Icon glyph=\"close\" size={12} />\n </button>\n )}\n <button\n type=\"button\"\n className=\"uxm-editable-cell__date-icon\"\n // Out of the tab order for the same reason as the ✕ above — Tab's\n // blur commits and unmounts this button. Keyboard users never need\n // it: entering edit mode on a date cell already opens the calendar.\n tabIndex={-1}\n // preventDefault keeps focus on the input — toggling the calendar\n // must not blur the field (a blur commits the in-flight draft).\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n if (submitting) return;\n // Clear any shown error as we open, so the calendar and the error\n // banner (both anchored here) are mutually exclusive — otherwise a\n // later `setError(null)` from typing would un-hide and reopen the\n // calendar the user had dismissed.\n setError(null);\n setOpen((o) => !o);\n }}\n disabled={submitting}\n aria-label={openCalendarLabel}\n // Match the popover's effective visibility (hidden while an error\n // shows) rather than raw `open`, so the two never disagree.\n aria-expanded={open && !shownError}\n >\n <Icon glyph=\"calendar\" size={14} />\n </button>\n <Popover\n open={open && !shownError}\n onOpenChange={setOpen}\n anchor={wrapRef}\n placement=\"bottom-start\"\n closeOnEscape\n closeOnOutsideClick\n restoreFocus={false}\n role=\"dialog\"\n // A `role=\"dialog\"` needs an accessible name. Non-modal by design\n // (no `aria-modal`): focus deliberately stays in the typed input\n // — `restoreFocus={false}` and the mousedown-preventDefault above\n // keep the field focused so the calendar augments typing rather\n // than trapping focus like a true modal.\n aria-label={calendarDialogLabel}\n >\n {/* Swallow mousedown so a calendar click never blurs the input:\n the pick commits explicitly via handlePick, and a blur-commit of\n the typed draft would race it. Purely focus management — the\n wrapper carries no semantics, so it stays a plain element. */}\n {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- focus-retention only; the interactive controls are the Calendar's own buttons */}\n <div onMouseDown={(e) => e.preventDefault()}>\n <Calendar value={calendarValue} onChange={handlePick} />\n </div>\n </Popover>\n <Popover\n open={Boolean(shownError)}\n onOpenChange={(next) => {\n if (!next) setError(null);\n }}\n anchor={wrapRef}\n placement=\"bottom-start\"\n closeOnEscape={false}\n closeOnOutsideClick={false}\n restoreFocus={false}\n role=\"presentation\"\n >\n {shownError && (\n <Banner\n variant={shownError.severity}\n id=\"uxm-editable-cell__error\"\n className=\"uxm-editable-cell__error-banner\"\n >\n {shownError.message}\n </Banner>\n )}\n </Popover>\n </span>\n );\n }\n\n // Text / number types\n if (!editing) {\n return (\n <button\n type=\"button\"\n className={cn(\n 'uxm-editable-cell',\n `uxm-editable-cell--${size}`,\n `uxm-editable-cell--align-${align}`,\n disabled && 'uxm-editable-cell--disabled',\n isEmpty && 'uxm-editable-cell--empty',\n className,\n )}\n style={style}\n onClick={() => {\n if (!disabled) setIsEditing(true);\n }}\n disabled={disabled}\n aria-label={ariaLabel ?? `Edit ${String(value ?? '')}`}\n >\n {/* The value needs its own element for ellipsis — text-overflow\n doesn't apply to a flex container's anonymous text. Values wider\n than the cell's max-width truncate instead of growing the column\n unboundedly; the hidden text surfaces in a hover tooltip (only\n when actually clipped) and stays editable by entering edit mode. */}\n <HoverTooltip content={titleText} disabled={disabled}>\n <span className=\"uxm-editable-cell__value\">{display}</span>\n </HoverTooltip>\n {/* Hover affordance — a pencil glyph fades in on hover / keyboard\n focus. The background tint alone can't signal editability inside\n a DataTable, where the row's own hover tint coincides with it.\n Absolute overlay in a reserved gutter (see editable-cell.scss) so\n it never shifts or collides with the value. Hidden when disabled. */}\n {!disabled && (\n <span className=\"uxm-editable-cell__pencil\" aria-hidden=\"true\">\n <Icon glyph=\"pencil\" size={16} strokeWidth={1.75} />\n </span>\n )}\n </button>\n );\n }\n\n return (\n <span\n ref={wrapRef}\n className={cn(\n 'uxm-editable-cell uxm-editable-cell--editing',\n `uxm-editable-cell--${size}`,\n `uxm-editable-cell--align-${align}`,\n clearable && 'uxm-editable-cell--clearable',\n shownError?.severity === 'error' && 'uxm-editable-cell--error',\n shownError?.severity === 'warning' && 'uxm-editable-cell--warning',\n className,\n )}\n style={style}\n >\n {/* Invisible width-keepers, stacked in the same grid cell as the\n input. The display content ghost preserves the exact min-content\n width this cell contributed before entering edit mode — without\n it, editing the column's WIDEST row would drop its contribution\n from auto table-column sizing and the column would visibly\n shrink. The draft ghost grows the cell naturally as the user\n types past the original width. */}\n <span className=\"uxm-editable-cell__ghost\" aria-hidden=\"true\">\n {display}\n </span>\n <span className=\"uxm-editable-cell__ghost\" aria-hidden=\"true\">\n {draft}\n </span>\n <input\n ref={inputRef}\n // `type=\"text\"` + `inputMode` — the input-family convention (see\n // NumberInput): native number inputs ship browser spinner UI, trigger\n // autofill, and disagree across browsers on accepted characters.\n // `maskNumeric` replaces the native filtering per keystroke (same as\n // the date editor's `maskDate`); without it, `Number()`'s loose\n // coercion would let e.g. \"0x1A\" commit as 26.\n type=\"text\"\n inputMode={type === 'number' ? 'decimal' : undefined}\n autoComplete=\"off\"\n className=\"uxm-editable-cell__input\"\n value={draft}\n onChange={(e) => {\n setDraft(\n type === 'number'\n ? maskNumeric(e.target.value, true, true, Number.POSITIVE_INFINITY)\n : e.target.value,\n );\n // Clear stale validation error as the user types — they'll\n // see a new one on the next commit attempt if it still fails.\n if (error) setError(null);\n }}\n onBlur={() => {\n // Don't double-commit while a previous commit is still pending.\n if (!submitting) handleCommit();\n }}\n onKeyDown={handleKeyDown}\n disabled={submitting}\n placeholder={placeholder}\n aria-invalid={Boolean(shownError)}\n aria-describedby={shownError ? 'uxm-editable-cell__error' : undefined}\n />\n {/* One-click ✕ (mirrors TextInput/NumberInput): empties the draft and\n keeps focus; hidden while the draft is already empty. The gutter is\n reserved via `--clearable` so its appearance never shifts text. */}\n {clearable && !submitting && draft !== '' && (\n <button\n type=\"button\"\n className=\"uxm-editable-cell__clear\"\n // Out of the tab order — see the date branch's note: the blur Tab\n // fires commits and unmounts this button, so leaving it focusable\n // would strand focus on a removed node instead of advancing to the\n // next cell.\n tabIndex={-1}\n onMouseDown={(e) => e.preventDefault()}\n onClick={handleClearDraft}\n aria-label={clearValueLabel}\n >\n <Icon glyph=\"close\" size={12} />\n </button>\n )}\n {/* Problems hang under the cell in a portal'd popover so the table\n row never changes height. The popover must not manage focus —\n the input keeps it for in-place correction / retry. Escape and\n outside clicks already have edit-mode semantics (cancel / blur-\n commit), so the popover's own close triggers are disabled; it\n opens and closes purely with the error state. */}\n <Popover\n open={Boolean(shownError)}\n onOpenChange={(next) => {\n if (!next) setError(null);\n }}\n anchor={wrapRef}\n placement=\"bottom-start\"\n closeOnEscape={false}\n closeOnOutsideClick={false}\n restoreFocus={false}\n role=\"presentation\"\n >\n {shownError && (\n <Banner\n variant={shownError.severity}\n id=\"uxm-editable-cell__error\"\n className=\"uxm-editable-cell__error-banner\"\n >\n {shownError.message}\n </Banner>\n )}\n </Popover>\n </span>\n );\n}\n"],"mappings":"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","names":["formatDateAs","parseFormattedDate"]}
|
|
1
|
+
{"version":3,"sources":["../../../src/ui/editable-cell/editable-cell.tsx"],"sourcesContent":["import {\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n type CSSProperties,\n type ReactNode,\n} from 'react';\n\nimport { cn } from '@/helpers';\n\nimport { Banner } from '../banner';\nimport { ButtonGhost } from '../button';\nimport { Calendar, type CalendarValue } from '../calendar';\nimport {\n FORMAT_SPEC,\n formatDate as formatDateAs,\n invalidDateMessage,\n maskDate,\n parseDate as parseFormattedDate,\n type DateInputFormat,\n} from '../date-input';\nimport { HoverTooltip } from '../hover-tooltip';\nimport { Icon } from '../icon';\nimport { Listbox, MultiListbox, type ListboxRenderTriggerState } from '../listbox';\nimport { maskNumeric } from '../number-input';\nimport { Popover } from '../popover';\n\nexport type EditableCellType = 'text' | 'number' | 'date' | 'select' | 'multiselect';\nexport type EditableCellAlign = 'left' | 'right' | 'center';\nexport type EditableCellSize = 'small' | 'medium';\nexport type EditableCellValue = string | number | string[];\n\n/** Strict `YYYY-MM-DD` → local Date (no TZ surprises), null if not parseable. */\nfunction parseISODate(s: string): Date | null {\n const m = /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(s);\n if (!m) return null;\n const y = Number(m[1]);\n const mo = Number(m[2]);\n const day = Number(m[3]);\n const d = new Date(y, mo - 1, day);\n // Reject regex-valid but out-of-range dates: `new Date(2024, 1, 30)` silently\n // rolls over to Mar 1 rather than producing NaN, which would later read as a\n // value change and trigger an unprompted rewrite. Require a clean round-trip.\n if (d.getFullYear() !== y || d.getMonth() !== mo - 1 || d.getDate() !== day) return null;\n return d;\n}\n\n/** Serialize a Date to ISO (`YYYY-MM-DD`) — reuses the shared `ymd` formatter. */\nconst toISODate = (d: Date): string => formatDateAs(d, 'ymd');\n\n/**\n * Committed dates are stored ISO (`YYYY-MM-DD`); the input and display show\n * the cell's `dateFormat` mask. Convert ISO → mask so `dmy`/`mdy` cells read\n * and edit in their own shape. Values that aren't ISO (e.g. a consumer's\n * already-formatted seed) pass through untouched.\n */\nfunction isoToFormatted(value: EditableCellValue, format: DateInputFormat): string {\n if (typeof value !== 'string' || !value) return '';\n const d = parseISODate(value);\n return d ? formatDateAs(d, format) : value;\n}\n\n/**\n * Emptiness rule for the `required` check — the single definition shared by\n * every editor type: an empty array (multiselect), a NaN number (blank numeric\n * draft), or a blank/whitespace string (text/select/date).\n */\nfunction isEmptyValue(v: EditableCellValue): boolean {\n if (Array.isArray(v)) return v.length === 0;\n if (typeof v === 'number') return Number.isNaN(v);\n return String(v ?? '').trim() === '';\n}\n\nexport interface EditableCellOption {\n value: string;\n label: string;\n}\n\n/**\n * Two severity levels, mapped to Banner variants:\n * - `warning` — recoverable input problems the user can fix in place\n * (sync `validate` failures, unparseable number drafts).\n * - `error` — the commit itself failed (`onCommit` rejected): the value\n * was fine but saving didn't happen.\n */\ninterface CellError {\n message: string;\n severity: 'warning' | 'error';\n}\n\nexport interface EditableCellProps {\n /** Current committed value. The atom keeps a draft internally while editing. */\n value: EditableCellValue;\n /**\n * Called when the user commits the change (Enter, or blur with no\n * validation error). The promise's settlement controls error display:\n * resolve → exit edit mode, reject → stay in edit mode with the error\n * surfaced inline so the user can correct.\n */\n onCommit: (next: EditableCellValue) => void | Promise<void>;\n /** Editor type. Text uses a string input; number coerces to Number on commit (an emptied draft commits `\"\"` — the uniform \"cleared\" value). Defaults to \"text\". */\n type?: EditableCellType;\n /** Date format — only used when `type=\"date\"`. Defaults to `\"mdy\"`. */\n dateFormat?: DateInputFormat;\n /** Options — required for `type=\"select\"` and `type=\"multiselect\"`. */\n options?: EditableCellOption[];\n /**\n * Whether the select panel includes a search box. Defaults to `\"auto\"` —\n * the box appears only once the option count passes the shared Listbox\n * threshold (6) — so a select/multiselect cell matches the `Select` atom's\n * behavior instead of inheriting the raw Listbox `true` default.\n */\n searchable?: boolean | 'auto';\n /**\n * Clear affordance — the surface differs per editor type:\n * - `select` / `multiselect` — a \"Clear\" / \"Clear all\" action in the\n * dropdown footer (commits \"\" / empties the draft).\n * - `text` / `number` / `date` — a ✕ inside the EDITING input (mirrors\n * TextInput/NumberInput/DateInput) that empties the draft and keeps\n * focus; nothing commits until Enter/blur, so `required`/`validate`\n * still guard, and Esc still restores the old value. Available on\n * required cells too — \"wipe it and type the right value\" is the\n * point. Display mode never shows a ✕ — the pencil owns that gutter,\n * and a one-click destroy on a static table cell invites accidents.\n *\n * `required` never hides the affordance — it guards the OUTCOME instead:\n * clearing a required cell surfaces the required warning (the value stays),\n * clearing an optional one empties it back to the placeholder.\n *\n * Defaults to `true` — the input-family default (TextInput, NumberInput,\n * DateInput …), so consumers like DataTable get the affordance uniformly\n * without per-column wiring. Pass `false` to opt out.\n */\n clearable?: boolean;\n /** Text alignment — pass through from a DataTable column's `align` so the editing input matches the display alignment. */\n align?: EditableCellAlign;\n /**\n * Size preset. `small` (default) is the dense table scale — the cell reads\n * like a static text cell inside a DataTable row (its font falls back to\n * the inherited size). `medium` steps the cell up to the input-family\n * scale (12/6 padding, 14px font) for standalone use in side panels /\n * detail views, where a table-dense cell looks undersized next to real\n * inputs. Each size owns a symmetric\n * `--uxm-editable-cell-{size}-{padding-x,padding-y,font-size}` knob set;\n * height always derives from font-size + padding (a `1lh` floor keeps\n * empty cells clickable).\n */\n size?: EditableCellSize;\n /**\n * Display-mode formatter. Receives the raw value, returns the React node\n * to render in display mode. Never called for an empty value — a cleared\n * cell renders its `placeholder` instead (so a Tag/Badge formatter can't\n * paint an empty pill).\n */\n format?: (value: EditableCellValue) => ReactNode;\n /** Synchronous validation. Return an error message to block commit; return null/undefined to accept. */\n validate?: (next: EditableCellValue) => string | null | undefined;\n /**\n * Mark the cell required — an empty value blocks commit and surfaces a\n * warning, checked BEFORE `validate` so you don't hand-write the empty rule.\n * Uniform across every editor type: `multiselect` → non-empty array (the\n * rule lives in MultiListbox, shared with every other picker), `select` →\n * a chosen option, `text`/`number`/`date` → a non-blank value.\n */\n required?: boolean;\n /** Override the default required message (per-type: \"Select at least one option\" / \"Select an option\" / \"Required\"). */\n requiredMessage?: string;\n /**\n * Shown at `error` severity when `onCommit` rejects without a message of\n * its own. Default `\"Failed to save\"`. A rejection that *does* carry an\n * `Error.message` still wins — that text is the server's, not the atom's.\n */\n saveErrorMessage?: string;\n /**\n * Shown at `warning` severity when a `type=\"number\"` draft isn't a number.\n * Default `\"Enter a number\"`.\n */\n invalidNumberMessage?: string;\n /**\n * Shown at `warning` severity when a `type=\"date\"` draft doesn't parse.\n * Defaults to `invalidDateMessage(dateFormat)` — `` `Enter a valid date\n * (YYYY-MM-DD)` `` — which names the expected mask, so a translation\n * should keep the mask in it.\n */\n invalidDateMessage?: string;\n /**\n * Sample warning text for `forceMode=\"warning\"`. Preview-only — production\n * consumers never see it. Default `\"Enter a valid value\"`.\n */\n invalidValueMessage?: string;\n /** Read-only — clicking does nothing, no edit affordance. */\n disabled?: boolean;\n /** Shown when value is empty/blank. */\n placeholder?: string;\n className?: string;\n style?: CSSProperties;\n 'aria-label'?: string;\n /**\n * Accessible names for the editors' icon-only controls. Which ones apply\n * depends on the editor type. The composed \"Edit {value}\" trigger names\n * are not here — pass `aria-label` to replace those wholesale.\n */\n /** Clear button, select + multiselect editors. Default `\"Clear selection\"`. */\n clearSelectionLabel?: string;\n /** Clear button, date editor. Default `\"Clear date\"`. */\n clearDateLabel?: string;\n /** Clear button, text / number editors. Default `\"Clear value\"`. */\n clearValueLabel?: string;\n /** Calendar trigger, date editor. Default `\"Open calendar\"`. */\n openCalendarLabel?: string;\n /** Calendar popover dialog, date editor. Default `\"Choose date\"`. */\n calendarDialogLabel?: string;\n /** Trigger name when the date editor is empty. Default `\"Add date\"`. */\n addDateLabel?: string;\n /**\n * Preview-only branch override. UXM's canvas passes this to render the\n * input branch without a real click — `editing` shows the input, `warning`\n * / `error` also surface a sample message at that severity so the matching\n * knobs paint. It does NOT focus the input (the canvas must never steal\n * focus from the editor panel); the preview mirrors the focus visuals via\n * a forced CSS class instead. Production consumers leave it unset.\n */\n forceMode?: 'editing' | 'warning' | 'error';\n}\n\n/**\n * Inline-editable cell. Three modes:\n *\n * - **Display** (default) — renders the formatted value as a button-styled\n * text run. Click (or Enter/Space when focused) to enter edit mode.\n * - **Editing** — replaces the display with a focused, select-all'd input.\n * Enter commits, Esc cancels, blur commits.\n * - **Submitting** — input disabled while an async `onCommit` is pending.\n *\n * Problems surface in a Popover anchored under the cell (never inline — a\n * table row must not change height) carrying a compact Banner: `warning`\n * (yellow) for sync validate failures the user can fix in place, `error`\n * (red) for a rejected `onCommit`. Either way the user stays in edit mode\n * to correct or retry.\n *\n * Designed primarily for DataTable cells, but works anywhere an inline\n * commit semantic is needed (PageHeader rename, StatCard label, etc.).\n */\nexport function EditableCell({\n value,\n onCommit,\n type = 'text',\n dateFormat = 'ymd',\n options,\n searchable = 'auto',\n clearable = true,\n align = 'left',\n size = 'small',\n format,\n validate,\n required = false,\n requiredMessage,\n saveErrorMessage = 'Failed to save',\n invalidNumberMessage = 'Enter a number',\n invalidDateMessage: invalidDateMessageProp,\n invalidValueMessage = 'Enter a valid value',\n disabled,\n placeholder,\n className,\n style,\n 'aria-label': ariaLabel,\n clearSelectionLabel = 'Clear selection',\n clearDateLabel = 'Clear date',\n clearValueLabel = 'Clear value',\n openCalendarLabel = 'Open calendar',\n calendarDialogLabel = 'Choose date',\n addDateLabel = 'Add date',\n forceMode,\n}: EditableCellProps) {\n const [isEditing, setIsEditing] = useState(false);\n const [draft, setDraft] = useState<string>(() => {\n if (type === 'date') return isoToFormatted(value, dateFormat);\n if (Array.isArray(value)) return '';\n return String(value ?? '');\n });\n const [submitting, setSubmitting] = useState(false);\n const [error, setError] = useState<CellError | null>(null);\n const [open, setOpen] = useState(false);\n const inputRef = useRef<HTMLInputElement | null>(null);\n // Anchor for the error popover — the editing wrapper, so the banner hangs\n // under the cell without affecting the table row's layout.\n const wrapRef = useRef<HTMLSpanElement | null>(null);\n\n // Committed date as a Date and in the cell's mask, memoized so the display,\n // tooltip, aria-label, calendar highlight, draft sync, and cancel-reset all\n // share one parse/format instead of each re-running it. Cheap `null`/`''`\n // no-ops for non-date types. Declared before the effects so they can reuse it.\n const parsedValue = useMemo(\n () =>\n type === 'date' && typeof value === 'string' && value\n ? parseISODate(value) ?? parseFormattedDate(value, dateFormat)\n : null,\n [type, value, dateFormat],\n );\n const displayFormatted = useMemo(\n () => (type === 'date' ? isoToFormatted(value, dateFormat) : ''),\n [type, value, dateFormat],\n );\n\n // When the externally-committed value changes (parent state updated, or\n // someone else edited the row), sync the draft — but only when we're\n // NOT mid-edit. Mid-edit syncs would clobber what the user is typing.\n useEffect(() => {\n if (!isEditing) {\n const next = Array.isArray(value)\n ? ''\n : type === 'date'\n ? displayFormatted\n : String(value ?? '');\n // eslint-disable-next-line react-hooks/set-state-in-effect -- intentional external-value → draft sync, gated to non-editing mode\n setDraft(next);\n }\n }, [value, isEditing, type, displayFormatted]);\n\n // Focus + select-all when entering edit mode. Select-all means typing\n // immediately replaces the value — the most common edit intent — while\n // keyboard navigation (Home/End/arrows) still works to refine.\n useEffect(() => {\n if (isEditing && inputRef.current) {\n inputRef.current.focus();\n inputRef.current.select();\n }\n }, [isEditing]);\n\n // A surfaced problem and the date calendar can't share the anchor, so the\n // calendar is hidden while an error shows. Close it outright (not just via\n // the render gate) so clearing the error later — e.g. the next keystroke's\n // `setError(null)` — doesn't silently reopen a calendar the user dismissed.\n useEffect(() => {\n if (error && type === 'date') {\n // eslint-disable-next-line react-hooks/set-state-in-effect -- close the calendar exactly when a date error appears, so it can't auto-reopen on clear\n setOpen(false);\n }\n }, [error, type]);\n\n const parseDraft = useCallback((): EditableCellValue => {\n // `Number('')` is 0, not NaN — map an empty/whitespace draft to NaN so\n // blur-with-empty hits the \"Enter a number\" boundary instead of silently\n // committing 0.\n if (type === 'number') return draft.trim() === '' ? Number.NaN : Number(draft);\n if (type === 'date') return draft;\n return draft;\n }, [type, draft]);\n\n // Per-type default required message; overridable via `requiredMessage`.\n // (multiselect's own default lives in MultiListbox, which owns that rule.)\n const requiredMsg =\n requiredMessage ?? (type === 'select' ? 'Select an option' : 'Required');\n\n const commitValue = useCallback(\n async (next: EditableCellValue): Promise<boolean> => {\n // Re-entrancy guard lives HERE, not only in each caller's render\n // condition: every commit path funnels through this function, so a new\n // call site can't forget to gate itself (`commitDate` keeps its own\n // early return for the same reason).\n if (submitting) return false;\n // Required is checked before `validate` so consumers get the empty rule\n // for free. Multiselect empties are already blocked upstream by\n // MultiListbox (they never reach here), so this covers the scalar types.\n if (required && isEmptyValue(next)) {\n setError({ message: requiredMsg, severity: 'warning' });\n return false;\n }\n const validationError = validate?.(next);\n if (validationError) {\n setError({ message: validationError, severity: 'warning' });\n return false;\n }\n\n setError(null);\n setSubmitting(true);\n try {\n await onCommit(next);\n setIsEditing(false);\n setOpen(false);\n return true;\n } catch (err) {\n // Error severity: the value was acceptable but saving failed.\n setError({\n message: err instanceof Error ? err.message : saveErrorMessage,\n severity: 'error',\n });\n // Refocus the input so the user can retry without re-entering edit mode.\n requestAnimationFrame(() => inputRef.current?.focus());\n return false;\n } finally {\n setSubmitting(false);\n }\n },\n [submitting, required, requiredMsg, saveErrorMessage, validate, onCommit],\n );\n\n // Shared commit path for both the typed input (handleCommit) and the calendar\n // (handlePick). Guards a re-entrant commit while one is in flight, and skips\n // the consumer's onCommit when nothing changed — comparing normalized ISO on\n // BOTH sides so a formatted seed (or a re-picked day) isn't mistaken for a\n // change — while still running `validate` on the no-op so a required /\n // row-dependent rule still surfaces.\n const commitDate = useCallback(\n async (iso: string) => {\n if (submitting) return;\n const currentIso = parsedValue ? toISODate(parsedValue) : '';\n if (iso === currentIso) {\n const validationError = validate?.(iso);\n if (validationError) {\n setError({ message: validationError, severity: 'warning' });\n return;\n }\n setIsEditing(false);\n setOpen(false);\n setError(null);\n return;\n }\n await commitValue(iso);\n },\n [submitting, parsedValue, validate, commitValue],\n );\n\n const handleCommit = useCallback(async () => {\n const next = parseDraft();\n\n // Number type with a NaN draft is rejected at the parse boundary —\n // EXCEPT when the draft was emptied outright (the ✕, or select-all +\n // Delete): that's an intentional clear, not a typo, so it routes\n // through `commitValue('')` like every other type — `required` blocks\n // it with its own message, an optional cell commits '' and shows its\n // placeholder. Half-typed non-numbers (\"-\", \".\") stay input errors.\n // Warning severity: the input is the problem and the user can fix it.\n if (type === 'number' && Number.isNaN(next)) {\n if (draft.trim() === '') {\n if (String(value ?? '') === '') {\n // Already empty — pure exit, no spurious onCommit (mirrors the\n // unchanged-value no-op below).\n setIsEditing(false);\n setError(null);\n return;\n }\n await commitValue('');\n return;\n }\n setError({ message: invalidNumberMessage, severity: 'warning' });\n return;\n }\n\n // Typed dates parse at the boundary: per the cell's `dateFormat` mask\n // first, canonical ISO as fallback. An emptied draft clears the date\n // (commits \"\"). Unchanged-value / submitting / validation handling all\n // live in the shared `commitDate` so typing and calendar picking behave\n // identically.\n if (type === 'date') {\n const raw = String(next).trim();\n if (raw === '') {\n await commitDate('');\n return;\n }\n const parsed = parseFormattedDate(raw, dateFormat) ?? parseISODate(raw);\n if (!parsed) {\n setError({\n message: invalidDateMessageProp ?? invalidDateMessage(dateFormat),\n severity: 'warning',\n });\n return;\n }\n await commitDate(toISODate(parsed));\n return;\n }\n\n // No-op when value didn't change — exit edit mode without firing\n // the consumer's handler (avoids spurious \"save\" calls on blur).\n // NOTE: unlike the date path in `commitDate` (which re-runs `validate`\n // on an unchanged value so a required / row-dependent rule still\n // surfaces on blur), text/number intentionally skip `validate` here.\n // These cells have no cross-row validity story today, so an unchanged\n // blur is treated as a pure exit; keep the two paths in sync if that\n // assumption ever changes.\n const valueStr = Array.isArray(value) ? '' : String(value ?? '');\n if (String(next) === valueStr) {\n setIsEditing(false);\n setError(null);\n return;\n }\n\n await commitValue(next);\n }, [\n parseDraft,\n type,\n draft,\n dateFormat,\n value,\n commitValue,\n commitDate,\n invalidNumberMessage,\n invalidDateMessageProp,\n ]);\n\n const handlePick = useCallback(\n async (calValue: CalendarValue) => {\n if (!calValue.start || submitting) return;\n setDraft(formatDateAs(calValue.start, dateFormat));\n // Routes through the shared guard: re-picking the already-selected day is\n // a no-op (no spurious onCommit), and a pick can't fire mid-commit.\n await commitDate(toISODate(calValue.start));\n },\n [dateFormat, submitting, commitDate],\n );\n\n // ✕ inside the editing input (text/number/date, gated by `clearable`).\n // Empties the DRAFT only — focus stays in the input (the button swallows\n // its mousedown so the field never blurs) and nothing commits until\n // Enter/blur, so required/validate still guard and Esc still restores.\n const handleClearDraft = useCallback(() => {\n setDraft('');\n setError(null);\n }, []);\n\n const handleCancel = useCallback(() => {\n if (Array.isArray(value)) {\n setDraft('');\n } else if (type === 'date') {\n setDraft(displayFormatted);\n } else {\n setDraft(String(value ?? ''));\n }\n setIsEditing(false);\n setOpen(false);\n setError(null);\n }, [value, type, displayFormatted]);\n\n const handleKeyDown = useCallback(\n (e: React.KeyboardEvent<HTMLInputElement>) => {\n if (e.key === 'Enter') {\n e.preventDefault();\n handleCommit();\n } else if (e.key === 'Escape') {\n e.preventDefault();\n handleCancel();\n }\n },\n [handleCommit, handleCancel],\n );\n\n // `forceMode` (preview-only) renders the input branch without a real\n // click. `warning` / `error` also surface a sample message at that\n // severity so the matching knobs paint. Derived in the render body —\n // deliberately NOT fed into the focus effect above, so a forced preview\n // never steals focus from the rest of the page. Production leaves\n // forceMode unset, so `editing` collapses to the internal `isEditing`.\n const editing = forceMode != null || isEditing;\n const shownError: CellError | null =\n forceMode === 'error'\n ? error ?? { message: saveErrorMessage, severity: 'error' }\n : forceMode === 'warning'\n ? error ?? { message: invalidValueMessage, severity: 'warning' }\n : error;\n\n // Hint shown in the date input — the canonical format pattern for the\n // chosen `dateFormat`. Used as the placeholder when the cell is empty\n // and as the input placeholder in edit mode.\n const dateHint = FORMAT_SPEC[dateFormat].placeholder;\n\n // Compute display value\n const isEmpty = Array.isArray(value)\n ? value.length === 0\n : value === '' || value === null || value === undefined;\n\n const display = (() => {\n // Empty wins over `format`: a cleared cell must read as its placeholder,\n // not as the formatter's rendering of \"\" (e.g. a Tag formatter would\n // paint an empty pill). `format` only ever sees real values.\n if (isEmpty) return placeholder ?? (type === 'date' ? dateHint : '');\n if (format) return format(value);\n if (Array.isArray(value)) {\n // For multiselect: show labels from options if available\n if (options) {\n return value\n .map((v) => options.find((o) => o.value === v)?.label ?? v)\n .join(', ');\n }\n return value.join(', ');\n }\n if (type === 'select' && options) {\n return options.find((o) => o.value === String(value))?.label ?? String(value);\n }\n return value;\n })();\n\n // Plain-text form of the value, fed to the HoverTooltip so a value that's\n // clipped at the cell's max-width is still readable on hover. Best-effort\n // for custom `format` cells — those render arbitrary nodes, so the tooltip\n // falls back to the underlying value rather than extracting text from JSX.\n const isMulti = type === 'multiselect';\n const isSelect = type === 'select';\n const isDate = type === 'date';\n const titleText = isEmpty\n ? placeholder ?? ''\n : isMulti\n ? Array.isArray(value)\n ? (options ?? [])\n .filter((o) => (value as string[]).includes(o.value))\n .map((o) => o.label)\n .join(', ')\n : ''\n : isSelect\n ? (options?.find((o) => o.value === String(value))?.label ?? String(value))\n : isDate\n ? displayFormatted || String(value)\n : String(value);\n\n // For select/multiselect in open state, we render a Listbox-driven UI\n if (type === 'select' || type === 'multiselect') {\n const optionItems = options ?? [];\n const getKey = (o: EditableCellOption) => o.value;\n const getLabel = (o: EditableCellOption) => o.label;\n\n if (type === 'select') {\n const selectedItem = Array.isArray(value)\n ? null\n : optionItems.find((o) => o.value === String(value ?? '')) ?? null;\n\n return (\n <span\n ref={wrapRef}\n className={cn(\n 'uxm-editable-cell',\n `uxm-editable-cell--${size}`,\n `uxm-editable-cell--align-${align}`,\n `uxm-editable-cell--type-${type}`,\n clearable && 'uxm-editable-cell--clearable',\n disabled && 'uxm-editable-cell--disabled',\n isEmpty && 'uxm-editable-cell--empty',\n open && 'uxm-editable-cell--open',\n className,\n )}\n style={style}\n >\n <Listbox<EditableCellOption>\n items={optionItems}\n getKey={getKey}\n getLabel={getLabel}\n value={selectedItem}\n onChange={async (next) => {\n if (next) await commitValue(next.value);\n }}\n disabled={disabled}\n searchable={searchable}\n matchAnchorWidth\n onOpenChange={setOpen}\n renderTrigger={({ open: isOpen, triggerProps }: ListboxRenderTriggerState<EditableCellOption>) => (\n <button\n type=\"button\"\n {...triggerProps}\n className={cn(\n 'uxm-editable-cell__select-trigger',\n isOpen && 'uxm-editable-cell__select-trigger--open',\n )}\n aria-label={ariaLabel ?? `Edit ${String(value ?? '')}`}\n >\n <HoverTooltip content={titleText} disabled={disabled}>\n <span className=\"uxm-editable-cell__value uxm-editable-cell__option-label\">\n {display}\n </span>\n </HoverTooltip>\n <span className=\"uxm-editable-cell__chevron\" aria-hidden=\"true\">\n <Icon glyph=\"chevron-down\" size={12} />\n </span>\n </button>\n )}\n renderItem={(o) => (\n <span className=\"uxm-editable-cell__option-label\">{o.label}</span>\n )}\n footer={clearable && selectedItem ? ({ close }: { close: () => void }) => (\n <ButtonGhost\n className=\"uxm-listbox__footer-clear-option\"\n onClick={() => {\n commitValue('');\n close();\n }}\n >\n <Icon glyph=\"close\" size={12} />\n Clear\n </ButtonGhost>\n ) : undefined}\n />\n {clearable && !disabled && !submitting && !isEmpty && (\n /* Trigger ✕ — clear from the field itself (the input-family\n convention), on every size: commits the empty value directly,\n complementing the panel-footer Clear. Sibling of the trigger (a\n button can't nest a button), absolutely positioned into the\n reserved second gutter slot. Revealed only while the panel is\n open (see the `--open` gate in the stylesheet) — unlike the\n chevron, which also shows on hover. */\n <button\n type=\"button\"\n className=\"uxm-editable-cell__trigger-clear\"\n // Out of the tab order, same as every in-field clear here.\n tabIndex={-1}\n // This button is a SIBLING of the Listbox trigger, so it sits\n // outside the refs `useDismiss` treats as \"inside\" — and dismiss\n // runs on mousedown, i.e. BEFORE this click. Left alone, pressing\n // ✕ would first dismiss the panel and only then clear. Stop the\n // mousedown here so the clear below is the single thing that acts.\n onMouseDown={(e) => e.stopPropagation()}\n onClick={() => void commitValue('')}\n aria-label={clearSelectionLabel}\n >\n <Icon glyph=\"close\" size={12} />\n </button>\n )}\n <Popover\n open={Boolean(shownError)}\n onOpenChange={(next) => {\n if (!next) setError(null);\n }}\n anchor={wrapRef}\n placement=\"bottom-start\"\n closeOnEscape={false}\n closeOnOutsideClick={false}\n restoreFocus={false}\n role=\"presentation\"\n >\n {shownError && (\n <Banner\n variant={shownError.severity}\n id=\"uxm-editable-cell__error\"\n className=\"uxm-editable-cell__error-banner\"\n >\n {shownError.message}\n </Banner>\n )}\n </Popover>\n </span>\n );\n }\n\n // multiselect. A cell is a commit boundary, so this branch opts into\n // MultiListbox's staged mode (`commitMode=\"close\"`: draft while open, one\n // commit on close) — a required cell then survives \"clear all → pick one\"\n // and N picks are one `onCommit`. It just hands over the committed\n // selection and commits the final array back.\n const selectedItems = Array.isArray(value)\n ? optionItems.filter((o) => value.includes(o.value))\n : [];\n\n return (\n <span\n ref={wrapRef}\n className={cn(\n 'uxm-editable-cell',\n `uxm-editable-cell--${size}`,\n `uxm-editable-cell--align-${align}`,\n `uxm-editable-cell--type-${type}`,\n clearable && 'uxm-editable-cell--clearable',\n disabled && 'uxm-editable-cell--disabled',\n isEmpty && 'uxm-editable-cell--empty',\n open && 'uxm-editable-cell--open',\n className,\n )}\n style={style}\n >\n <MultiListbox<EditableCellOption>\n items={optionItems}\n getKey={getKey}\n getLabel={getLabel}\n value={selectedItems}\n onChange={(items) => void commitValue(items.map((o) => o.value))}\n // Commit on close, not per toggle — see the note above.\n commitMode=\"close\"\n required={required}\n requiredMessage={requiredMessage}\n // MultiListbox owns the empty rule for multi (shared with every\n // picker); route its violation into this cell's error Banner.\n onRequiredViolation={(message) => setError({ message, severity: 'warning' })}\n disabled={disabled}\n searchable={searchable}\n matchAnchorWidth\n // Clear a stale required warning when the panel reopens for a fresh edit.\n onOpenChange={(next) => {\n setOpen(next);\n if (next) setError(null);\n }}\n renderTrigger={({ open: isOpen, triggerProps }: ListboxRenderTriggerState<EditableCellOption>) => (\n <button\n type=\"button\"\n {...triggerProps}\n className={cn(\n 'uxm-editable-cell__select-trigger',\n isOpen && 'uxm-editable-cell__select-trigger--open',\n )}\n aria-label={ariaLabel ?? `Edit ${String(value ?? '')}`}\n >\n <HoverTooltip content={titleText} disabled={disabled}>\n <span className=\"uxm-editable-cell__value uxm-editable-cell__option-label\">\n {display}\n </span>\n </HoverTooltip>\n <span className=\"uxm-editable-cell__chevron\" aria-hidden=\"true\">\n <Icon glyph=\"chevron-down\" size={12} />\n </span>\n </button>\n )}\n renderItem={(o) => (\n <span className=\"uxm-editable-cell__option-label\">{o.label}</span>\n )}\n footer={clearable ? ({ clear, selected }: { clear: () => void; selected: EditableCellOption[] }) =>\n selected.length > 0 ? (\n <ButtonGhost\n className=\"uxm-listbox__footer-clear-option\"\n // `clear` (from MultiListbox) empties the draft and keeps the\n // panel open, so \"clear all then pick one\" works even on a\n // required cell — the empty state is transient and never\n // reaches `validate` / `onCommit`. `selected` is the live\n // draft, so the button hides itself once nothing is picked.\n onClick={clear}\n >\n <Icon glyph=\"close\" size={12} />\n Clear all\n </ButtonGhost>\n ) : null\n : undefined}\n />\n {clearable && !disabled && !submitting && !isEmpty && (\n /* Trigger ✕ — clear from the field itself (the input-family\n convention), on every size: commits the empty value directly,\n complementing the panel-footer Clear. Sibling of the trigger (a\n button can't nest a button), absolutely positioned into the\n reserved second gutter slot. Revealed only while the panel is\n open (see the `--open` gate in the stylesheet) — unlike the\n chevron, which also shows on hover. */\n <button\n type=\"button\"\n className=\"uxm-editable-cell__trigger-clear\"\n // Out of the tab order, same as every in-field clear here.\n tabIndex={-1}\n // Same sibling-of-the-trigger problem as the select ✕ above, but\n // costlier here: `useDismiss` fires on mousedown, and this panel\n // runs `commitMode=\"close\"`, so an unswallowed mousedown would\n // commit the STAGED DRAFT first and only then clear — two commits\n // for one press, and if the first is still in flight the\n // re-entrancy guard in `commitValue` drops the clear entirely.\n // Stopping it leaves the panel open, which is deliberate and\n // matches the footer's \"Clear all\": clear, then pick afresh.\n // MultiListbox re-seeds its draft from `value` under an open panel,\n // so the later close commits the cleared set, not the stale draft.\n onMouseDown={(e) => e.stopPropagation()}\n onClick={() => void commitValue([])}\n aria-label={clearSelectionLabel}\n >\n <Icon glyph=\"close\" size={12} />\n </button>\n )}\n <Popover\n open={Boolean(shownError)}\n onOpenChange={(next) => {\n if (!next) setError(null);\n }}\n anchor={wrapRef}\n placement=\"bottom-start\"\n closeOnEscape={false}\n closeOnOutsideClick={false}\n restoreFocus={false}\n role=\"presentation\"\n >\n {shownError && (\n <Banner\n variant={shownError.severity}\n id=\"uxm-editable-cell__error\"\n className=\"uxm-editable-cell__error-banner\"\n >\n {shownError.message}\n </Banner>\n )}\n </Popover>\n </span>\n );\n }\n\n // Date type — click-to-edit like text/number, but the editing surface is a\n // DateInput-style masked field: type the date OR pick it from the calendar\n // that opens on focus. Committed values are ISO; the input shows the mask.\n if (type === 'date') {\n // `parsedValue` (memoized above) already parses ISO-first with a mask\n // fallback, so the calendar highlights the right day for every `dateFormat`.\n const calendarValue: CalendarValue = { start: parsedValue, end: parsedValue };\n const spec = FORMAT_SPEC[dateFormat];\n\n if (!editing) {\n const displayNode = isEmpty\n // Fall back to the format hint (MM/DD/YYYY etc.) so an empty date cell\n // shows the mask as a placeholder — same as the shared `display` and\n // the edit-mode input, rather than rendering blank.\n ? placeholder ?? dateHint\n : format\n ? format(value)\n : displayFormatted || value;\n\n // Single button root — same shape as the text/number display so the\n // value's left edge and padding match the editing input exactly (no\n // horizontal jump on enter-edit). A wrapper + inner trigger would\n // double the padding; the display has no popover to anchor, so the\n // wrapper span is only needed in edit mode (for the calendar/error\n // popovers).\n return (\n <button\n type=\"button\"\n className={cn(\n 'uxm-editable-cell',\n `uxm-editable-cell--${size}`,\n `uxm-editable-cell--align-${align}`,\n `uxm-editable-cell--type-date`,\n disabled && 'uxm-editable-cell--disabled',\n isEmpty && 'uxm-editable-cell--empty',\n className,\n )}\n style={style}\n onClick={() => {\n if (disabled) return;\n // Enter edit AND open the calendar in one deterministic step —\n // mirrors DateInput's \"focus reveals the calendar\". Opening here\n // (rather than on the input's onFocus) avoids the focus-after-\n // remount race: the display button and editing input are\n // different elements, so the auto-focus that mounts the input\n // doesn't reliably deliver a focus event the calendar can hook.\n setIsEditing(true);\n setOpen(true);\n }}\n disabled={disabled}\n // Empty cell has no value to edit — \"Add date\" reads better than the\n // trailing-space \"Edit date \" and signals the fill affordance.\n aria-label={ariaLabel ?? (isEmpty ? addDateLabel : `Edit date ${displayFormatted || String(value)}`)}\n >\n <HoverTooltip content={titleText} disabled={disabled}>\n <span className=\"uxm-editable-cell__value\">{displayNode}</span>\n </HoverTooltip>\n {!disabled && (\n <span className=\"uxm-editable-cell__pencil\" aria-hidden=\"true\">\n <Icon glyph=\"calendar\" size={14} />\n </span>\n )}\n </button>\n );\n }\n\n // Date editing — DateInput-style masked input plus a calendar popover.\n return (\n <span\n ref={wrapRef}\n className={cn(\n 'uxm-editable-cell uxm-editable-cell--editing',\n `uxm-editable-cell--${size}`,\n `uxm-editable-cell--align-${align}`,\n `uxm-editable-cell--type-date`,\n clearable && 'uxm-editable-cell--clearable',\n shownError?.severity === 'error' && 'uxm-editable-cell--error',\n shownError?.severity === 'warning' && 'uxm-editable-cell--warning',\n className,\n )}\n style={style}\n >\n <span className=\"uxm-editable-cell__ghost\" aria-hidden=\"true\">\n {display}\n </span>\n <span className=\"uxm-editable-cell__ghost\" aria-hidden=\"true\">\n {draft || spec.placeholder}\n </span>\n <input\n ref={inputRef}\n type=\"text\"\n inputMode=\"numeric\"\n className=\"uxm-editable-cell__input\"\n value={draft}\n placeholder={spec.placeholder}\n onChange={(e) => {\n setDraft(maskDate(e.target.value, dateFormat));\n if (error) setError(null);\n }}\n onBlur={() => {\n if (!submitting) handleCommit();\n }}\n onKeyDown={handleKeyDown}\n disabled={submitting}\n aria-invalid={Boolean(shownError)}\n aria-describedby={shownError ? 'uxm-editable-cell__error' : undefined}\n />\n {/* ✕ sits inboard of the calendar toggle (mirrors DateInput's layout);\n hidden while the draft is empty — the reserved gutter stays, so\n typing never shifts the text. */}\n {clearable && !submitting && draft !== '' && (\n <button\n type=\"button\"\n className=\"uxm-editable-cell__clear\"\n // Out of the tab order (the in-field-clear convention). Tab must\n // keep moving to the next real control: the blur it fires commits\n // and leaves edit mode, which unmounts this button mid-focus-shift\n // and would drop focus to <body>. Pointer-only; select-all +\n // Delete is the keyboard path to the same result.\n tabIndex={-1}\n onMouseDown={(e) => e.preventDefault()}\n onClick={handleClearDraft}\n aria-label={clearDateLabel}\n >\n <Icon glyph=\"close\" size={12} />\n </button>\n )}\n <button\n type=\"button\"\n className=\"uxm-editable-cell__date-icon\"\n // Out of the tab order for the same reason as the ✕ above — Tab's\n // blur commits and unmounts this button. Keyboard users never need\n // it: entering edit mode on a date cell already opens the calendar.\n tabIndex={-1}\n // preventDefault keeps focus on the input — toggling the calendar\n // must not blur the field (a blur commits the in-flight draft).\n onMouseDown={(e) => e.preventDefault()}\n onClick={() => {\n if (submitting) return;\n // Clear any shown error as we open, so the calendar and the error\n // banner (both anchored here) are mutually exclusive — otherwise a\n // later `setError(null)` from typing would un-hide and reopen the\n // calendar the user had dismissed.\n setError(null);\n setOpen((o) => !o);\n }}\n disabled={submitting}\n aria-label={openCalendarLabel}\n // Match the popover's effective visibility (hidden while an error\n // shows) rather than raw `open`, so the two never disagree.\n aria-expanded={open && !shownError}\n >\n <Icon glyph=\"calendar\" size={14} />\n </button>\n <Popover\n open={open && !shownError}\n onOpenChange={setOpen}\n anchor={wrapRef}\n placement=\"bottom-start\"\n closeOnEscape\n closeOnOutsideClick\n restoreFocus={false}\n role=\"dialog\"\n // A `role=\"dialog\"` needs an accessible name. Non-modal by design\n // (no `aria-modal`): focus deliberately stays in the typed input\n // — `restoreFocus={false}` and the mousedown-preventDefault above\n // keep the field focused so the calendar augments typing rather\n // than trapping focus like a true modal.\n aria-label={calendarDialogLabel}\n >\n {/* Swallow mousedown so a calendar click never blurs the input:\n the pick commits explicitly via handlePick, and a blur-commit of\n the typed draft would race it. Purely focus management — the\n wrapper carries no semantics, so it stays a plain element. */}\n {/* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- focus-retention only; the interactive controls are the Calendar's own buttons */}\n <div onMouseDown={(e) => e.preventDefault()}>\n <Calendar value={calendarValue} onChange={handlePick} />\n </div>\n </Popover>\n <Popover\n open={Boolean(shownError)}\n onOpenChange={(next) => {\n if (!next) setError(null);\n }}\n anchor={wrapRef}\n placement=\"bottom-start\"\n closeOnEscape={false}\n closeOnOutsideClick={false}\n restoreFocus={false}\n role=\"presentation\"\n >\n {shownError && (\n <Banner\n variant={shownError.severity}\n id=\"uxm-editable-cell__error\"\n className=\"uxm-editable-cell__error-banner\"\n >\n {shownError.message}\n </Banner>\n )}\n </Popover>\n </span>\n );\n }\n\n // Text / number types\n if (!editing) {\n return (\n <button\n type=\"button\"\n className={cn(\n 'uxm-editable-cell',\n `uxm-editable-cell--${size}`,\n `uxm-editable-cell--align-${align}`,\n disabled && 'uxm-editable-cell--disabled',\n isEmpty && 'uxm-editable-cell--empty',\n className,\n )}\n style={style}\n onClick={() => {\n if (!disabled) setIsEditing(true);\n }}\n disabled={disabled}\n aria-label={ariaLabel ?? `Edit ${String(value ?? '')}`}\n >\n {/* The value needs its own element for ellipsis — text-overflow\n doesn't apply to a flex container's anonymous text. Values wider\n than the cell's max-width truncate instead of growing the column\n unboundedly; the hidden text surfaces in a hover tooltip (only\n when actually clipped) and stays editable by entering edit mode. */}\n <HoverTooltip content={titleText} disabled={disabled}>\n <span className=\"uxm-editable-cell__value\">{display}</span>\n </HoverTooltip>\n {/* Hover affordance — a pencil glyph fades in on hover / keyboard\n focus. The background tint alone can't signal editability inside\n a DataTable, where the row's own hover tint coincides with it.\n Absolute overlay in a reserved gutter (see editable-cell.scss) so\n it never shifts or collides with the value. Hidden when disabled. */}\n {!disabled && (\n <span className=\"uxm-editable-cell__pencil\" aria-hidden=\"true\">\n <Icon glyph=\"pencil\" size={16} strokeWidth={1.75} />\n </span>\n )}\n </button>\n );\n }\n\n return (\n <span\n ref={wrapRef}\n className={cn(\n 'uxm-editable-cell uxm-editable-cell--editing',\n `uxm-editable-cell--${size}`,\n `uxm-editable-cell--align-${align}`,\n clearable && 'uxm-editable-cell--clearable',\n shownError?.severity === 'error' && 'uxm-editable-cell--error',\n shownError?.severity === 'warning' && 'uxm-editable-cell--warning',\n className,\n )}\n style={style}\n >\n {/* Invisible width-keepers, stacked in the same grid cell as the\n input. The display content ghost preserves the exact min-content\n width this cell contributed before entering edit mode — without\n it, editing the column's WIDEST row would drop its contribution\n from auto table-column sizing and the column would visibly\n shrink. The draft ghost grows the cell naturally as the user\n types past the original width. */}\n <span className=\"uxm-editable-cell__ghost\" aria-hidden=\"true\">\n {display}\n </span>\n <span className=\"uxm-editable-cell__ghost\" aria-hidden=\"true\">\n {draft}\n </span>\n <input\n ref={inputRef}\n // `type=\"text\"` + `inputMode` — the input-family convention (see\n // NumberInput): native number inputs ship browser spinner UI, trigger\n // autofill, and disagree across browsers on accepted characters.\n // `maskNumeric` replaces the native filtering per keystroke (same as\n // the date editor's `maskDate`); without it, `Number()`'s loose\n // coercion would let e.g. \"0x1A\" commit as 26.\n type=\"text\"\n inputMode={type === 'number' ? 'decimal' : undefined}\n autoComplete=\"off\"\n className=\"uxm-editable-cell__input\"\n value={draft}\n onChange={(e) => {\n setDraft(\n type === 'number'\n ? maskNumeric(e.target.value, true, true, Number.POSITIVE_INFINITY)\n : e.target.value,\n );\n // Clear stale validation error as the user types — they'll\n // see a new one on the next commit attempt if it still fails.\n if (error) setError(null);\n }}\n onBlur={() => {\n // Don't double-commit while a previous commit is still pending.\n if (!submitting) handleCommit();\n }}\n onKeyDown={handleKeyDown}\n disabled={submitting}\n placeholder={placeholder}\n aria-invalid={Boolean(shownError)}\n aria-describedby={shownError ? 'uxm-editable-cell__error' : undefined}\n />\n {/* One-click ✕ (mirrors TextInput/NumberInput): empties the draft and\n keeps focus; hidden while the draft is already empty. The gutter is\n reserved via `--clearable` so its appearance never shifts text. */}\n {clearable && !submitting && draft !== '' && (\n <button\n type=\"button\"\n className=\"uxm-editable-cell__clear\"\n // Out of the tab order — see the date branch's note: the blur Tab\n // fires commits and unmounts this button, so leaving it focusable\n // would strand focus on a removed node instead of advancing to the\n // next cell.\n tabIndex={-1}\n onMouseDown={(e) => e.preventDefault()}\n onClick={handleClearDraft}\n aria-label={clearValueLabel}\n >\n <Icon glyph=\"close\" size={12} />\n </button>\n )}\n {/* Problems hang under the cell in a portal'd popover so the table\n row never changes height. The popover must not manage focus —\n the input keeps it for in-place correction / retry. Escape and\n outside clicks already have edit-mode semantics (cancel / blur-\n commit), so the popover's own close triggers are disabled; it\n opens and closes purely with the error state. */}\n <Popover\n open={Boolean(shownError)}\n onOpenChange={(next) => {\n if (!next) setError(null);\n }}\n anchor={wrapRef}\n placement=\"bottom-start\"\n closeOnEscape={false}\n closeOnOutsideClick={false}\n restoreFocus={false}\n role=\"presentation\"\n >\n {shownError && (\n <Banner\n variant={shownError.severity}\n id=\"uxm-editable-cell__error\"\n className=\"uxm-editable-cell__error-banner\"\n >\n {shownError.message}\n </Banner>\n )}\n </Popover>\n </span>\n );\n}\n"],"mappings":"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","names":["formatDateAs","parseFormattedDate"]}
|
|
@@ -85,6 +85,29 @@ export interface EditableCellProps {
|
|
|
85
85
|
required?: boolean;
|
|
86
86
|
/** Override the default required message (per-type: "Select at least one option" / "Select an option" / "Required"). */
|
|
87
87
|
requiredMessage?: string;
|
|
88
|
+
/**
|
|
89
|
+
* Shown at `error` severity when `onCommit` rejects without a message of
|
|
90
|
+
* its own. Default `"Failed to save"`. A rejection that *does* carry an
|
|
91
|
+
* `Error.message` still wins — that text is the server's, not the atom's.
|
|
92
|
+
*/
|
|
93
|
+
saveErrorMessage?: string;
|
|
94
|
+
/**
|
|
95
|
+
* Shown at `warning` severity when a `type="number"` draft isn't a number.
|
|
96
|
+
* Default `"Enter a number"`.
|
|
97
|
+
*/
|
|
98
|
+
invalidNumberMessage?: string;
|
|
99
|
+
/**
|
|
100
|
+
* Shown at `warning` severity when a `type="date"` draft doesn't parse.
|
|
101
|
+
* Defaults to `invalidDateMessage(dateFormat)` — `` `Enter a valid date
|
|
102
|
+
* (YYYY-MM-DD)` `` — which names the expected mask, so a translation
|
|
103
|
+
* should keep the mask in it.
|
|
104
|
+
*/
|
|
105
|
+
invalidDateMessage?: string;
|
|
106
|
+
/**
|
|
107
|
+
* Sample warning text for `forceMode="warning"`. Preview-only — production
|
|
108
|
+
* consumers never see it. Default `"Enter a valid value"`.
|
|
109
|
+
*/
|
|
110
|
+
invalidValueMessage?: string;
|
|
88
111
|
/** Read-only — clicking does nothing, no edit affordance. */
|
|
89
112
|
disabled?: boolean;
|
|
90
113
|
/** Shown when value is empty/blank. */
|
|
@@ -137,5 +160,5 @@ export interface EditableCellProps {
|
|
|
137
160
|
* Designed primarily for DataTable cells, but works anywhere an inline
|
|
138
161
|
* commit semantic is needed (PageHeader rename, StatCard label, etc.).
|
|
139
162
|
*/
|
|
140
|
-
export declare function EditableCell({ value, onCommit, type, dateFormat, options, searchable, clearable, align, size, format, validate, required, requiredMessage, disabled, placeholder, className, style, 'aria-label': ariaLabel, clearSelectionLabel, clearDateLabel, clearValueLabel, openCalendarLabel, calendarDialogLabel, addDateLabel, forceMode, }: EditableCellProps): import("react/jsx-runtime").JSX.Element;
|
|
163
|
+
export declare function EditableCell({ value, onCommit, type, dateFormat, options, searchable, clearable, align, size, format, validate, required, requiredMessage, saveErrorMessage, invalidNumberMessage, invalidDateMessage: invalidDateMessageProp, invalidValueMessage, disabled, placeholder, className, style, 'aria-label': ariaLabel, clearSelectionLabel, clearDateLabel, clearValueLabel, openCalendarLabel, calendarDialogLabel, addDateLabel, forceMode, }: EditableCellProps): import("react/jsx-runtime").JSX.Element;
|
|
141
164
|
//# sourceMappingURL=editable-cell.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"editable-cell.d.ts","sourceRoot":"","sources":["../../../src/ui/editable-cell/editable-cell.tsx"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"editable-cell.d.ts","sourceRoot":"","sources":["../../../src/ui/editable-cell/editable-cell.tsx"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,aAAa,EAClB,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAOf,OAAO,EAML,KAAK,eAAe,EACrB,MAAM,eAAe,CAAC;AAOvB,MAAM,MAAM,gBAAgB,GAAG,MAAM,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,aAAa,CAAC;AACrF,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,CAAC;AAC5D,MAAM,MAAM,gBAAgB,GAAG,OAAO,GAAG,QAAQ,CAAC;AAClD,MAAM,MAAM,iBAAiB,GAAG,MAAM,GAAG,MAAM,GAAG,MAAM,EAAE,CAAC;AA2C3D,MAAM,WAAW,kBAAkB;IACjC,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;CACf;AAcD,MAAM,WAAW,iBAAiB;IAChC,gFAAgF;IAChF,KAAK,EAAE,iBAAiB,CAAC;IACzB;;;;;OAKG;IACH,QAAQ,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5D,mKAAmK;IACnK,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB,uEAAuE;IACvE,UAAU,CAAC,EAAE,eAAe,CAAC;IAC7B,uEAAuE;IACvE,OAAO,CAAC,EAAE,kBAAkB,EAAE,CAAC;IAC/B;;;;;OAKG;IACH,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IAC9B;;;;;;;;;;;;;;;;;;;OAmBG;IACH,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,0HAA0H;IAC1H,KAAK,CAAC,EAAE,iBAAiB,CAAC;IAC1B;;;;;;;;;;OAUG;IACH,IAAI,CAAC,EAAE,gBAAgB,CAAC;IACxB;;;;;OAKG;IACH,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,iBAAiB,KAAK,SAAS,CAAC;IACjD,wGAAwG;IACxG,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,iBAAiB,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAClE;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,wHAAwH;IACxH,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;OAGG;IACH,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B;;;;;OAKG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,6DAA6D;IAC7D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,uCAAuC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,aAAa,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;OAIG;IACH,+EAA+E;IAC/E,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,yDAAyD;IACzD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,oEAAoE;IACpE,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gEAAgE;IAChE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,qEAAqE;IACrE,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B,wEAAwE;IACxE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB;;;;;;;OAOG;IACH,SAAS,CAAC,EAAE,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;CAC7C;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,EAC3B,KAAK,EACL,QAAQ,EACR,IAAa,EACb,UAAkB,EAClB,OAAO,EACP,UAAmB,EACnB,SAAgB,EAChB,KAAc,EACd,IAAc,EACd,MAAM,EACN,QAAQ,EACR,QAAgB,EAChB,eAAe,EACf,gBAAmC,EACnC,oBAAuC,EACvC,kBAAkB,EAAE,sBAAsB,EAC1C,mBAA2C,EAC3C,QAAQ,EACR,WAAW,EACX,SAAS,EACT,KAAK,EACL,YAAY,EAAE,SAAS,EACvB,mBAAuC,EACvC,cAA6B,EAC7B,eAA+B,EAC/B,iBAAmC,EACnC,mBAAmC,EACnC,YAAyB,EACzB,SAAS,GACV,EAAE,iBAAiB,2CAo8BnB"}
|