@entifix/react-controls 0.19.0 → 0.20.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/README.md +8 -0
- package/dist/i18n/catalog/en.d.ts.map +1 -1
- package/dist/i18n/catalog/en.js +55 -9
- package/dist/i18n/catalog/en.js.map +1 -1
- package/dist/i18n/catalog/es.d.ts +64 -5
- package/dist/i18n/catalog/es.d.ts.map +1 -1
- package/dist/i18n/catalog/es.js +61 -9
- package/dist/i18n/catalog/es.js.map +1 -1
- package/dist/i18n/catalog/index.d.ts +102 -10
- package/dist/i18n/catalog/index.d.ts.map +1 -1
- package/dist/i18n/entity-labels.d.ts +14 -0
- package/dist/i18n/entity-labels.d.ts.map +1 -1
- package/dist/i18n/entity-labels.js +15 -0
- package/dist/i18n/entity-labels.js.map +1 -1
- package/dist/i18n/i18n-context.js +2 -2
- package/dist/i18n/i18n-context.js.map +1 -1
- package/dist/index.d.ts +6 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/primitives.d.ts +0 -1
- package/dist/primitives.d.ts.map +1 -1
- package/dist/primitives.js +0 -1
- package/dist/primitives.js.map +1 -1
- package/dist/theme/index.d.ts +1 -0
- package/dist/theme/index.d.ts.map +1 -1
- package/dist/theme/index.js +1 -0
- package/dist/theme/index.js.map +1 -1
- package/dist/theme/theme-context.d.ts +15 -3
- package/dist/theme/theme-context.d.ts.map +1 -1
- package/dist/theme/theme-context.js +49 -23
- package/dist/theme/theme-context.js.map +1 -1
- package/dist/theme/theme-cookie.d.ts +9 -0
- package/dist/theme/theme-cookie.d.ts.map +1 -0
- package/dist/theme/theme-cookie.js +8 -0
- package/dist/theme/theme-cookie.js.map +1 -0
- package/dist/theme/theme-script.d.ts +8 -6
- package/dist/theme/theme-script.d.ts.map +1 -1
- package/dist/theme/theme-script.js +21 -12
- package/dist/theme/theme-script.js.map +1 -1
- package/dist/ui/actions/action-surfaces.d.ts +1 -1
- package/dist/ui/actions/action-surfaces.js +1 -1
- package/dist/ui/actions/action-surfaces.js.map +1 -1
- package/dist/ui/actions/error-actions.d.ts +32 -0
- package/dist/ui/actions/error-actions.d.ts.map +1 -0
- package/dist/ui/actions/error-actions.js +40 -0
- package/dist/ui/actions/error-actions.js.map +1 -0
- package/dist/ui/actions/index.d.ts +3 -0
- package/dist/ui/actions/index.d.ts.map +1 -1
- package/dist/ui/actions/index.js +3 -0
- package/dist/ui/actions/index.js.map +1 -1
- package/dist/ui/actions/shortcut.d.ts +13 -0
- package/dist/ui/actions/shortcut.d.ts.map +1 -0
- package/dist/ui/actions/shortcut.js +18 -0
- package/dist/ui/actions/shortcut.js.map +1 -0
- package/dist/ui/actions/use-verb-invoker.d.ts +55 -0
- package/dist/ui/actions/use-verb-invoker.d.ts.map +1 -0
- package/dist/ui/actions/use-verb-invoker.js +98 -0
- package/dist/ui/actions/use-verb-invoker.js.map +1 -0
- package/dist/ui/atoms/cell-value/cell-value.d.ts.map +1 -1
- package/dist/ui/atoms/cell-value/cell-value.js +28 -14
- package/dist/ui/atoms/cell-value/cell-value.js.map +1 -1
- package/dist/ui/atoms/field-control/field-control.d.ts +1 -0
- package/dist/ui/atoms/field-control/field-control.d.ts.map +1 -1
- package/dist/ui/atoms/field-control/field-control.js +2 -0
- package/dist/ui/atoms/field-control/field-control.js.map +1 -1
- package/dist/ui/atoms/number-input/quantity-input.d.ts +8 -1
- package/dist/ui/atoms/number-input/quantity-input.d.ts.map +1 -1
- package/dist/ui/atoms/number-input/quantity-input.js +2 -1
- package/dist/ui/atoms/number-input/quantity-input.js.map +1 -1
- package/dist/ui/atoms/table/table.d.ts +12 -3
- package/dist/ui/atoms/table/table.d.ts.map +1 -1
- package/dist/ui/atoms/table/table.js +59 -10
- package/dist/ui/atoms/table/table.js.map +1 -1
- package/dist/ui/atoms/text/text.d.ts +2 -2
- package/dist/ui/atoms/text/text.d.ts.map +1 -1
- package/dist/ui/atoms/text/text.js.map +1 -1
- package/dist/ui/molecules/action-bar/action-bar.d.ts +76 -0
- package/dist/ui/molecules/action-bar/action-bar.d.ts.map +1 -0
- package/dist/ui/molecules/action-bar/action-bar.js +218 -0
- package/dist/ui/molecules/action-bar/action-bar.js.map +1 -0
- package/dist/ui/molecules/action-bar/index.d.ts +2 -0
- package/dist/ui/molecules/action-bar/index.d.ts.map +1 -0
- package/dist/ui/molecules/action-bar/index.js +3 -0
- package/dist/ui/molecules/action-bar/index.js.map +1 -0
- package/dist/ui/molecules/bulk-action-bar/bulk-action-bar.d.ts.map +1 -1
- package/dist/ui/molecules/bulk-action-bar/bulk-action-bar.js +8 -10
- package/dist/ui/molecules/bulk-action-bar/bulk-action-bar.js.map +1 -1
- package/dist/ui/molecules/bulk-result/bulk-result.d.ts +3 -3
- package/dist/ui/molecules/bulk-result/bulk-result.js +4 -4
- package/dist/ui/molecules/bulk-result/bulk-result.js.map +1 -1
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.d.ts +24 -9
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.d.ts.map +1 -1
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.js +36 -11
- package/dist/ui/molecules/confirm-dialog/confirm-dialog.js.map +1 -1
- package/dist/ui/molecules/entity-record-card/entity-record-card.d.ts +33 -20
- package/dist/ui/molecules/entity-record-card/entity-record-card.d.ts.map +1 -1
- package/dist/ui/molecules/entity-record-card/entity-record-card.js +53 -20
- package/dist/ui/molecules/entity-record-card/entity-record-card.js.map +1 -1
- package/dist/ui/molecules/filter-builder/filter-builder.d.ts +8 -2
- package/dist/ui/molecules/filter-builder/filter-builder.d.ts.map +1 -1
- package/dist/ui/molecules/filter-builder/filter-builder.js +14 -5
- package/dist/ui/molecules/filter-builder/filter-builder.js.map +1 -1
- package/dist/ui/molecules/filter-chips/filter-chips.d.ts +16 -0
- package/dist/ui/molecules/filter-chips/filter-chips.d.ts.map +1 -0
- package/dist/ui/molecules/filter-chips/filter-chips.js +73 -0
- package/dist/ui/molecules/filter-chips/filter-chips.js.map +1 -0
- package/dist/ui/molecules/filter-chips/index.d.ts +2 -0
- package/dist/ui/molecules/filter-chips/index.d.ts.map +1 -0
- package/dist/ui/molecules/filter-chips/index.js +3 -0
- package/dist/ui/molecules/filter-chips/index.js.map +1 -0
- package/dist/ui/molecules/menu/menu.d.ts +11 -1
- package/dist/ui/molecules/menu/menu.d.ts.map +1 -1
- package/dist/ui/molecules/menu/menu.js +62 -2
- package/dist/ui/molecules/menu/menu.js.map +1 -1
- package/dist/ui/molecules/pagination/pagination.d.ts +5 -0
- package/dist/ui/molecules/pagination/pagination.d.ts.map +1 -1
- package/dist/ui/molecules/pagination/pagination.js +35 -18
- package/dist/ui/molecules/pagination/pagination.js.map +1 -1
- package/dist/ui/molecules/region-state/index.d.ts +2 -0
- package/dist/ui/molecules/region-state/index.d.ts.map +1 -0
- package/dist/ui/molecules/region-state/index.js +3 -0
- package/dist/ui/molecules/region-state/index.js.map +1 -0
- package/dist/ui/molecules/region-state/region-state.d.ts +42 -0
- package/dist/ui/molecules/region-state/region-state.d.ts.map +1 -0
- package/dist/ui/molecules/region-state/region-state.js +83 -0
- package/dist/ui/molecules/region-state/region-state.js.map +1 -0
- package/dist/ui/molecules/table-toolbar/table-toolbar.d.ts +3 -1
- package/dist/ui/molecules/table-toolbar/table-toolbar.d.ts.map +1 -1
- package/dist/ui/molecules/table-toolbar/table-toolbar.js +3 -2
- package/dist/ui/molecules/table-toolbar/table-toolbar.js.map +1 -1
- package/dist/ui/molecules/toast-region/toast-region.d.ts +63 -19
- package/dist/ui/molecules/toast-region/toast-region.d.ts.map +1 -1
- package/dist/ui/molecules/toast-region/toast-region.js +129 -42
- package/dist/ui/molecules/toast-region/toast-region.js.map +1 -1
- package/dist/ui/organisms/command-palette/command-palette.d.ts.map +1 -1
- package/dist/ui/organisms/command-palette/command-palette.js +19 -12
- package/dist/ui/organisms/command-palette/command-palette.js.map +1 -1
- package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.d.ts.map +1 -1
- package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.js +0 -1
- package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.js.map +1 -1
- package/dist/ui/organisms/entity-form/entity-form.d.ts +1 -1
- package/dist/ui/organisms/entity-form/entity-form.d.ts.map +1 -1
- package/dist/ui/organisms/entity-form/entity-form.js +322 -214
- package/dist/ui/organisms/entity-form/entity-form.js.map +1 -1
- package/dist/ui/organisms/entity-form/entity-form.types.d.ts +16 -0
- package/dist/ui/organisms/entity-form/entity-form.types.d.ts.map +1 -1
- package/dist/ui/organisms/entity-form/entity-form.types.js.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.d.ts +1 -1
- package/dist/ui/organisms/entity-table/entity-table.d.ts.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.js +332 -197
- package/dist/ui/organisms/entity-table/entity-table.js.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.types.d.ts +19 -4
- package/dist/ui/organisms/entity-table/entity-table.types.d.ts.map +1 -1
- package/dist/ui/organisms/entity-table/entity-table.types.js.map +1 -1
- package/dist/ui/organisms/entity-table/header-sort.d.ts +18 -0
- package/dist/ui/organisms/entity-table/header-sort.d.ts.map +1 -0
- package/dist/ui/organisms/entity-table/header-sort.js +54 -0
- package/dist/ui/organisms/entity-table/header-sort.js.map +1 -0
- package/dist/ui/organisms/entity-table/pivot.d.ts +25 -0
- package/dist/ui/organisms/entity-table/pivot.d.ts.map +1 -0
- package/dist/ui/organisms/entity-table/pivot.js +106 -0
- package/dist/ui/organisms/entity-table/pivot.js.map +1 -0
- package/dist/ui/organisms/entity-table/use-result-announcement.d.ts +13 -0
- package/dist/ui/organisms/entity-table/use-result-announcement.d.ts.map +1 -0
- package/dist/ui/organisms/entity-table/use-result-announcement.js +31 -0
- package/dist/ui/organisms/entity-table/use-result-announcement.js.map +1 -0
- package/dist/ui/organisms/top-bar/live-indicator.d.ts +11 -0
- package/dist/ui/organisms/top-bar/live-indicator.d.ts.map +1 -0
- package/dist/ui/organisms/top-bar/live-indicator.js +46 -0
- package/dist/ui/organisms/top-bar/live-indicator.js.map +1 -0
- package/dist/ui/organisms/top-bar/top-bar.d.ts +11 -2
- package/dist/ui/organisms/top-bar/top-bar.d.ts.map +1 -1
- package/dist/ui/organisms/top-bar/top-bar.js +14 -5
- package/dist/ui/organisms/top-bar/top-bar.js.map +1 -1
- package/dist/ui/organisms/wizard/wizard.d.ts +1 -1
- package/dist/ui/organisms/wizard/wizard.d.ts.map +1 -1
- package/dist/ui/organisms/wizard/wizard.js +7 -1
- package/dist/ui/organisms/wizard/wizard.js.map +1 -1
- package/dist/ui/organisms/wizard/wizard.types.d.ts +8 -0
- package/dist/ui/organisms/wizard/wizard.types.d.ts.map +1 -1
- package/dist/ui/organisms/wizard/wizard.types.js.map +1 -1
- package/dist/ui/utils/live-updates.d.ts +24 -0
- package/dist/ui/utils/live-updates.d.ts.map +1 -0
- package/dist/ui/utils/live-updates.js +20 -0
- package/dist/ui/utils/live-updates.js.map +1 -0
- package/dist/ui/utils/use-focus-on-arrival.d.ts +14 -0
- package/dist/ui/utils/use-focus-on-arrival.d.ts.map +1 -0
- package/dist/ui/utils/use-focus-on-arrival.js +42 -0
- package/dist/ui/utils/use-focus-on-arrival.js.map +1 -0
- package/dist/ui/utils/use-hotkey.d.ts +44 -25
- package/dist/ui/utils/use-hotkey.d.ts.map +1 -1
- package/dist/ui/utils/use-hotkey.js +80 -48
- package/dist/ui/utils/use-hotkey.js.map +1 -1
- package/package.json +5 -5
- package/dist/ui/molecules/split-button/index.d.ts +0 -2
- package/dist/ui/molecules/split-button/index.d.ts.map +0 -1
- package/dist/ui/molecules/split-button/index.js +0 -3
- package/dist/ui/molecules/split-button/index.js.map +0 -1
- package/dist/ui/molecules/split-button/split-button.d.ts +0 -21
- package/dist/ui/molecules/split-button/split-button.d.ts.map +0 -1
- package/dist/ui/molecules/split-button/split-button.js +0 -35
- package/dist/ui/molecules/split-button/split-button.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/organisms/command-palette/command-palette.tsx"],"sourcesContent":["'use client';\n\nimport {\n commandEffectOf,\n type CommandGroup,\n type CommandOption,\n type CommandPage,\n} from '@entifix/core';\nimport {\n Combobox,\n ComboboxInput,\n ComboboxOption,\n ComboboxOptions,\n Dialog,\n DialogPanel,\n DialogTitle,\n} from '@headlessui/react';\nimport type { KeyboardEvent } from 'react';\n\nimport { SkeletonText } from '../../atoms/skeleton/index.js';\nimport { cn } from '../../utils/cn.js';\n\n/** Every string the palette renders. Resolved — this control does not translate. */\nexport interface CommandPaletteLabels {\n /** Names the dialog. A dialog with no accessible name is announced as \"dialog\". */\n readonly title: string;\n /** Nothing matched, and every source said so. */\n readonly empty: string;\n /** Announced while a group is still answering. */\n readonly loading: string;\n /** \"N más\" — a function, so the plural rule stays in the catalog. */\n readonly more: (remaining: number) => string;\n /** Leaves a pushed page. Also the chip's accessible name. */\n readonly back: string;\n}\n\nexport interface CommandPaletteProps {\n open: boolean;\n onClose: () => void;\n /** Every page this palette can show. The root is the first. */\n pages: readonly CommandPage[];\n /**\n * The pushed page ids, outermost first — empty means the root.\n *\n * Controlled, so the host can empty it in the same handler that opens the\n * palette. An internal stack would have to be reset from an effect keyed on\n * `open`, which is setting state in a passive effect for something that is a\n * fact about the event that opened it.\n */\n stack: readonly string[];\n onStackChange: (stack: readonly string[]) => void;\n term: string;\n onTermChange: (term: string) => void;\n /**\n * An option was chosen. The host runs it — this control has no router and\n * cannot navigate, which is the `hrefFor` rule.\n */\n onSelect: (option: CommandOption) => void;\n labels: CommandPaletteLabels;\n}\n\nconst groupIsEmpty = (group: CommandGroup): boolean =>\n group.options.length === 0 &&\n !group.isLoading &&\n group.unavailable === undefined;\n\nfunction GroupHeading({ label }: { label: string }) {\n return (\n // `presentation`, not a heading: everything inside a `listbox` that is not\n // an `option` must be presentational, or assistive tech counts the heading\n // as a selectable row and the option count it announces is wrong.\n <div\n role=\"presentation\"\n className=\"tracking-wide px-2xs pt-2xs pb-3xs text-step-xs font-semibold text-content-muted uppercase\"\n >\n {label}\n </div>\n );\n}\n\nfunction CommandRow({ option }: { option: CommandOption }) {\n return (\n <ComboboxOption\n value={option}\n className={cn(\n 'flex min-h-(--target-min) cursor-pointer items-baseline gap-2xs rounded-md px-2xs py-3xs',\n 'text-step-sm text-content',\n 'focus-ring data-focus:bg-surface data-focus:-outline-offset-2',\n )}\n >\n <span className=\"truncate\">{option.label}</span>\n {option.sublabel !== undefined && (\n <span className=\"truncate text-step-xs text-content-muted\">\n {option.sublabel}\n </span>\n )}\n {option.hint !== undefined && (\n <span className=\"ml-auto shrink-0 text-step-xs text-content-muted\">\n {option.hint}\n </span>\n )}\n </ComboboxOption>\n );\n}\n\nfunction Group({\n group,\n labels,\n}: {\n group: CommandGroup;\n labels: CommandPaletteLabels;\n}) {\n const remaining =\n group.total === undefined ? 0 : group.total - group.options.length;\n\n return (\n <div data-testid={`command-group-${group.key}`}>\n <GroupHeading label={group.label} />\n\n {group.isLoading && (\n <div role=\"presentation\" className=\"px-2xs py-3xs\">\n {/* The skeleton is `aria-hidden`, so the wait needs its own text or a\n screen reader hears a group that simply has no rows. */}\n <span className=\"sr-only\">{labels.loading}</span>\n <SkeletonText lines={2} />\n </div>\n )}\n\n {group.options.map(option => (\n <CommandRow key={option.id} option={option} />\n ))}\n\n {remaining > 0 && (\n <div\n role=\"presentation\"\n className=\"px-2xs py-3xs text-step-xs text-content-muted\"\n >\n {labels.more(remaining)}\n </div>\n )}\n\n {/*\n A degraded source is NAMED, never a missing group: an absent group reads\n as \"nothing matched\", which nobody can honestly claim about a source they\n could not reach. The two severities look different on purpose — `scope`\n is the ordinary state for an operator with no organization, and painting\n that as a warning on every keystroke teaches people to ignore the one\n that means something is broken.\n */}\n {group.unavailable !== undefined && (\n <div\n role=\"presentation\"\n data-testid={`command-unavailable-${group.key}`}\n className={cn(\n 'px-2xs py-3xs text-step-xs',\n group.unavailable.severity === 'reachability'\n ? 'text-danger'\n : 'text-content-muted',\n )}\n >\n {group.unavailable.message}\n </div>\n )}\n </div>\n );\n}\n\n/**\n * A floating command bar: search destinations, records and commands, and run\n * them from the keyboard.\n *\n * Presentational, and strictly so. It holds no index, performs no fetch and\n * knows no router — every group arrives already filtered, already authorized and\n * already translated, from hooks in the shell layer that meet this component at\n * the framework-free `CommandSource` port in core. That is the same arrangement\n * `EntityLinkSource` uses, and it exists because `@entifix/react-controls` may\n * not import `@entifix/react-integration`.\n *\n * **The sources filter, not this component.** A record group is filtered by the\n * service that answered it; a command group is filtered by the hook that built\n * it. Filtering again here would mean the palette had to know which groups were\n * pre-filtered, and a group that answered a *different* term would be silently\n * emptied rather than shown as stale.\n *\n * Depth is a **stack of pages**: selecting an option carrying `push` makes the\n * palette become that page. `Escape` pops one level and closes at the root;\n * `Backspace` on an empty term pops too, which is the gesture Raycast trained\n * people on and costs nothing when there is nothing to pop.\n *\n * Built on Headless UI's `Dialog` + `Combobox`, so the focus trap, focus\n * restoration, `role=\"combobox\"`, `aria-expanded`, `aria-activedescendant` and\n * the arrow-key contract are the library's rather than seven hand-written\n * behaviours that each have to be right.\n */\nexport function CommandPalette({\n open,\n onClose,\n pages,\n stack,\n onStackChange,\n term,\n onTermChange,\n onSelect,\n labels,\n}: CommandPaletteProps) {\n const activeId = stack[stack.length - 1];\n const page =\n (activeId === undefined\n ? pages[0]\n : pages.find(candidate => candidate.id === activeId)) ?? pages[0];\n\n const groups = page.sources.flatMap(source => source.groups);\n // Runs on every render of the palette, not on the click: an option with no\n // effect — or with two — is a line that looks selectable and misbehaves, and\n // the check that only fires when someone already selected it is the check\n // that fires after the bug is reported.\n groups.forEach(group => group.options.forEach(commandEffectOf));\n\n const isEmpty = groups.every(groupIsEmpty);\n\n const pop = () => {\n if (stack.length === 0) {\n onClose();\n return;\n }\n onStackChange(stack.slice(0, -1));\n onTermChange('');\n };\n\n const handleSelect = (option: CommandOption | null) => {\n // Headless UI types `onChange` as nullable — it reports `null` when a search\n // is abandoned rather than chosen, and acting on that would run whatever\n // happened to be highlighted.\n //\n // ⚠️ Unreachable *here*, and ignored for coverage rather than contorted\n // around: this combobox is driven entirely from the outside (`value` is the\n // caller's `term`, there is no `displayValue`) and Escape is handled on the\n // input above, so the library never produces the null it admits. The guard\n // stays because the type is the library's contract, not ours to assume away.\n /* v8 ignore next 2 */\n if (option === null) return;\n if (option.push !== undefined) {\n onStackChange([...stack, option.push]);\n onTermChange('');\n return;\n }\n onSelect(option);\n };\n\n const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n // ⚠️ Escape is handled here rather than left to the `Dialog`, because\n // `Combobox` consumes it first once a term has been typed — it treats the\n // key as \"close the suggestion list\", which for a `static` list means\n // nothing at all, and the palette then refuses to close for exactly the\n // person who has typed something. Measured, not defensive.\n if (event.key === 'Escape') {\n event.preventDefault();\n event.stopPropagation();\n pop();\n return;\n }\n // Only when there is nothing to delete, so the key keeps its ordinary\n // meaning for as long as the person is still editing a term.\n if (event.key === 'Backspace' && term === '' && stack.length > 0) {\n event.preventDefault();\n pop();\n }\n };\n\n return (\n <Dialog\n open={open}\n // Headless UI routes Escape here. Popping rather than closing is what\n // makes the stack feel like a stack; at the root the two are the same.\n onClose={pop}\n className=\"inset-0 fixed z-(--z-palette) flex items-start justify-center overflow-y-auto p-s pt-(--overlay-offset-block)\"\n >\n <div aria-hidden className=\"inset-0 fixed bg-scrim\" />\n <DialogPanel\n data-testid=\"command-palette\"\n className=\"relative w-full max-w-(--container-xl) overflow-hidden rounded-lg border border-border bg-surface-elevated shadow-overlay\"\n >\n <DialogTitle className=\"sr-only\">{labels.title}</DialogTitle>\n\n {/*\n Uncontrolled: this combobox has no value worth holding — an option is\n an act, not a stored choice. Every render rebuilds its options from the\n sources, so the identities differ and selecting the same row twice\n still fires `onChange` both times.\n */}\n <Combobox<CommandOption> onChange={handleSelect}>\n <div className=\"flex items-center gap-2xs border-b border-border px-s py-2xs\">\n {page.title !== undefined && (\n <button\n type=\"button\"\n aria-label={labels.back}\n onClick={pop}\n className=\"shrink-0 rounded-md bg-surface px-2xs py-3xs text-step-xs text-content-muted focus-ring\"\n >\n {page.title}\n </button>\n )}\n <ComboboxInput\n data-autofocus\n aria-label={labels.title}\n placeholder={page.placeholder}\n value={term}\n onChange={event => onTermChange(event.currentTarget.value)}\n onKeyDown={handleKeyDown}\n className=\"flex-1 rounded-sm bg-transparent py-3xs text-step-1 text-content focus-ring placeholder:text-content-muted\"\n />\n </div>\n\n <ComboboxOptions\n static\n className={cn(\n 'max-h-(--overlay-max-block) overflow-y-auto p-3xs',\n // The panel is a focus-managed CONTAINER, not a target: the ring\n // belongs on the row inside it.\n 'focus:outline-none',\n )}\n >\n {isEmpty ? (\n <div\n role=\"presentation\"\n className=\"px-2xs py-s text-step-sm text-content-muted\"\n >\n {labels.empty}\n </div>\n ) : (\n groups\n .filter(group => !groupIsEmpty(group))\n .map(group => (\n <Group key={group.key} group={group} labels={labels} />\n ))\n )}\n </ComboboxOptions>\n </Combobox>\n </DialogPanel>\n </Dialog>\n );\n}\n"],"names":["commandEffectOf","Combobox","ComboboxInput","ComboboxOption","ComboboxOptions","Dialog","DialogPanel","DialogTitle","SkeletonText","cn","groupIsEmpty","group","options","length","isLoading","unavailable","undefined","GroupHeading","label","div","role","className","CommandRow","option","value","span","sublabel","hint","Group","labels","remaining","total","data-testid","key","loading","lines","map","id","more","severity","message","CommandPalette","open","onClose","pages","stack","onStackChange","term","onTermChange","onSelect","activeId","page","find","candidate","groups","sources","flatMap","source","forEach","isEmpty","every","pop","slice","handleSelect","push","handleKeyDown","event","preventDefault","stopPropagation","aria-hidden","title","onChange","button","type","aria-label","back","onClick","data-autofocus","placeholder","currentTarget","onKeyDown","static","empty","filter"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/organisms/command-palette/command-palette.tsx"],"sourcesContent":["'use client';\n\nimport {\n commandEffectOf,\n type CommandGroup,\n type CommandOption,\n type CommandPage,\n} from '@entifix/core';\nimport {\n Combobox,\n ComboboxInput,\n ComboboxOption,\n ComboboxOptions,\n Dialog,\n DialogPanel,\n DialogTitle,\n} from '@headlessui/react';\nimport type { KeyboardEvent } from 'react';\n\nimport { SkeletonText } from '../../atoms/skeleton/index.js';\nimport { cn } from '../../utils/cn.js';\n\n/** Every string the palette renders. Resolved — this control does not translate. */\nexport interface CommandPaletteLabels {\n /** Names the dialog. A dialog with no accessible name is announced as \"dialog\". */\n readonly title: string;\n /** Nothing matched, and every source said so. */\n readonly empty: string;\n /** Announced while a group is still answering. */\n readonly loading: string;\n /** \"N más\" — a function, so the plural rule stays in the catalog. */\n readonly more: (remaining: number) => string;\n /** Leaves a pushed page. Also the chip's accessible name. */\n readonly back: string;\n}\n\nexport interface CommandPaletteProps {\n open: boolean;\n onClose: () => void;\n /** Every page this palette can show. The root is the first. */\n pages: readonly CommandPage[];\n /**\n * The pushed page ids, outermost first — empty means the root.\n *\n * Controlled, so the host can empty it in the same handler that opens the\n * palette. An internal stack would have to be reset from an effect keyed on\n * `open`, which is setting state in a passive effect for something that is a\n * fact about the event that opened it.\n */\n stack: readonly string[];\n onStackChange: (stack: readonly string[]) => void;\n term: string;\n onTermChange: (term: string) => void;\n /**\n * An option was chosen. The host runs it — this control has no router and\n * cannot navigate, which is the `hrefFor` rule.\n */\n onSelect: (option: CommandOption) => void;\n labels: CommandPaletteLabels;\n}\n\nconst groupIsEmpty = (group: CommandGroup): boolean =>\n group.options.length === 0 &&\n !group.isLoading &&\n group.unavailable === undefined;\n\nfunction GroupHeading({ label }: { label: string }) {\n return (\n // `presentation`, not a heading: everything inside a `listbox` that is not\n // an `option` must be presentational, or assistive tech counts the heading\n // as a selectable row and the option count it announces is wrong.\n <div\n role=\"presentation\"\n className=\"tracking-wide px-2xs pt-2xs pb-3xs text-step-xs font-semibold text-content-muted uppercase\"\n >\n {label}\n </div>\n );\n}\n\nfunction CommandRow({ option }: { option: CommandOption }) {\n return (\n <ComboboxOption\n value={option}\n className={cn(\n 'flex min-h-(--target-min) cursor-pointer items-baseline gap-2xs rounded-md px-2xs py-3xs',\n 'text-step-sm text-content',\n 'focus-ring data-focus:bg-surface data-focus:-outline-offset-2',\n )}\n >\n <span className=\"truncate\">{option.label}</span>\n {option.sublabel !== undefined && (\n <span className=\"truncate text-step-xs text-content-muted\">\n {option.sublabel}\n </span>\n )}\n {option.hint !== undefined && (\n <span className=\"ml-auto shrink-0 text-step-xs text-content-muted\">\n {option.hint}\n </span>\n )}\n </ComboboxOption>\n );\n}\n\nfunction Group({\n group,\n labels,\n}: {\n group: CommandGroup;\n labels: CommandPaletteLabels;\n}) {\n const remaining =\n group.total === undefined ? 0 : group.total - group.options.length;\n\n return (\n <div data-testid={`command-group-${group.key}`}>\n <GroupHeading label={group.label} />\n\n {group.isLoading && (\n // The wait is said once, by the palette's status region: a listbox\n // holds options, and text inside it is read as none of them.\n <div role=\"presentation\" className=\"px-2xs py-3xs\">\n <SkeletonText lines={2} />\n </div>\n )}\n\n {group.options.map(option => (\n <CommandRow key={option.id} option={option} />\n ))}\n\n {remaining > 0 && (\n <div\n role=\"presentation\"\n className=\"px-2xs py-3xs text-step-xs text-content-muted\"\n >\n {labels.more(remaining)}\n </div>\n )}\n\n {/*\n A degraded source is NAMED, never a missing group: an absent group reads\n as \"nothing matched\", which nobody can honestly claim about a source they\n could not reach. The two severities look different on purpose — `scope`\n is the ordinary state for an operator with no organization, and painting\n that as a warning on every keystroke teaches people to ignore the one\n that means something is broken.\n */}\n {group.unavailable !== undefined && (\n <div\n role=\"presentation\"\n data-testid={`command-unavailable-${group.key}`}\n className={cn(\n 'px-2xs py-3xs text-step-xs',\n group.unavailable.severity === 'reachability'\n ? 'text-danger'\n : 'text-content-muted',\n )}\n >\n {group.unavailable.message}\n </div>\n )}\n </div>\n );\n}\n\n/**\n * A floating command bar: search destinations, records and commands, and run\n * them from the keyboard.\n *\n * Presentational, and strictly so. It holds no index, performs no fetch and\n * knows no router — every group arrives already filtered, already authorized and\n * already translated, from hooks in the shell layer that meet this component at\n * the framework-free `CommandSource` port in core. That is the same arrangement\n * `EntityLinkSource` uses, and it exists because `@entifix/react-controls` may\n * not import `@entifix/react-integration`.\n *\n * **The sources filter, not this component.** A record group is filtered by the\n * service that answered it; a command group is filtered by the hook that built\n * it. Filtering again here would mean the palette had to know which groups were\n * pre-filtered, and a group that answered a *different* term would be silently\n * emptied rather than shown as stale.\n *\n * Depth is a **stack of pages**: selecting an option carrying `push` makes the\n * palette become that page. `Escape` pops one level and closes at the root;\n * `Backspace` on an empty term pops too, which is the gesture Raycast trained\n * people on and costs nothing when there is nothing to pop.\n *\n * Built on Headless UI's `Dialog` + `Combobox`, so the focus trap, focus\n * restoration, `role=\"combobox\"`, `aria-expanded`, `aria-activedescendant` and\n * the arrow-key contract are the library's rather than seven hand-written\n * behaviours that each have to be right.\n */\nexport function CommandPalette({\n open,\n onClose,\n pages,\n stack,\n onStackChange,\n term,\n onTermChange,\n onSelect,\n labels,\n}: CommandPaletteProps) {\n const activeId = stack[stack.length - 1];\n const page =\n (activeId === undefined\n ? pages[0]\n : pages.find(candidate => candidate.id === activeId)) ?? pages[0];\n\n const groups = page.sources.flatMap(source => source.groups);\n // Runs on every render of the palette, not on the click: an option with no\n // effect — or with two — is a line that looks selectable and misbehaves, and\n // the check that only fires when someone already selected it is the check\n // that fires after the bug is reported.\n groups.forEach(group => group.options.forEach(commandEffectOf));\n\n const isEmpty = groups.every(groupIsEmpty);\n const isAnswering = groups.some(group => group.isLoading);\n\n const pop = () => {\n if (stack.length === 0) {\n onClose();\n return;\n }\n onStackChange(stack.slice(0, -1));\n onTermChange('');\n };\n\n const handleSelect = (option: CommandOption | null) => {\n // Headless UI types `onChange` as nullable — it reports `null` when a search\n // is abandoned rather than chosen, and acting on that would run whatever\n // happened to be highlighted.\n //\n // ⚠️ Unreachable *here*, and ignored for coverage rather than contorted\n // around: this combobox is driven entirely from the outside (`value` is the\n // caller's `term`, there is no `displayValue`) and Escape is handled on the\n // input above, so the library never produces the null it admits. The guard\n // stays because the type is the library's contract, not ours to assume away.\n /* v8 ignore next 2 */\n if (option === null) return;\n if (option.push !== undefined) {\n onStackChange([...stack, option.push]);\n onTermChange('');\n return;\n }\n onSelect(option);\n };\n\n const handleKeyDown = (event: KeyboardEvent<HTMLInputElement>) => {\n // ⚠️ Escape is handled here rather than left to the `Dialog`, because\n // `Combobox` consumes it first once a term has been typed — it treats the\n // key as \"close the suggestion list\", which for a `static` list means\n // nothing at all, and the palette then refuses to close for exactly the\n // person who has typed something. Measured, not defensive.\n if (event.key === 'Escape') {\n event.preventDefault();\n event.stopPropagation();\n pop();\n return;\n }\n // Only when there is nothing to delete, so the key keeps its ordinary\n // meaning for as long as the person is still editing a term.\n if (event.key === 'Backspace' && term === '' && stack.length > 0) {\n event.preventDefault();\n pop();\n }\n };\n\n return (\n <Dialog\n open={open}\n // Headless UI routes Escape here. Popping rather than closing is what\n // makes the stack feel like a stack; at the root the two are the same.\n onClose={pop}\n className=\"inset-0 fixed z-(--z-palette) flex items-start justify-center overflow-y-auto p-s pt-(--overlay-offset-block)\"\n >\n <div aria-hidden className=\"inset-0 fixed bg-scrim\" />\n <DialogPanel\n data-testid=\"command-palette\"\n className=\"relative w-full max-w-(--container-xl) overflow-hidden rounded-lg border border-border bg-surface-elevated shadow-overlay\"\n >\n <DialogTitle className=\"sr-only\">{labels.title}</DialogTitle>\n\n {/*\n Uncontrolled: this combobox has no value worth holding — an option is\n an act, not a stored choice. Every render rebuilds its options from the\n sources, so the identities differ and selecting the same row twice\n still fires `onChange` both times.\n */}\n <Combobox<CommandOption> onChange={handleSelect}>\n <div className=\"flex items-center gap-2xs border-b border-border px-s py-2xs\">\n {page.title !== undefined && (\n <button\n type=\"button\"\n // Its name holds the text it shows (WCAG 2.5.3): \"Volver\" is\n // said, and \"Nuevo\" is both seen and said.\n aria-label={`${labels.back} ${page.title}`}\n onClick={pop}\n className=\"inline-flex min-h-(--target-min) shrink-0 items-center rounded-md bg-surface px-2xs py-3xs text-step-xs text-content-muted focus-ring\"\n >\n {page.title}\n </button>\n )}\n <ComboboxInput\n data-autofocus\n aria-label={labels.title}\n placeholder={page.placeholder}\n value={term}\n onChange={event => onTermChange(event.currentTarget.value)}\n onKeyDown={handleKeyDown}\n className=\"flex-1 rounded-sm bg-transparent py-3xs text-step-1 text-content focus-ring placeholder:text-content-muted\"\n />\n </div>\n\n {/* Outside the listbox, which may hold only options: said when the\n palette finds nothing, and while a group is still answering. */}\n <span role=\"status\" className=\"sr-only\">\n {isEmpty ? labels.empty : isAnswering ? labels.loading : ''}\n </span>\n\n <ComboboxOptions\n static\n // Still filling: a listbox with no options yet is not a broken one.\n aria-busy={isAnswering || undefined}\n className={cn(\n 'max-h-(--overlay-max-block) overflow-y-auto p-3xs',\n // The panel is a focus-managed CONTAINER, not a target: the ring\n // belongs on the row inside it.\n 'focus:outline-none',\n )}\n >\n {isEmpty ? (\n <div\n role=\"presentation\"\n aria-hidden=\"true\"\n className=\"px-2xs py-s text-step-sm text-content-muted\"\n >\n {labels.empty}\n </div>\n ) : (\n groups\n .filter(group => !groupIsEmpty(group))\n .map(group => (\n <Group key={group.key} group={group} labels={labels} />\n ))\n )}\n </ComboboxOptions>\n </Combobox>\n </DialogPanel>\n </Dialog>\n );\n}\n"],"names":["commandEffectOf","Combobox","ComboboxInput","ComboboxOption","ComboboxOptions","Dialog","DialogPanel","DialogTitle","SkeletonText","cn","groupIsEmpty","group","options","length","isLoading","unavailable","undefined","GroupHeading","label","div","role","className","CommandRow","option","value","span","sublabel","hint","Group","labels","remaining","total","data-testid","key","lines","map","id","more","severity","message","CommandPalette","open","onClose","pages","stack","onStackChange","term","onTermChange","onSelect","activeId","page","find","candidate","groups","sources","flatMap","source","forEach","isEmpty","every","isAnswering","some","pop","slice","handleSelect","push","handleKeyDown","event","preventDefault","stopPropagation","aria-hidden","title","onChange","button","type","aria-label","back","onClick","data-autofocus","placeholder","currentTarget","onKeyDown","empty","loading","static","aria-busy","filter"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-detail-grid.d.ts","sourceRoot":"","sources":["../../../../src/ui/organisms/entity-detail-grid/entity-detail-grid.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAK3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,EACV,IAAI,EACJ,YAAY,EACZ,MAAM,EACN,OAAe,EACf,SAAiB,EACjB,QAAe,EACf,MAAM,EACN,SAAS,GACV,EAAE,qBAAqB,+
|
|
1
|
+
{"version":3,"file":"entity-detail-grid.d.ts","sourceRoot":"","sources":["../../../../src/ui/organisms/entity-detail-grid/entity-detail-grid.tsx"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAC;AAK3E;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AACH,wBAAgB,gBAAgB,CAAC,EAC/B,UAAU,EACV,IAAI,EACJ,YAAY,EACZ,MAAM,EACN,OAAe,EACf,SAAiB,EACjB,QAAe,EACf,MAAM,EACN,SAAS,GACV,EAAE,qBAAqB,+BA4MvB"}
|
|
@@ -141,7 +141,6 @@ import { cn } from '../../utils/cn.js';
|
|
|
141
141
|
/*#__PURE__*/ _jsxs(Table, {
|
|
142
142
|
"aria-label": descriptor.label,
|
|
143
143
|
"aria-busy": isLoading || undefined,
|
|
144
|
-
"aria-rowcount": rows.length,
|
|
145
144
|
children: [
|
|
146
145
|
/*#__PURE__*/ _jsx(TableHead, {
|
|
147
146
|
children: /*#__PURE__*/ _jsxs(TableRow, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../../../src/ui/organisms/entity-detail-grid/entity-detail-grid.tsx"],"sourcesContent":["'use client';\n\nimport {\n describeChildColumns,\n editableChildColumns,\n type EntityFieldDescriptor,\n type EntityRowDraft,\n newRowKey,\n parseRowFieldPath,\n ROW_KEY,\n rowFieldPath,\n} from '@entifix/core';\nimport { type KeyboardEvent, useId, useMemo } from 'react';\n\nimport { useLocalizedDescriptors, useT } from '../../../i18n/index.js';\nimport { Button } from '../../atoms/button/index.js';\nimport { CellValue } from '../../atoms/cell-value/index.js';\nimport { FieldControl } from '../../atoms/field-control/index.js';\nimport { Skeleton } from '../../atoms/skeleton/index.js';\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeaderCell,\n TableMessageRow,\n TableRow,\n} from '../../atoms/table/index.js';\nimport { Stack } from '../../molecules/stack/index.js';\nimport { cn } from '../../utils/cn.js';\nimport type { EntityDetailGridProps } from './entity-detail-grid.types.js';\n\n/** Rows the placeholder stands in for. Enough to read as a grid, not a page. */\nconst SKELETON_ROW_COUNT = 3;\n\n/**\n * The rows a record **owns**, edited in place.\n *\n * A `composition` — an order and its lines — where the rows have no life outside\n * the master and go out in the same write\n *. Not\n * a `linkCollection`, which is association to records that exist on their own\n * and save separately; that editor is still #26.\n *\n * ## Why this is not `EntityTable`\n *\n * `EntityTable` is a *server* listing: pages, RSQL filtering and sorting,\n * column personalization, a selection model and a bulk bar. A detail grid has\n * none of those and cannot have them — its rows are local, unsaved, and\n * unqueryable by construction, since a collection member is never `filterable`.\n * Sharing the organism would mean every one of those features growing a\n * \"detail\" branch. It shares the `Table` **atoms** instead, which is the level\n * the two actually have in common.\n *\n * ## Inline cells, and why Enter is free\n *\n * Each cell is a {@link FieldControl}, the same descriptor→input map the form\n * uses, so a child's `enum` gets a select and its `date` a date input with no\n * code here. `Tab` walks the row natively and crosses into the next; `Enter` on\n * the last row appends and focuses the new row's first cell.\n *\n * That last binding is available only because `EntityForm` renders a `<Card>`\n * with a `<button onClick>` Save rather than a `<form>` — inside a real form,\n * `Enter` in a text input submits, and appending a row would fight the browser\n * for it. Wrapping the form in a `<form>` element later would break this, which\n * is the one thing worth knowing before doing it.\n *\n * ## Errors\n *\n * A cell carries `aria-invalid` and points at its own message; the grid carries\n * **one** summary above it, which is the only `role=\"alert\"` in the component.\n * A live region per cell would announce the whole grid on every keystroke, and\n * a message rendered only in the cell is unreachable when the failing row is\n * scrolled out — the case a long grid always meets.\n */\nexport function EntityDetailGrid({\n descriptor,\n rows,\n onRowsChange,\n errors,\n editing = false,\n isLoading = false,\n skeleton = true,\n footer,\n className,\n}: EntityDetailGridProps) {\n const t = useT();\n const gridId = useId();\n\n const described = useMemo(\n () =>\n descriptor.childType === undefined\n ? []\n : editableChildColumns(describeChildColumns(descriptor.childType)),\n [descriptor.childType],\n );\n const columns = useLocalizedDescriptors(described);\n\n // Only the messages that name *this* member. The form hands over its whole\n // error map so a record with two owned collections needs no bookkeeping at\n // the call site, and a grid that filtered nothing would show the other's.\n const rowErrors = useMemo(\n () => readRowErrors(descriptor.name, errors),\n [descriptor.name, errors],\n );\n\n const showSkeleton = isLoading && skeleton !== false;\n const customSkeleton = typeof skeleton === 'boolean' ? undefined : skeleton;\n // One more than the columns: every row carries its remove control.\n const columnCount = columns.length + (editing ? 1 : 0);\n\n const setRow = (index: number, member: string, value: string) => {\n onRowsChange(\n rows.map((row, position) =>\n position === index ? { ...row, [member]: value } : row,\n ),\n );\n };\n\n const firstColumn = columns[0]?.name;\n\n const appendRow = () => {\n onRowsChange([...rows, seedEmptyRow(columns)]);\n focusCell(gridId, rows.length, firstColumn);\n };\n\n const removeRow = (index: number) => {\n onRowsChange(rows.filter((_row, position) => position !== index));\n // Focus the row that slid into this position, or the one above it when the\n // removed row was last — never nothing, which drops the user to the page.\n focusCell(gridId, Math.min(index, rows.length - 2), firstColumn);\n };\n\n /**\n * `Enter` anywhere in the **last row** appends the next one.\n *\n * Bound to the row rather than to its final cell, which is what it looked\n * like it should be: the last *column* is whatever the child declared last,\n * and a read-only member there renders a disabled input that never receives a\n * key at all — so the binding would exist and be unreachable, on exactly the\n * entities that have a computed column. The row is the unit the user is\n * finishing anyway.\n *\n * Only from a text input: a `<select>` opens on Enter and a checkbox toggles\n * on Space, so taking the key from those would break a control's own\n * behaviour to add a row the user did not ask for. A read-only column needs\n * no test of its own — its input is `disabled`, so it never takes focus and\n * never raises a key here at all.\n */\n const onLastRowKeyDown = (event: KeyboardEvent<HTMLTableRowElement>) => {\n if (event.key !== 'Enter') return;\n if (!(event.target instanceof HTMLInputElement)) return;\n event.preventDefault();\n appendRow();\n };\n\n return (\n <Stack gap=\"2xs\" className={className}>\n <div className=\"flex items-center justify-between gap-s\">\n <span className=\"text-step-sm font-medium text-content-muted\">\n {descriptor.label}\n </span>\n {editing && (\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n onClick={appendRow}\n disabled={isLoading}\n >\n {t('detail.addRow')}\n </Button>\n )}\n </div>\n\n {editing && rowErrors.size > 0 && (\n <div role=\"alert\" className=\"text-step-sm text-danger\">\n {t('detail.errorSummary', { count: rowErrors.size })}\n </div>\n )}\n\n <Table\n aria-label={descriptor.label}\n aria-busy={isLoading || undefined}\n aria-rowcount={rows.length}\n >\n <TableHead>\n <TableRow>\n {columns.map(column => (\n <TableHeaderCell\n key={column.name}\n id={`${gridId}-${column.name}`}\n >\n {column.label}\n </TableHeaderCell>\n ))}\n {editing && (\n <TableHeaderCell align=\"end\">\n <span className=\"sr-only\">{t('detail.rowActions')}</span>\n </TableHeaderCell>\n )}\n </TableRow>\n </TableHead>\n\n <TableBody>\n {showSkeleton\n ? (customSkeleton ?? (\n <SkeletonRows\n columns={columns.length}\n extra={editing ? 1 : 0}\n />\n ))\n : rows.map((row, index) => (\n <TableRow\n key={row[ROW_KEY]}\n onKeyDown={\n editing && index === rows.length - 1\n ? onLastRowKeyDown\n : undefined\n }\n >\n {columns.map(column => {\n const error = rowErrors.get(\n rowFieldPath(descriptor.name, index, column.name),\n );\n const cellId = `${gridId}-${index}-${column.name}`;\n return (\n <TableCell key={column.name}>\n {editing ? (\n <FieldControl\n descriptor={column}\n value={row[column.name] ?? ''}\n id={cellId}\n aria-labelledby={`${gridId}-${column.name}`}\n aria-invalid={error !== undefined || undefined}\n aria-describedby={\n error === undefined\n ? undefined\n : `${cellId}-error`\n }\n onChange={next => setRow(index, column.name, next)}\n record={row}\n />\n ) : column.money === undefined ? (\n <span>{row[column.name] ?? ''}</span>\n ) : (\n <CellValue\n value={row[column.name]}\n descriptor={column}\n record={row}\n />\n )}\n {editing && error !== undefined && (\n <span\n id={`${cellId}-error`}\n className=\"mt-3xs block text-step-sm text-danger\"\n >\n {error}\n </span>\n )}\n </TableCell>\n );\n })}\n {editing && (\n <TableCell align=\"end\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => removeRow(index)}\n >\n {t('detail.removeRow', { row: index + 1 })}\n </Button>\n </TableCell>\n )}\n </TableRow>\n ))}\n\n {!showSkeleton && rows.length === 0 && (\n <TableMessageRow colSpan={Math.max(columnCount, 1)}>\n {t('detail.empty')}\n </TableMessageRow>\n )}\n </TableBody>\n </Table>\n\n {footer !== undefined && !showSkeleton && (\n <div className={cn('flex justify-end text-step-sm text-content')}>\n {footer(rows)}\n </div>\n )}\n </Stack>\n );\n}\n\n/** The placeholder, shaped like the grid it stands in for. */\nfunction SkeletonRows({ columns, extra }: { columns: number; extra: number }) {\n return (\n <>\n {Array.from({ length: SKELETON_ROW_COUNT }, (_row, index) => (\n <TableRow key={index}>\n {Array.from({ length: columns + extra }, (_cell, cellIndex) => (\n <TableCell key={cellIndex}>\n <Skeleton className=\"h-8 w-full\" />\n </TableCell>\n ))}\n </TableRow>\n ))}\n </>\n );\n}\n\n/**\n * A blank row, seeded with every column the grid edits.\n *\n * Every member is present and empty rather than absent, for the reason a form's\n * seed carries every key: a control handed `undefined` flips from controlled to\n * uncontrolled the moment the user types into it.\n */\nfunction seedEmptyRow(\n columns: readonly EntityFieldDescriptor[],\n): EntityRowDraft {\n const row: EntityRowDraft = { [ROW_KEY]: newRowKey() };\n for (const column of columns) row[column.name] = '';\n return row;\n}\n\n/**\n * Moves focus into a cell after the list changed.\n *\n * By element id rather than through a ref into the `<tbody>`, because the row\n * being focused does not exist yet when this is called — it is the row the\n * caller has just asked React to render. Deferring to a microtask lets that\n * render commit first; `getElementById` then finds the input the same way the\n * cell's own `aria-describedby` does.\n *\n * A row index that names nothing simply focuses nothing, which is the right\n * outcome for removing the only row: there is no cell to land in, and stealing\n * focus somewhere arbitrary is worse than leaving it where the user put it.\n */\nfunction focusCell(gridId: string, index: number, column?: string): void {\n if (column === undefined || index < 0) return;\n\n queueMicrotask(() => {\n document.getElementById(`${gridId}-${index}-${column}`)?.focus();\n });\n}\n\n/** The messages naming a row of `member`, keyed by their full path. */\nfunction readRowErrors(\n member: string,\n errors: Record<string, string> | undefined,\n): Map<string, string> {\n const found = new Map<string, string>();\n if (errors === undefined) return found;\n\n for (const [path, message] of Object.entries(errors)) {\n if (parseRowFieldPath(path)?.member === member) found.set(path, message);\n }\n return found;\n}\n"],"names":["describeChildColumns","editableChildColumns","newRowKey","parseRowFieldPath","ROW_KEY","rowFieldPath","useId","useMemo","useLocalizedDescriptors","useT","Button","CellValue","FieldControl","Skeleton","Table","TableBody","TableCell","TableHead","TableHeaderCell","TableMessageRow","TableRow","Stack","cn","SKELETON_ROW_COUNT","EntityDetailGrid","descriptor","rows","onRowsChange","errors","editing","isLoading","skeleton","footer","className","columns","t","gridId","described","childType","undefined","rowErrors","readRowErrors","name","showSkeleton","customSkeleton","columnCount","length","setRow","index","member","value","map","row","position","firstColumn","appendRow","seedEmptyRow","focusCell","removeRow","filter","_row","Math","min","onLastRowKeyDown","event","key","target","HTMLInputElement","preventDefault","gap","div","span","label","type","variant","size","onClick","disabled","role","count","aria-label","aria-busy","aria-rowcount","column","id","align","SkeletonRows","extra","onKeyDown","error","get","cellId","aria-labelledby","aria-invalid","aria-describedby","onChange","next","record","money","colSpan","max","Array","from","_cell","cellIndex","queueMicrotask","document","getElementById","focus","found","Map","path","message","Object","entries","set"],"mappings":"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"}
|
|
1
|
+
{"version":3,"sources":["../../../../src/ui/organisms/entity-detail-grid/entity-detail-grid.tsx"],"sourcesContent":["'use client';\n\nimport {\n describeChildColumns,\n editableChildColumns,\n type EntityFieldDescriptor,\n type EntityRowDraft,\n newRowKey,\n parseRowFieldPath,\n ROW_KEY,\n rowFieldPath,\n} from '@entifix/core';\nimport { type KeyboardEvent, useId, useMemo } from 'react';\n\nimport { useLocalizedDescriptors, useT } from '../../../i18n/index.js';\nimport { Button } from '../../atoms/button/index.js';\nimport { CellValue } from '../../atoms/cell-value/index.js';\nimport { FieldControl } from '../../atoms/field-control/index.js';\nimport { Skeleton } from '../../atoms/skeleton/index.js';\nimport {\n Table,\n TableBody,\n TableCell,\n TableHead,\n TableHeaderCell,\n TableMessageRow,\n TableRow,\n} from '../../atoms/table/index.js';\nimport { Stack } from '../../molecules/stack/index.js';\nimport { cn } from '../../utils/cn.js';\nimport type { EntityDetailGridProps } from './entity-detail-grid.types.js';\n\n/** Rows the placeholder stands in for. Enough to read as a grid, not a page. */\nconst SKELETON_ROW_COUNT = 3;\n\n/**\n * The rows a record **owns**, edited in place.\n *\n * A `composition` — an order and its lines — where the rows have no life outside\n * the master and go out in the same write\n *. Not\n * a `linkCollection`, which is association to records that exist on their own\n * and save separately; that editor is still #26.\n *\n * ## Why this is not `EntityTable`\n *\n * `EntityTable` is a *server* listing: pages, RSQL filtering and sorting,\n * column personalization, a selection model and a bulk bar. A detail grid has\n * none of those and cannot have them — its rows are local, unsaved, and\n * unqueryable by construction, since a collection member is never `filterable`.\n * Sharing the organism would mean every one of those features growing a\n * \"detail\" branch. It shares the `Table` **atoms** instead, which is the level\n * the two actually have in common.\n *\n * ## Inline cells, and why Enter is free\n *\n * Each cell is a {@link FieldControl}, the same descriptor→input map the form\n * uses, so a child's `enum` gets a select and its `date` a date input with no\n * code here. `Tab` walks the row natively and crosses into the next; `Enter` on\n * the last row appends and focuses the new row's first cell.\n *\n * That last binding is available only because `EntityForm` renders a `<Card>`\n * with a `<button onClick>` Save rather than a `<form>` — inside a real form,\n * `Enter` in a text input submits, and appending a row would fight the browser\n * for it. Wrapping the form in a `<form>` element later would break this, which\n * is the one thing worth knowing before doing it.\n *\n * ## Errors\n *\n * A cell carries `aria-invalid` and points at its own message; the grid carries\n * **one** summary above it, which is the only `role=\"alert\"` in the component.\n * A live region per cell would announce the whole grid on every keystroke, and\n * a message rendered only in the cell is unreachable when the failing row is\n * scrolled out — the case a long grid always meets.\n */\nexport function EntityDetailGrid({\n descriptor,\n rows,\n onRowsChange,\n errors,\n editing = false,\n isLoading = false,\n skeleton = true,\n footer,\n className,\n}: EntityDetailGridProps) {\n const t = useT();\n const gridId = useId();\n\n const described = useMemo(\n () =>\n descriptor.childType === undefined\n ? []\n : editableChildColumns(describeChildColumns(descriptor.childType)),\n [descriptor.childType],\n );\n const columns = useLocalizedDescriptors(described);\n\n // Only the messages that name *this* member. The form hands over its whole\n // error map so a record with two owned collections needs no bookkeeping at\n // the call site, and a grid that filtered nothing would show the other's.\n const rowErrors = useMemo(\n () => readRowErrors(descriptor.name, errors),\n [descriptor.name, errors],\n );\n\n const showSkeleton = isLoading && skeleton !== false;\n const customSkeleton = typeof skeleton === 'boolean' ? undefined : skeleton;\n // One more than the columns: every row carries its remove control.\n const columnCount = columns.length + (editing ? 1 : 0);\n\n const setRow = (index: number, member: string, value: string) => {\n onRowsChange(\n rows.map((row, position) =>\n position === index ? { ...row, [member]: value } : row,\n ),\n );\n };\n\n const firstColumn = columns[0]?.name;\n\n const appendRow = () => {\n onRowsChange([...rows, seedEmptyRow(columns)]);\n focusCell(gridId, rows.length, firstColumn);\n };\n\n const removeRow = (index: number) => {\n onRowsChange(rows.filter((_row, position) => position !== index));\n // Focus the row that slid into this position, or the one above it when the\n // removed row was last — never nothing, which drops the user to the page.\n focusCell(gridId, Math.min(index, rows.length - 2), firstColumn);\n };\n\n /**\n * `Enter` anywhere in the **last row** appends the next one.\n *\n * Bound to the row rather than to its final cell, which is what it looked\n * like it should be: the last *column* is whatever the child declared last,\n * and a read-only member there renders a disabled input that never receives a\n * key at all — so the binding would exist and be unreachable, on exactly the\n * entities that have a computed column. The row is the unit the user is\n * finishing anyway.\n *\n * Only from a text input: a `<select>` opens on Enter and a checkbox toggles\n * on Space, so taking the key from those would break a control's own\n * behaviour to add a row the user did not ask for. A read-only column needs\n * no test of its own — its input is `disabled`, so it never takes focus and\n * never raises a key here at all.\n */\n const onLastRowKeyDown = (event: KeyboardEvent<HTMLTableRowElement>) => {\n if (event.key !== 'Enter') return;\n if (!(event.target instanceof HTMLInputElement)) return;\n event.preventDefault();\n appendRow();\n };\n\n return (\n <Stack gap=\"2xs\" className={className}>\n <div className=\"flex items-center justify-between gap-s\">\n <span className=\"text-step-sm font-medium text-content-muted\">\n {descriptor.label}\n </span>\n {editing && (\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n onClick={appendRow}\n disabled={isLoading}\n >\n {t('detail.addRow')}\n </Button>\n )}\n </div>\n\n {editing && rowErrors.size > 0 && (\n <div role=\"alert\" className=\"text-step-sm text-danger\">\n {t('detail.errorSummary', { count: rowErrors.size })}\n </div>\n )}\n\n <Table aria-label={descriptor.label} aria-busy={isLoading || undefined}>\n <TableHead>\n <TableRow>\n {columns.map(column => (\n <TableHeaderCell\n key={column.name}\n id={`${gridId}-${column.name}`}\n >\n {column.label}\n </TableHeaderCell>\n ))}\n {editing && (\n <TableHeaderCell align=\"end\">\n <span className=\"sr-only\">{t('detail.rowActions')}</span>\n </TableHeaderCell>\n )}\n </TableRow>\n </TableHead>\n\n <TableBody>\n {showSkeleton\n ? (customSkeleton ?? (\n <SkeletonRows\n columns={columns.length}\n extra={editing ? 1 : 0}\n />\n ))\n : rows.map((row, index) => (\n <TableRow\n key={row[ROW_KEY]}\n onKeyDown={\n editing && index === rows.length - 1\n ? onLastRowKeyDown\n : undefined\n }\n >\n {columns.map(column => {\n const error = rowErrors.get(\n rowFieldPath(descriptor.name, index, column.name),\n );\n const cellId = `${gridId}-${index}-${column.name}`;\n return (\n <TableCell key={column.name}>\n {editing ? (\n <FieldControl\n descriptor={column}\n value={row[column.name] ?? ''}\n id={cellId}\n aria-labelledby={`${gridId}-${column.name}`}\n aria-invalid={error !== undefined || undefined}\n aria-describedby={\n error === undefined\n ? undefined\n : `${cellId}-error`\n }\n onChange={next => setRow(index, column.name, next)}\n record={row}\n />\n ) : column.money === undefined ? (\n <span>{row[column.name] ?? ''}</span>\n ) : (\n <CellValue\n value={row[column.name]}\n descriptor={column}\n record={row}\n />\n )}\n {editing && error !== undefined && (\n <span\n id={`${cellId}-error`}\n className=\"mt-3xs block text-step-sm text-danger\"\n >\n {error}\n </span>\n )}\n </TableCell>\n );\n })}\n {editing && (\n <TableCell align=\"end\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n onClick={() => removeRow(index)}\n >\n {t('detail.removeRow', { row: index + 1 })}\n </Button>\n </TableCell>\n )}\n </TableRow>\n ))}\n\n {!showSkeleton && rows.length === 0 && (\n <TableMessageRow colSpan={Math.max(columnCount, 1)}>\n {t('detail.empty')}\n </TableMessageRow>\n )}\n </TableBody>\n </Table>\n\n {footer !== undefined && !showSkeleton && (\n <div className={cn('flex justify-end text-step-sm text-content')}>\n {footer(rows)}\n </div>\n )}\n </Stack>\n );\n}\n\n/** The placeholder, shaped like the grid it stands in for. */\nfunction SkeletonRows({ columns, extra }: { columns: number; extra: number }) {\n return (\n <>\n {Array.from({ length: SKELETON_ROW_COUNT }, (_row, index) => (\n <TableRow key={index}>\n {Array.from({ length: columns + extra }, (_cell, cellIndex) => (\n <TableCell key={cellIndex}>\n <Skeleton className=\"h-8 w-full\" />\n </TableCell>\n ))}\n </TableRow>\n ))}\n </>\n );\n}\n\n/**\n * A blank row, seeded with every column the grid edits.\n *\n * Every member is present and empty rather than absent, for the reason a form's\n * seed carries every key: a control handed `undefined` flips from controlled to\n * uncontrolled the moment the user types into it.\n */\nfunction seedEmptyRow(\n columns: readonly EntityFieldDescriptor[],\n): EntityRowDraft {\n const row: EntityRowDraft = { [ROW_KEY]: newRowKey() };\n for (const column of columns) row[column.name] = '';\n return row;\n}\n\n/**\n * Moves focus into a cell after the list changed.\n *\n * By element id rather than through a ref into the `<tbody>`, because the row\n * being focused does not exist yet when this is called — it is the row the\n * caller has just asked React to render. Deferring to a microtask lets that\n * render commit first; `getElementById` then finds the input the same way the\n * cell's own `aria-describedby` does.\n *\n * A row index that names nothing simply focuses nothing, which is the right\n * outcome for removing the only row: there is no cell to land in, and stealing\n * focus somewhere arbitrary is worse than leaving it where the user put it.\n */\nfunction focusCell(gridId: string, index: number, column?: string): void {\n if (column === undefined || index < 0) return;\n\n queueMicrotask(() => {\n document.getElementById(`${gridId}-${index}-${column}`)?.focus();\n });\n}\n\n/** The messages naming a row of `member`, keyed by their full path. */\nfunction readRowErrors(\n member: string,\n errors: Record<string, string> | undefined,\n): Map<string, string> {\n const found = new Map<string, string>();\n if (errors === undefined) return found;\n\n for (const [path, message] of Object.entries(errors)) {\n if (parseRowFieldPath(path)?.member === member) found.set(path, message);\n }\n return found;\n}\n"],"names":["describeChildColumns","editableChildColumns","newRowKey","parseRowFieldPath","ROW_KEY","rowFieldPath","useId","useMemo","useLocalizedDescriptors","useT","Button","CellValue","FieldControl","Skeleton","Table","TableBody","TableCell","TableHead","TableHeaderCell","TableMessageRow","TableRow","Stack","cn","SKELETON_ROW_COUNT","EntityDetailGrid","descriptor","rows","onRowsChange","errors","editing","isLoading","skeleton","footer","className","columns","t","gridId","described","childType","undefined","rowErrors","readRowErrors","name","showSkeleton","customSkeleton","columnCount","length","setRow","index","member","value","map","row","position","firstColumn","appendRow","seedEmptyRow","focusCell","removeRow","filter","_row","Math","min","onLastRowKeyDown","event","key","target","HTMLInputElement","preventDefault","gap","div","span","label","type","variant","size","onClick","disabled","role","count","aria-label","aria-busy","column","id","align","SkeletonRows","extra","onKeyDown","error","get","cellId","aria-labelledby","aria-invalid","aria-describedby","onChange","next","record","money","colSpan","max","Array","from","_cell","cellIndex","queueMicrotask","document","getElementById","focus","found","Map","path","message","Object","entries","set"],"mappings":"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"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import { type Entity } from '@entifix/core';
|
|
2
2
|
import type { EntityFormProps } from './entity-form.types.js';
|
|
3
|
-
export declare function EntityForm<TEntity extends Entity>({ entityConstructor, entity, values, onFieldChange, linkSources, onLinkChange, mode: modeProp, defaultMode, onModeChange, errors, formError, onSubmit, onDelete, embedded, isLoading, skeleton, isSaving, isPending, isDeleting, error, backHref, renderLink, title, metadata, isMetadataLoading, onUseCase, onClone, children, }: EntityFormProps<TEntity>): import("react").JSX.Element;
|
|
3
|
+
export declare function EntityForm<TEntity extends Entity>({ entityConstructor, entity, values, onFieldChange, linkSources, onLinkChange, mode: modeProp, defaultMode, onModeChange, errors, formError, onSubmit, onDelete, deletePolicy, recordLabel: hostRecordLabel, embedded, isLoading, skeleton, isSaving, isPending, isDeleting, error, onRetry, backHref, renderLink, title, metadata, isMetadataLoading, onUseCase, onClone, children, }: EntityFormProps<TEntity>): import("react").JSX.Element;
|
|
4
4
|
//# sourceMappingURL=entity-form.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"entity-form.d.ts","sourceRoot":"","sources":["../../../../src/ui/organisms/entity-form/entity-form.tsx"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,MAAM,
|
|
1
|
+
{"version":3,"file":"entity-form.d.ts","sourceRoot":"","sources":["../../../../src/ui/organisms/entity-form/entity-form.tsx"],"names":[],"mappings":"AAEA,OAAO,EAGL,KAAK,MAAM,EAWZ,MAAM,eAAe,CAAC;AA+BvB,OAAO,KAAK,EAGV,eAAe,EAChB,MAAM,wBAAwB,CAAC;AAqDhC,wBAAgB,UAAU,CAAC,OAAO,SAAS,MAAM,EAAE,EACjD,iBAAiB,EACjB,MAAM,EACN,MAAM,EACN,aAAa,EACb,WAAW,EACX,YAAY,EACZ,IAAI,EAAE,QAAQ,EACd,WAAW,EACX,YAAY,EACZ,MAAM,EACN,SAAS,EACT,QAAQ,EACR,QAAQ,EACR,YAAwB,EACxB,WAAW,EAAE,eAAe,EAC5B,QAAgB,EAChB,SAAiB,EACjB,QAAe,EACf,QAAgB,EAChB,SAAiB,EACjB,UAAkB,EAClB,KAAK,EACL,OAAO,EACP,QAAQ,EACR,UAAyB,EACzB,KAAK,EACL,QAAQ,EACR,iBAAyB,EACzB,SAAS,EACT,OAAO,EACP,QAAQ,GACT,EAAE,eAAe,CAAC,OAAO,CAAC,+BAsc1B"}
|