@entifix/react-controls 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/i18n/catalog/es.d.ts +1 -1
  2. package/dist/i18n/catalog/es.js +1 -1
  3. package/dist/i18n/catalog/es.js.map +1 -1
  4. package/dist/preferences/use-viewport-mode.d.ts +0 -1
  5. package/dist/preferences/use-viewport-mode.d.ts.map +1 -1
  6. package/dist/preferences/use-viewport-mode.js +0 -1
  7. package/dist/preferences/use-viewport-mode.js.map +1 -1
  8. package/dist/ui/actions/action-surfaces.d.ts +3 -3
  9. package/dist/ui/actions/action-surfaces.js +4 -4
  10. package/dist/ui/actions/action-surfaces.js.map +1 -1
  11. package/dist/ui/atoms/field-control/field-control.d.ts +1 -1
  12. package/dist/ui/atoms/field-control/field-control.js +1 -1
  13. package/dist/ui/atoms/field-control/field-control.js.map +1 -1
  14. package/dist/ui/molecules/bulk-action-bar/bulk-action-bar.d.ts +2 -2
  15. package/dist/ui/molecules/bulk-action-bar/bulk-action-bar.js.map +1 -1
  16. package/dist/ui/molecules/bulk-result/bulk-result.js.map +1 -1
  17. package/dist/ui/molecules/loading-boundary/loading-boundary.d.ts +1 -1
  18. package/dist/ui/molecules/loading-boundary/loading-boundary.js +1 -1
  19. package/dist/ui/molecules/loading-boundary/loading-boundary.js.map +1 -1
  20. package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.d.ts +2 -2
  21. package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.js +2 -2
  22. package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.js.map +1 -1
  23. package/dist/ui/organisms/entity-form/entity-form.js +4 -4
  24. package/dist/ui/organisms/entity-form/entity-form.js.map +1 -1
  25. package/dist/ui/organisms/entity-form/entity-form.types.d.ts +2 -2
  26. package/dist/ui/organisms/entity-form/entity-form.types.js.map +1 -1
  27. package/dist/ui/organisms/entity-table/entity-table.js +2 -2
  28. package/dist/ui/organisms/entity-table/entity-table.js.map +1 -1
  29. package/dist/ui/organisms/wizard/wizard.d.ts +1 -2
  30. package/dist/ui/organisms/wizard/wizard.d.ts.map +1 -1
  31. package/dist/ui/organisms/wizard/wizard.js +2 -3
  32. package/dist/ui/organisms/wizard/wizard.js.map +1 -1
  33. package/dist/ui/organisms/wizard/wizard.types.d.ts +2 -2
  34. package/dist/ui/organisms/wizard/wizard.types.js.map +1 -1
  35. package/dist/ui/utils/active-section/active-section.d.ts +1 -1
  36. package/dist/ui/utils/active-section/active-section.js +1 -1
  37. package/dist/ui/utils/active-section/active-section.js.map +1 -1
  38. package/dist/ui/utils/keep-section/keep-section.d.ts +1 -1
  39. package/dist/ui/utils/keep-section/keep-section.js +1 -1
  40. package/dist/ui/utils/keep-section/keep-section.js.map +1 -1
  41. package/package.json +5 -5
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/ui/organisms/wizard/wizard.tsx"],"sourcesContent":["'use client';\n\nimport type { WizardStepStatus } from '@entifix/core';\nimport { type ReactNode, useEffect, useRef } from 'react';\n\nimport { useT } from '../../../i18n/index.js';\nimport { Button } from '../../atoms/button/index.js';\nimport { Skeleton } from '../../atoms/skeleton/index.js';\nimport { Text } from '../../atoms/text/index.js';\nimport { Cluster } from '../../layout/cluster/index.js';\nimport { Card } from '../../molecules/card/index.js';\nimport { Stack } from '../../molecules/stack/index.js';\nimport { cn } from '../../utils/cn.js';\nimport type { WizardProps, WizardStepView } from './wizard.types.js';\n\n/** How many placeholder steps stand in when the caller has none yet. */\nconst SKELETON_STEP_COUNT = 4;\n\n/**\n * The status catalog keys, written out rather than composed.\n *\n * `t(\\`wizard.status.${status}\\`)` would work and would be untyped — the\n * augmentation cannot see a template literal, so the only thing left checking\n * it would be `@r10c/i18n-check`, which does not read `.tsx`. Four literals\n * cost nothing and stay compile-checked.\n */\nconst STATUS_KEY = {\n pending: 'wizard.status.pending',\n active: 'wizard.status.active',\n complete: 'wizard.status.complete',\n error: 'wizard.status.error',\n} as const satisfies Record<WizardStepStatus, string>;\n\nconst MARKER_CLASS: Record<WizardStepStatus, string> = {\n pending: 'border-border bg-surface text-content-muted',\n active: 'border-accent bg-accent text-primary-content',\n complete: 'border-accent bg-surface text-accent',\n error: 'border-danger bg-danger-subtle text-danger',\n};\n\nconst LABEL_CLASS: Record<WizardStepStatus, string> = {\n pending: 'text-content-muted',\n active: 'font-medium text-content',\n complete: 'text-content',\n error: 'font-medium text-danger',\n};\n\n/**\n * A guided multi-step flow: the stepper, the active step's body, and the\n * controls that move between them.\n *\n * **Presentational, and controlled from above.** It holds no step state, no\n * draft and no router — `activeStep` is a prop and `onStepChange` reports a\n * click, the same rule that made `hrefFor` a prop, so URL synchronisation stays\n * the shell's job and this control can be told to render step four by a route,\n * a workspace tab, or a Storybook arg without knowing which\n * ([ADR 0045](../../../../../../../docs/adr/0045-the-wizard-a-step-graph-and-a-submit-that-hands-off.md)).\n *\n * ⚠️ **The stepper is an ordered list, not a tablist.** `TabStrip` is the\n * nearest-looking thing in this package and is the wrong precedent: a\n * `role=\"tablist\"` tells assistive technology that the panels are siblings the\n * user may choose between, and a wizard's steps are ordered and gated. So this\n * is `<ol>`/`<li>` with `aria-current=\"step\"`, and each step's status is\n * announced rather than carried by colour alone.\n */\nexport function Wizard({\n steps,\n activeStep,\n title,\n children,\n onNext,\n onFinish,\n onPrevious,\n onStepChange,\n canFinish = false,\n canAdvance = true,\n isSubmitting = false,\n recap,\n onDismissRecap,\n isLoading = false,\n skeleton = true,\n className,\n}: WizardProps) {\n const t = useT();\n const heading = useRef<HTMLHeadingElement>(null);\n const focused = useRef(activeStep);\n\n // Focus moves to the new step's heading, and **only on a change**: doing it on\n // mount would take focus from whatever opened the wizard, which on a route is\n // the link the operator just used.\n useEffect(() => {\n if (focused.current === activeStep) return;\n focused.current = activeStep;\n heading.current?.focus();\n }, [activeStep]);\n\n if (isLoading && skeleton !== false) {\n return (\n <WizardSkeleton\n stepCount={steps.length > 0 ? steps.length : SKELETON_STEP_COUNT}\n placeholder={skeleton === true ? undefined : skeleton}\n className={className}\n />\n );\n }\n\n const position = steps.findIndex(step => step.id === activeStep) + 1;\n\n return (\n <Stack gap=\"m\" className={className}>\n <Stepper\n steps={steps}\n activeStep={activeStep}\n onStepChange={onStepChange}\n />\n\n {recap && (\n <Card data-testid=\"wizard-recap\" className=\"border-l-4 border-l-accent\">\n <Stack gap=\"2xs\">\n <Text as=\"h3\" step={0} weight=\"semibold\">\n {t('wizard.resume.title')}\n </Text>\n {recap}\n {onDismissRecap && (\n <Cluster justify=\"end\">\n <Button variant=\"ghost\" size=\"sm\" onClick={onDismissRecap}>\n {t('wizard.resume.dismiss')}\n </Button>\n </Cluster>\n )}\n </Stack>\n </Card>\n )}\n\n {/* No `Card` around the body: a step brings its own surface — an embedded\n `EntityForm` is already a card — and nesting one inside another reads\n as two panels for one step. */}\n <Stack gap=\"s\">\n <Stack gap=\"3xs\">\n {/*\n A plain heading with a real ref, and `tabIndex={-1}` so script can\n focus it without putting it in the tab order — the target of the\n advance above.\n */}\n <h2\n ref={heading}\n tabIndex={-1}\n className=\"rounded-sm text-step-1 font-semibold text-content focus-ring outline-none\"\n >\n {title}\n </h2>\n {position > 0 && (\n <Text step={-1} tone=\"muted\">\n {t('wizard.stepOf', {\n current: position,\n total: steps.length,\n })}\n </Text>\n )}\n </Stack>\n\n {children}\n </Stack>\n\n <Cluster justify=\"between\">\n <div>\n {onPrevious && (\n <Button\n type=\"button\"\n variant=\"secondary\"\n data-testid=\"wizard-previous\"\n onClick={onPrevious}\n disabled={isSubmitting}\n >\n {t('wizard.previous')}\n </Button>\n )}\n </div>\n {/* Named for the tests and the e2e, because a step may bring controls\n of its own — a table step's pager sits in the same view. */}\n <Button\n type=\"button\"\n data-testid=\"wizard-next\"\n onClick={canFinish ? onFinish : onNext}\n disabled={isSubmitting || !canAdvance}\n >\n {isSubmitting && t('wizard.submitting')}\n {!isSubmitting && canFinish && t('wizard.finish')}\n {!isSubmitting && !canFinish && t('wizard.next')}\n </Button>\n </Cluster>\n </Stack>\n );\n}\n\ninterface StepperProps {\n steps: readonly WizardStepView[];\n activeStep: string;\n onStepChange?: (stepId: string) => void;\n}\n\nfunction Stepper({ steps, activeStep, onStepChange }: StepperProps) {\n const t = useT();\n\n return (\n <ol\n aria-label={t('wizard.steps')}\n className=\"flex flex-wrap items-center gap-x-s gap-y-2xs\"\n >\n {steps.map((step, index) => (\n <li\n key={step.id}\n aria-current={step.id === activeStep ? 'step' : undefined}\n className=\"flex items-center gap-2xs\"\n >\n <StepMarker step={step} index={index} onStepChange={onStepChange} />\n {index < steps.length - 1 && (\n <span aria-hidden=\"true\" className=\"h-px w-s shrink-0 bg-border\" />\n )}\n </li>\n ))}\n </ol>\n );\n}\n\ninterface StepMarkerProps {\n step: WizardStepView;\n index: number;\n onStepChange?: (stepId: string) => void;\n}\n\n/**\n * One step's dot and label.\n *\n * A completed step is a `<button>` and every other step is not, rather than a\n * disabled button throughout: a disabled control is still announced as a\n * control, so a pending step would read as something the operator may activate\n * and cannot, when in truth there is nothing there to activate yet.\n */\nfunction StepMarker({ step, index, onStepChange }: StepMarkerProps) {\n const t = useT();\n const clickable = onStepChange !== undefined && step.status === 'complete';\n\n const body = (\n <>\n <span\n aria-hidden=\"true\"\n className={cn(\n 'size-6 flex shrink-0 items-center justify-center rounded-full border',\n 'text-step-xs font-medium transition duration-200 ease-smooth',\n MARKER_CLASS[step.status],\n )}\n >\n {index + 1}\n </span>\n <span className={cn('text-step-sm', LABEL_CLASS[step.status])}>\n {step.label}\n </span>\n {/*\n Status is announced as well as coloured. A screen reader walks this list\n without seeing the ring, and \"completed\" is the whole point of a stepper.\n */}\n <span className=\"sr-only\">{t(STATUS_KEY[step.status])}</span>\n </>\n );\n\n if (!clickable) {\n return <span className=\"flex items-center gap-3xs\">{body}</span>;\n }\n\n return (\n <button\n type=\"button\"\n onClick={() => onStepChange(step.id)}\n aria-label={t('wizard.goToStep', { label: step.label })}\n className=\"flex items-center gap-3xs rounded-md p-3xs focus-ring hover:bg-surface-elevated\"\n >\n {body}\n </button>\n );\n}\n\ninterface WizardSkeletonProps {\n stepCount: number;\n placeholder?: ReactNode;\n className?: string;\n}\n\n/**\n * The wizard's shape while it is in flight.\n *\n * Sized from the step count it was handed, so the swap to the real stepper\n * shifts nothing — the geometry rule every skeleton in this package follows.\n */\nfunction WizardSkeleton({\n stepCount,\n placeholder,\n className,\n}: WizardSkeletonProps) {\n return (\n <Stack\n gap=\"m\"\n className={className}\n role=\"status\"\n aria-busy=\"true\"\n data-testid=\"wizard-skeleton\"\n >\n <Cluster gap=\"s\">\n {Array.from({ length: stepCount }, (_, index) => (\n <Skeleton key={index} shape=\"line\" className=\"h-6 w-24\" />\n ))}\n </Cluster>\n {placeholder ?? <Skeleton className=\"h-40 w-full\" />}\n </Stack>\n );\n}\n"],"names":["useEffect","useRef","useT","Button","Skeleton","Text","Cluster","Card","Stack","cn","SKELETON_STEP_COUNT","STATUS_KEY","pending","active","complete","error","MARKER_CLASS","LABEL_CLASS","Wizard","steps","activeStep","title","children","onNext","onFinish","onPrevious","onStepChange","canFinish","canAdvance","isSubmitting","recap","onDismissRecap","isLoading","skeleton","className","t","heading","focused","current","focus","WizardSkeleton","stepCount","length","placeholder","undefined","position","findIndex","step","id","gap","Stepper","data-testid","as","weight","justify","variant","size","onClick","h2","ref","tabIndex","tone","total","div","type","disabled","ol","aria-label","map","index","li","aria-current","StepMarker","span","aria-hidden","clickable","status","body","label","button","role","aria-busy","Array","from","_","shape"],"mappings":"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"}
1
+ {"version":3,"sources":["../../../../src/ui/organisms/wizard/wizard.tsx"],"sourcesContent":["'use client';\n\nimport type { WizardStepStatus } from '@entifix/core';\nimport { type ReactNode, useEffect, useRef } from 'react';\n\nimport { useT } from '../../../i18n/index.js';\nimport { Button } from '../../atoms/button/index.js';\nimport { Skeleton } from '../../atoms/skeleton/index.js';\nimport { Text } from '../../atoms/text/index.js';\nimport { Cluster } from '../../layout/cluster/index.js';\nimport { Card } from '../../molecules/card/index.js';\nimport { Stack } from '../../molecules/stack/index.js';\nimport { cn } from '../../utils/cn.js';\nimport type { WizardProps, WizardStepView } from './wizard.types.js';\n\n/** How many placeholder steps stand in when the caller has none yet. */\nconst SKELETON_STEP_COUNT = 4;\n\n/**\n * The status catalog keys, written out rather than composed.\n *\n * `t(\\`wizard.status.${status}\\`)` would work and would be untyped — the\n * augmentation cannot see a template literal, so the only thing left checking\n * it would be a host's catalog check, which typically does not read `.tsx`. Four literals\n * cost nothing and stay compile-checked.\n */\nconst STATUS_KEY = {\n pending: 'wizard.status.pending',\n active: 'wizard.status.active',\n complete: 'wizard.status.complete',\n error: 'wizard.status.error',\n} as const satisfies Record<WizardStepStatus, string>;\n\nconst MARKER_CLASS: Record<WizardStepStatus, string> = {\n pending: 'border-border bg-surface text-content-muted',\n active: 'border-accent bg-accent text-primary-content',\n complete: 'border-accent bg-surface text-accent',\n error: 'border-danger bg-danger-subtle text-danger',\n};\n\nconst LABEL_CLASS: Record<WizardStepStatus, string> = {\n pending: 'text-content-muted',\n active: 'font-medium text-content',\n complete: 'text-content',\n error: 'font-medium text-danger',\n};\n\n/**\n * A guided multi-step flow: the stepper, the active step's body, and the\n * controls that move between them.\n *\n * **Presentational, and controlled from above.** It holds no step state, no\n * draft and no router — `activeStep` is a prop and `onStepChange` reports a\n * click, the same rule that made `hrefFor` a prop, so URL synchronisation stays\n * the shell's job and this control can be told to render step four by a route,\n * a workspace tab, or a Storybook arg without knowing which.\n *\n * ⚠️ **The stepper is an ordered list, not a tablist.** `TabStrip` is the\n * nearest-looking thing in this package and is the wrong precedent: a\n * `role=\"tablist\"` tells assistive technology that the panels are siblings the\n * user may choose between, and a wizard's steps are ordered and gated. So this\n * is `<ol>`/`<li>` with `aria-current=\"step\"`, and each step's status is\n * announced rather than carried by colour alone.\n */\nexport function Wizard({\n steps,\n activeStep,\n title,\n children,\n onNext,\n onFinish,\n onPrevious,\n onStepChange,\n canFinish = false,\n canAdvance = true,\n isSubmitting = false,\n recap,\n onDismissRecap,\n isLoading = false,\n skeleton = true,\n className,\n}: WizardProps) {\n const t = useT();\n const heading = useRef<HTMLHeadingElement>(null);\n const focused = useRef(activeStep);\n\n // Focus moves to the new step's heading, and **only on a change**: doing it on\n // mount would take focus from whatever opened the wizard, which on a route is\n // the link the operator just used.\n useEffect(() => {\n if (focused.current === activeStep) return;\n focused.current = activeStep;\n heading.current?.focus();\n }, [activeStep]);\n\n if (isLoading && skeleton !== false) {\n return (\n <WizardSkeleton\n stepCount={steps.length > 0 ? steps.length : SKELETON_STEP_COUNT}\n placeholder={skeleton === true ? undefined : skeleton}\n className={className}\n />\n );\n }\n\n const position = steps.findIndex(step => step.id === activeStep) + 1;\n\n return (\n <Stack gap=\"m\" className={className}>\n <Stepper\n steps={steps}\n activeStep={activeStep}\n onStepChange={onStepChange}\n />\n\n {recap && (\n <Card data-testid=\"wizard-recap\" className=\"border-l-4 border-l-accent\">\n <Stack gap=\"2xs\">\n <Text as=\"h3\" step={0} weight=\"semibold\">\n {t('wizard.resume.title')}\n </Text>\n {recap}\n {onDismissRecap && (\n <Cluster justify=\"end\">\n <Button variant=\"ghost\" size=\"sm\" onClick={onDismissRecap}>\n {t('wizard.resume.dismiss')}\n </Button>\n </Cluster>\n )}\n </Stack>\n </Card>\n )}\n\n {/* No `Card` around the body: a step brings its own surface — an embedded\n `EntityForm` is already a card — and nesting one inside another reads\n as two panels for one step. */}\n <Stack gap=\"s\">\n <Stack gap=\"3xs\">\n {/*\n A plain heading with a real ref, and `tabIndex={-1}` so script can\n focus it without putting it in the tab order — the target of the\n advance above.\n */}\n <h2\n ref={heading}\n tabIndex={-1}\n className=\"rounded-sm text-step-1 font-semibold text-content focus-ring outline-none\"\n >\n {title}\n </h2>\n {position > 0 && (\n <Text step={-1} tone=\"muted\">\n {t('wizard.stepOf', {\n current: position,\n total: steps.length,\n })}\n </Text>\n )}\n </Stack>\n\n {children}\n </Stack>\n\n <Cluster justify=\"between\">\n <div>\n {onPrevious && (\n <Button\n type=\"button\"\n variant=\"secondary\"\n data-testid=\"wizard-previous\"\n onClick={onPrevious}\n disabled={isSubmitting}\n >\n {t('wizard.previous')}\n </Button>\n )}\n </div>\n {/* Named for the tests and the e2e, because a step may bring controls\n of its own — a table step's pager sits in the same view. */}\n <Button\n type=\"button\"\n data-testid=\"wizard-next\"\n onClick={canFinish ? onFinish : onNext}\n disabled={isSubmitting || !canAdvance}\n >\n {isSubmitting && t('wizard.submitting')}\n {!isSubmitting && canFinish && t('wizard.finish')}\n {!isSubmitting && !canFinish && t('wizard.next')}\n </Button>\n </Cluster>\n </Stack>\n );\n}\n\ninterface StepperProps {\n steps: readonly WizardStepView[];\n activeStep: string;\n onStepChange?: (stepId: string) => void;\n}\n\nfunction Stepper({ steps, activeStep, onStepChange }: StepperProps) {\n const t = useT();\n\n return (\n <ol\n aria-label={t('wizard.steps')}\n className=\"flex flex-wrap items-center gap-x-s gap-y-2xs\"\n >\n {steps.map((step, index) => (\n <li\n key={step.id}\n aria-current={step.id === activeStep ? 'step' : undefined}\n className=\"flex items-center gap-2xs\"\n >\n <StepMarker step={step} index={index} onStepChange={onStepChange} />\n {index < steps.length - 1 && (\n <span aria-hidden=\"true\" className=\"h-px w-s shrink-0 bg-border\" />\n )}\n </li>\n ))}\n </ol>\n );\n}\n\ninterface StepMarkerProps {\n step: WizardStepView;\n index: number;\n onStepChange?: (stepId: string) => void;\n}\n\n/**\n * One step's dot and label.\n *\n * A completed step is a `<button>` and every other step is not, rather than a\n * disabled button throughout: a disabled control is still announced as a\n * control, so a pending step would read as something the operator may activate\n * and cannot, when in truth there is nothing there to activate yet.\n */\nfunction StepMarker({ step, index, onStepChange }: StepMarkerProps) {\n const t = useT();\n const clickable = onStepChange !== undefined && step.status === 'complete';\n\n const body = (\n <>\n <span\n aria-hidden=\"true\"\n className={cn(\n 'size-6 flex shrink-0 items-center justify-center rounded-full border',\n 'text-step-xs font-medium transition duration-200 ease-smooth',\n MARKER_CLASS[step.status],\n )}\n >\n {index + 1}\n </span>\n <span className={cn('text-step-sm', LABEL_CLASS[step.status])}>\n {step.label}\n </span>\n {/*\n Status is announced as well as coloured. A screen reader walks this list\n without seeing the ring, and \"completed\" is the whole point of a stepper.\n */}\n <span className=\"sr-only\">{t(STATUS_KEY[step.status])}</span>\n </>\n );\n\n if (!clickable) {\n return <span className=\"flex items-center gap-3xs\">{body}</span>;\n }\n\n return (\n <button\n type=\"button\"\n onClick={() => onStepChange(step.id)}\n aria-label={t('wizard.goToStep', { label: step.label })}\n className=\"flex items-center gap-3xs rounded-md p-3xs focus-ring hover:bg-surface-elevated\"\n >\n {body}\n </button>\n );\n}\n\ninterface WizardSkeletonProps {\n stepCount: number;\n placeholder?: ReactNode;\n className?: string;\n}\n\n/**\n * The wizard's shape while it is in flight.\n *\n * Sized from the step count it was handed, so the swap to the real stepper\n * shifts nothing — the geometry rule every skeleton in this package follows.\n */\nfunction WizardSkeleton({\n stepCount,\n placeholder,\n className,\n}: WizardSkeletonProps) {\n return (\n <Stack\n gap=\"m\"\n className={className}\n role=\"status\"\n aria-busy=\"true\"\n data-testid=\"wizard-skeleton\"\n >\n <Cluster gap=\"s\">\n {Array.from({ length: stepCount }, (_, index) => (\n <Skeleton key={index} shape=\"line\" className=\"h-6 w-24\" />\n ))}\n </Cluster>\n {placeholder ?? <Skeleton className=\"h-40 w-full\" />}\n </Stack>\n );\n}\n"],"names":["useEffect","useRef","useT","Button","Skeleton","Text","Cluster","Card","Stack","cn","SKELETON_STEP_COUNT","STATUS_KEY","pending","active","complete","error","MARKER_CLASS","LABEL_CLASS","Wizard","steps","activeStep","title","children","onNext","onFinish","onPrevious","onStepChange","canFinish","canAdvance","isSubmitting","recap","onDismissRecap","isLoading","skeleton","className","t","heading","focused","current","focus","WizardSkeleton","stepCount","length","placeholder","undefined","position","findIndex","step","id","gap","Stepper","data-testid","as","weight","justify","variant","size","onClick","h2","ref","tabIndex","tone","total","div","type","disabled","ol","aria-label","map","index","li","aria-current","StepMarker","span","aria-hidden","clickable","status","body","label","button","role","aria-busy","Array","from","_","shape"],"mappings":"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"}
@@ -54,7 +54,7 @@ export interface WizardProps {
54
54
  * The submit is in flight.
55
55
  *
56
56
  * It stays in flight: `onFinish` hands off and returns before the write is
57
- * terminal ([ADR 0043](../../../../../../../docs/adr/0043-the-optimistic-mutation-contract.md)),
57
+ * terminal,
58
58
  * so what this disables is a second submit — it is not a spinner waiting for
59
59
  * a resolution the caller never gets.
60
60
  */
@@ -64,7 +64,7 @@ export interface WizardProps {
64
64
  *
65
65
  * Persistence gives "continue" for free; what it does not give is remembering
66
66
  * what you had answered, which is the known failure of a long flow restored
67
- * mid-way ([#111](https://github.com/r10c-technologies/r10c/issues/111)).
67
+ * mid-way.
68
68
  */
69
69
  recap?: ReactNode;
70
70
  /** Dismiss the recap. Omit and it has no dismiss control. */
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/ui/organisms/wizard/wizard.types.ts"],"sourcesContent":["import type { WizardStepStatus } from '@entifix/core';\nimport type { ReactNode } from 'react';\n\n/**\n * One step as the stepper shows it.\n *\n * `label` is **already-resolved copy**, not a catalog key, which is the rule\n * `ConfirmDialog`'s `title`/`message` already follow: a step's name belongs to\n * the domain that declared the wizard, so its copy is `shell:`-namespaced and\n * this package cannot type-check the key.\n */\nexport interface WizardStepView {\n readonly id: string;\n readonly label: string;\n readonly status: WizardStepStatus;\n}\n\nexport interface WizardProps {\n /**\n * The steps on the path this flow is taking, in order — the projection\n * `stepStatuses` returns, not the definition's whole step list.\n *\n * A graph's declared steps include branches not taken, so rendering all of\n * them promises an operator who chose to start blank a step they will never\n * see.\n */\n steps: readonly WizardStepView[];\n\n /** Which step's body `children` is. Controlled; the shell owns the URL. */\n activeStep: string;\n\n /**\n * The active step's heading.\n *\n * It is also the **focus target on advance**: moving focus to the top of the\n * new step is what makes a keyboard or screen-reader user land somewhere\n * meaningful instead of wherever the previous step's Next button was.\n */\n title: string;\n\n /** The active step's body. */\n children: ReactNode;\n\n onNext: () => void;\n onFinish: () => void;\n /** Absent at the entry step, where there is nowhere to go back to. */\n onPrevious?: () => void;\n /**\n * A completed step was clicked. Omit and the stepper is read-only.\n *\n * Only `complete` steps are clickable; a `pending` one would skip the per-step\n * submit that validates everything in between.\n */\n onStepChange?: (stepId: string) => void;\n\n /** The active step ends the flow, so the primary button reads Finalizar. */\n canFinish?: boolean;\n /** Advancing is blocked — a required answer is missing on this step. */\n canAdvance?: boolean;\n\n /**\n * The submit is in flight.\n *\n * It stays in flight: `onFinish` hands off and returns before the write is\n * terminal ([ADR 0043](../../../../../../../docs/adr/0043-the-optimistic-mutation-contract.md)),\n * so what this disables is a second submit — it is not a spinner waiting for\n * a resolution the caller never gets.\n */\n isSubmitting?: boolean;\n\n /**\n * What was already decided, shown when a half-finished wizard is resumed.\n *\n * Persistence gives \"continue\" for free; what it does not give is remembering\n * what you had answered, which is the known failure of a long flow restored\n * mid-way ([#111](https://github.com/r10c-technologies/r10c/issues/111)).\n */\n recap?: ReactNode;\n /** Dismiss the recap. Omit and it has no dismiss control. */\n onDismissRecap?: () => void;\n\n isLoading?: boolean;\n /**\n * What holds the wizard's shape while it is in flight.\n *\n * `true` (the default) renders the built-in placeholder, sized from the step\n * count it was given so the swap to the real stepper shifts nothing. A node\n * replaces that default; `false` renders no placeholder at all.\n */\n skeleton?: boolean | ReactNode;\n\n className?: string;\n}\n"],"names":[],"mappings":"AAiBA,WA2EC"}
1
+ {"version":3,"sources":["../../../../src/ui/organisms/wizard/wizard.types.ts"],"sourcesContent":["import type { WizardStepStatus } from '@entifix/core';\nimport type { ReactNode } from 'react';\n\n/**\n * One step as the stepper shows it.\n *\n * `label` is **already-resolved copy**, not a catalog key, which is the rule\n * `ConfirmDialog`'s `title`/`message` already follow: a step's name belongs to\n * the domain that declared the wizard, so its copy is `shell:`-namespaced and\n * this package cannot type-check the key.\n */\nexport interface WizardStepView {\n readonly id: string;\n readonly label: string;\n readonly status: WizardStepStatus;\n}\n\nexport interface WizardProps {\n /**\n * The steps on the path this flow is taking, in order — the projection\n * `stepStatuses` returns, not the definition's whole step list.\n *\n * A graph's declared steps include branches not taken, so rendering all of\n * them promises an operator who chose to start blank a step they will never\n * see.\n */\n steps: readonly WizardStepView[];\n\n /** Which step's body `children` is. Controlled; the shell owns the URL. */\n activeStep: string;\n\n /**\n * The active step's heading.\n *\n * It is also the **focus target on advance**: moving focus to the top of the\n * new step is what makes a keyboard or screen-reader user land somewhere\n * meaningful instead of wherever the previous step's Next button was.\n */\n title: string;\n\n /** The active step's body. */\n children: ReactNode;\n\n onNext: () => void;\n onFinish: () => void;\n /** Absent at the entry step, where there is nowhere to go back to. */\n onPrevious?: () => void;\n /**\n * A completed step was clicked. Omit and the stepper is read-only.\n *\n * Only `complete` steps are clickable; a `pending` one would skip the per-step\n * submit that validates everything in between.\n */\n onStepChange?: (stepId: string) => void;\n\n /** The active step ends the flow, so the primary button reads Finalizar. */\n canFinish?: boolean;\n /** Advancing is blocked — a required answer is missing on this step. */\n canAdvance?: boolean;\n\n /**\n * The submit is in flight.\n *\n * It stays in flight: `onFinish` hands off and returns before the write is\n * terminal,\n * so what this disables is a second submit — it is not a spinner waiting for\n * a resolution the caller never gets.\n */\n isSubmitting?: boolean;\n\n /**\n * What was already decided, shown when a half-finished wizard is resumed.\n *\n * Persistence gives \"continue\" for free; what it does not give is remembering\n * what you had answered, which is the known failure of a long flow restored\n * mid-way.\n */\n recap?: ReactNode;\n /** Dismiss the recap. Omit and it has no dismiss control. */\n onDismissRecap?: () => void;\n\n isLoading?: boolean;\n /**\n * What holds the wizard's shape while it is in flight.\n *\n * `true` (the default) renders the built-in placeholder, sized from the step\n * count it was given so the swap to the real stepper shifts nothing. A node\n * replaces that default; `false` renders no placeholder at all.\n */\n skeleton?: boolean | ReactNode;\n\n className?: string;\n}\n"],"names":[],"mappings":"AAiBA,WA2EC"}
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Marks the nav anchor of the section in view with `aria-current`: an
3
- * enhancement over anchors that already work (ADR 0008). Renders nothing.
3
+ * enhancement over anchors that already work. Renders nothing.
4
4
  *
5
5
  * A section counts as in view while it crosses the band just above the
6
6
  * viewport's middle; over the hero, none does.
@@ -2,7 +2,7 @@
2
2
  import { useEffect } from 'react';
3
3
  /**
4
4
  * Marks the nav anchor of the section in view with `aria-current`: an
5
- * enhancement over anchors that already work (ADR 0008). Renders nothing.
5
+ * enhancement over anchors that already work. Renders nothing.
6
6
  *
7
7
  * A section counts as in view while it crosses the band just above the
8
8
  * viewport's middle; over the hero, none does.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/ui/utils/active-section/active-section.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\n\n/**\n * Marks the nav anchor of the section in view with `aria-current`: an\n * enhancement over anchors that already work (ADR 0008). Renders nothing.\n *\n * A section counts as in view while it crosses the band just above the\n * viewport's middle; over the hero, none does.\n */\nexport function ActiveSection({ sections }: { sections: readonly string[] }) {\n useEffect(() => {\n const links = new Map(\n sections.map(id => [\n id,\n document.querySelectorAll<HTMLAnchorElement>(`a[data-section=\"${id}\"]`),\n ]),\n );\n const visible = new Set<string>();\n const mark = () => {\n const current = sections.find(id => visible.has(id));\n for (const [id, anchors] of links) {\n for (const anchor of anchors) {\n if (id === current) anchor.setAttribute('aria-current', 'location');\n else anchor.removeAttribute('aria-current');\n }\n }\n };\n const observer = new IntersectionObserver(\n entries => {\n for (const entry of entries) {\n if (entry.isIntersecting) visible.add(entry.target.id);\n else visible.delete(entry.target.id);\n }\n mark();\n },\n { rootMargin: '-40% 0px -55% 0px' },\n );\n for (const id of sections) {\n const section = document.getElementById(id);\n if (section) observer.observe(section);\n }\n return () => observer.disconnect();\n }, [sections]);\n return null;\n}\n"],"names":["useEffect","ActiveSection","sections","links","Map","map","id","document","querySelectorAll","visible","Set","mark","current","find","has","anchors","anchor","setAttribute","removeAttribute","observer","IntersectionObserver","entries","entry","isIntersecting","add","target","delete","rootMargin","section","getElementById","observe","disconnect"],"mappings":"AAAA;AAEA,SAASA,SAAS,QAAQ,QAAQ;AAElC;;;;;;CAMC,GACD,OAAO,SAASC,cAAc,EAAEC,QAAQ,EAAmC;IACzEF,UAAU;QACR,MAAMG,QAAQ,IAAIC,IAChBF,SAASG,GAAG,CAACC,CAAAA,KAAM;gBACjBA;gBACAC,SAASC,gBAAgB,CAAoB,CAAC,gBAAgB,EAAEF,GAAG,EAAE,CAAC;aACvE;QAEH,MAAMG,UAAU,IAAIC;QACpB,MAAMC,OAAO;YACX,MAAMC,UAAUV,SAASW,IAAI,CAACP,CAAAA,KAAMG,QAAQK,GAAG,CAACR;YAChD,KAAK,MAAM,CAACA,IAAIS,QAAQ,IAAIZ,MAAO;gBACjC,KAAK,MAAMa,UAAUD,QAAS;oBAC5B,IAAIT,OAAOM,SAASI,OAAOC,YAAY,CAAC,gBAAgB;yBACnDD,OAAOE,eAAe,CAAC;gBAC9B;YACF;QACF;QACA,MAAMC,WAAW,IAAIC,qBACnBC,CAAAA;YACE,KAAK,MAAMC,SAASD,QAAS;gBAC3B,IAAIC,MAAMC,cAAc,EAAEd,QAAQe,GAAG,CAACF,MAAMG,MAAM,CAACnB,EAAE;qBAChDG,QAAQiB,MAAM,CAACJ,MAAMG,MAAM,CAACnB,EAAE;YACrC;YACAK;QACF,GACA;YAAEgB,YAAY;QAAoB;QAEpC,KAAK,MAAMrB,MAAMJ,SAAU;YACzB,MAAM0B,UAAUrB,SAASsB,cAAc,CAACvB;YACxC,IAAIsB,SAAST,SAASW,OAAO,CAACF;QAChC;QACA,OAAO,IAAMT,SAASY,UAAU;IAClC,GAAG;QAAC7B;KAAS;IACb,OAAO;AACT"}
1
+ {"version":3,"sources":["../../../../src/ui/utils/active-section/active-section.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\n\n/**\n * Marks the nav anchor of the section in view with `aria-current`: an\n * enhancement over anchors that already work. Renders nothing.\n *\n * A section counts as in view while it crosses the band just above the\n * viewport's middle; over the hero, none does.\n */\nexport function ActiveSection({ sections }: { sections: readonly string[] }) {\n useEffect(() => {\n const links = new Map(\n sections.map(id => [\n id,\n document.querySelectorAll<HTMLAnchorElement>(`a[data-section=\"${id}\"]`),\n ]),\n );\n const visible = new Set<string>();\n const mark = () => {\n const current = sections.find(id => visible.has(id));\n for (const [id, anchors] of links) {\n for (const anchor of anchors) {\n if (id === current) anchor.setAttribute('aria-current', 'location');\n else anchor.removeAttribute('aria-current');\n }\n }\n };\n const observer = new IntersectionObserver(\n entries => {\n for (const entry of entries) {\n if (entry.isIntersecting) visible.add(entry.target.id);\n else visible.delete(entry.target.id);\n }\n mark();\n },\n { rootMargin: '-40% 0px -55% 0px' },\n );\n for (const id of sections) {\n const section = document.getElementById(id);\n if (section) observer.observe(section);\n }\n return () => observer.disconnect();\n }, [sections]);\n return null;\n}\n"],"names":["useEffect","ActiveSection","sections","links","Map","map","id","document","querySelectorAll","visible","Set","mark","current","find","has","anchors","anchor","setAttribute","removeAttribute","observer","IntersectionObserver","entries","entry","isIntersecting","add","target","delete","rootMargin","section","getElementById","observe","disconnect"],"mappings":"AAAA;AAEA,SAASA,SAAS,QAAQ,QAAQ;AAElC;;;;;;CAMC,GACD,OAAO,SAASC,cAAc,EAAEC,QAAQ,EAAmC;IACzEF,UAAU;QACR,MAAMG,QAAQ,IAAIC,IAChBF,SAASG,GAAG,CAACC,CAAAA,KAAM;gBACjBA;gBACAC,SAASC,gBAAgB,CAAoB,CAAC,gBAAgB,EAAEF,GAAG,EAAE,CAAC;aACvE;QAEH,MAAMG,UAAU,IAAIC;QACpB,MAAMC,OAAO;YACX,MAAMC,UAAUV,SAASW,IAAI,CAACP,CAAAA,KAAMG,QAAQK,GAAG,CAACR;YAChD,KAAK,MAAM,CAACA,IAAIS,QAAQ,IAAIZ,MAAO;gBACjC,KAAK,MAAMa,UAAUD,QAAS;oBAC5B,IAAIT,OAAOM,SAASI,OAAOC,YAAY,CAAC,gBAAgB;yBACnDD,OAAOE,eAAe,CAAC;gBAC9B;YACF;QACF;QACA,MAAMC,WAAW,IAAIC,qBACnBC,CAAAA;YACE,KAAK,MAAMC,SAASD,QAAS;gBAC3B,IAAIC,MAAMC,cAAc,EAAEd,QAAQe,GAAG,CAACF,MAAMG,MAAM,CAACnB,EAAE;qBAChDG,QAAQiB,MAAM,CAACJ,MAAMG,MAAM,CAACnB,EAAE;YACrC;YACAK;QACF,GACA;YAAEgB,YAAY;QAAoB;QAEpC,KAAK,MAAMrB,MAAMJ,SAAU;YACzB,MAAM0B,UAAUrB,SAASsB,cAAc,CAACvB;YACxC,IAAIsB,SAAST,SAASW,OAAO,CAACF;QAChC;QACA,OAAO,IAAMT,SAASY,UAAU;IAClC,GAAG;QAAC7B;KAAS;IACb,OAAO;AACT"}
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Carries the section in view across the language switch (ADR 0008): a
2
+ * Carries the section in view across the language switch: a
3
3
  * fragment never reaches a server, so the switch's `href` is given one as it
4
4
  * is followed. Without scripting the visitor lands at the top of the other
5
5
  * locale. Renders nothing.
@@ -1,7 +1,7 @@
1
1
  'use client';
2
2
  import { useEffect } from 'react';
3
3
  /**
4
- * Carries the section in view across the language switch (ADR 0008): a
4
+ * Carries the section in view across the language switch: a
5
5
  * fragment never reaches a server, so the switch's `href` is given one as it
6
6
  * is followed. Without scripting the visitor lands at the top of the other
7
7
  * locale. Renders nothing.
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/ui/utils/keep-section/keep-section.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\n\n/**\n * Carries the section in view across the language switch (ADR 0008): a\n * fragment never reaches a server, so the switch's `href` is given one as it\n * is followed. Without scripting the visitor lands at the top of the other\n * locale. Renders nothing.\n */\nexport function KeepSection() {\n useEffect(() => {\n const follow = (event: MouseEvent) => {\n const link =\n event.target instanceof Element\n ? event.target.closest<HTMLAnchorElement>('a[data-keep-section]')\n : null;\n if (!link) return;\n const current = document.querySelector('a[data-section][aria-current]');\n const section = current?.getAttribute('data-section');\n link.hash = section ? section : window.location.hash;\n };\n // Capture: the `href` must be set before the browser follows it.\n document.addEventListener('click', follow, true);\n return () => document.removeEventListener('click', follow, true);\n }, []);\n return null;\n}\n"],"names":["useEffect","KeepSection","follow","event","link","target","Element","closest","current","document","querySelector","section","getAttribute","hash","window","location","addEventListener","removeEventListener"],"mappings":"AAAA;AAEA,SAASA,SAAS,QAAQ,QAAQ;AAElC;;;;;CAKC,GACD,OAAO,SAASC;IACdD,UAAU;QACR,MAAME,SAAS,CAACC;YACd,MAAMC,OACJD,MAAME,MAAM,YAAYC,UACpBH,MAAME,MAAM,CAACE,OAAO,CAAoB,0BACxC;YACN,IAAI,CAACH,MAAM;YACX,MAAMI,UAAUC,SAASC,aAAa,CAAC;YACvC,MAAMC,UAAUH,2BAAAA,QAASI,YAAY,CAAC;YACtCR,KAAKS,IAAI,GAAGF,UAAUA,UAAUG,OAAOC,QAAQ,CAACF,IAAI;QACtD;QACA,iEAAiE;QACjEJ,SAASO,gBAAgB,CAAC,SAASd,QAAQ;QAC3C,OAAO,IAAMO,SAASQ,mBAAmB,CAAC,SAASf,QAAQ;IAC7D,GAAG,EAAE;IACL,OAAO;AACT"}
1
+ {"version":3,"sources":["../../../../src/ui/utils/keep-section/keep-section.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect } from 'react';\n\n/**\n * Carries the section in view across the language switch: a\n * fragment never reaches a server, so the switch's `href` is given one as it\n * is followed. Without scripting the visitor lands at the top of the other\n * locale. Renders nothing.\n */\nexport function KeepSection() {\n useEffect(() => {\n const follow = (event: MouseEvent) => {\n const link =\n event.target instanceof Element\n ? event.target.closest<HTMLAnchorElement>('a[data-keep-section]')\n : null;\n if (!link) return;\n const current = document.querySelector('a[data-section][aria-current]');\n const section = current?.getAttribute('data-section');\n link.hash = section ? section : window.location.hash;\n };\n // Capture: the `href` must be set before the browser follows it.\n document.addEventListener('click', follow, true);\n return () => document.removeEventListener('click', follow, true);\n }, []);\n return null;\n}\n"],"names":["useEffect","KeepSection","follow","event","link","target","Element","closest","current","document","querySelector","section","getAttribute","hash","window","location","addEventListener","removeEventListener"],"mappings":"AAAA;AAEA,SAASA,SAAS,QAAQ,QAAQ;AAElC;;;;;CAKC,GACD,OAAO,SAASC;IACdD,UAAU;QACR,MAAME,SAAS,CAACC;YACd,MAAMC,OACJD,MAAME,MAAM,YAAYC,UACpBH,MAAME,MAAM,CAACE,OAAO,CAAoB,0BACxC;YACN,IAAI,CAACH,MAAM;YACX,MAAMI,UAAUC,SAASC,aAAa,CAAC;YACvC,MAAMC,UAAUH,2BAAAA,QAASI,YAAY,CAAC;YACtCR,KAAKS,IAAI,GAAGF,UAAUA,UAAUG,OAAOC,QAAQ,CAACF,IAAI;QACtD;QACA,iEAAiE;QACjEJ,SAASO,gBAAgB,CAAC,SAASd,QAAQ;QAC3C,OAAO,IAAMO,SAASQ,mBAAmB,CAAC,SAASf,QAAQ;IAC7D,GAAG,EAAE;IACL,OAAO;AACT"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@entifix/react-controls",
3
- "version": "0.5.0",
3
+ "version": "0.6.0",
4
4
  "description": "Entity-agnostic React UI — table, form, filters, wizard, command palette — rendered from entity metadata, plus layout primitives.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -19,7 +19,7 @@
19
19
  "@headlessui/react": "^2.2.10",
20
20
  "class-variance-authority": "^0.7.1",
21
21
  "idb": "^8.0.4",
22
- "@entifix/core": "0.5.0"
22
+ "@entifix/core": "0.6.0"
23
23
  },
24
24
  "exports": {
25
25
  "./package.json": "./package.json",
@@ -53,8 +53,8 @@
53
53
  "react": "19.3.1",
54
54
  "react-i18next": "17.1.0",
55
55
  "storybook": "^10.6.2",
56
- "@entifix/i18n": "0.5.0",
57
- "@entifix/style": "0.5.0"
56
+ "@entifix/i18n": "0.6.0",
57
+ "@entifix/style": "0.6.0"
58
58
  },
59
59
  "nx": {
60
60
  "tags": [
@@ -97,7 +97,7 @@
97
97
  "i18next": ">=26 <27",
98
98
  "react": ">=19 <20",
99
99
  "react-i18next": ">=17 <18",
100
- "@entifix/i18n": "0.5.0"
100
+ "@entifix/i18n": "0.6.0"
101
101
  },
102
102
  "peerDependenciesMeta": {
103
103
  "@entifix/i18n": {