@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.
Files changed (203) hide show
  1. package/README.md +8 -0
  2. package/dist/i18n/catalog/en.d.ts.map +1 -1
  3. package/dist/i18n/catalog/en.js +55 -9
  4. package/dist/i18n/catalog/en.js.map +1 -1
  5. package/dist/i18n/catalog/es.d.ts +64 -5
  6. package/dist/i18n/catalog/es.d.ts.map +1 -1
  7. package/dist/i18n/catalog/es.js +61 -9
  8. package/dist/i18n/catalog/es.js.map +1 -1
  9. package/dist/i18n/catalog/index.d.ts +102 -10
  10. package/dist/i18n/catalog/index.d.ts.map +1 -1
  11. package/dist/i18n/entity-labels.d.ts +14 -0
  12. package/dist/i18n/entity-labels.d.ts.map +1 -1
  13. package/dist/i18n/entity-labels.js +15 -0
  14. package/dist/i18n/entity-labels.js.map +1 -1
  15. package/dist/i18n/i18n-context.js +2 -2
  16. package/dist/i18n/i18n-context.js.map +1 -1
  17. package/dist/index.d.ts +6 -2
  18. package/dist/index.d.ts.map +1 -1
  19. package/dist/index.js +6 -2
  20. package/dist/index.js.map +1 -1
  21. package/dist/primitives.d.ts +0 -1
  22. package/dist/primitives.d.ts.map +1 -1
  23. package/dist/primitives.js +0 -1
  24. package/dist/primitives.js.map +1 -1
  25. package/dist/theme/index.d.ts +1 -0
  26. package/dist/theme/index.d.ts.map +1 -1
  27. package/dist/theme/index.js +1 -0
  28. package/dist/theme/index.js.map +1 -1
  29. package/dist/theme/theme-context.d.ts +15 -3
  30. package/dist/theme/theme-context.d.ts.map +1 -1
  31. package/dist/theme/theme-context.js +49 -23
  32. package/dist/theme/theme-context.js.map +1 -1
  33. package/dist/theme/theme-cookie.d.ts +9 -0
  34. package/dist/theme/theme-cookie.d.ts.map +1 -0
  35. package/dist/theme/theme-cookie.js +8 -0
  36. package/dist/theme/theme-cookie.js.map +1 -0
  37. package/dist/theme/theme-script.d.ts +8 -6
  38. package/dist/theme/theme-script.d.ts.map +1 -1
  39. package/dist/theme/theme-script.js +21 -12
  40. package/dist/theme/theme-script.js.map +1 -1
  41. package/dist/ui/actions/action-surfaces.d.ts +1 -1
  42. package/dist/ui/actions/action-surfaces.js +1 -1
  43. package/dist/ui/actions/action-surfaces.js.map +1 -1
  44. package/dist/ui/actions/error-actions.d.ts +32 -0
  45. package/dist/ui/actions/error-actions.d.ts.map +1 -0
  46. package/dist/ui/actions/error-actions.js +40 -0
  47. package/dist/ui/actions/error-actions.js.map +1 -0
  48. package/dist/ui/actions/index.d.ts +3 -0
  49. package/dist/ui/actions/index.d.ts.map +1 -1
  50. package/dist/ui/actions/index.js +3 -0
  51. package/dist/ui/actions/index.js.map +1 -1
  52. package/dist/ui/actions/shortcut.d.ts +13 -0
  53. package/dist/ui/actions/shortcut.d.ts.map +1 -0
  54. package/dist/ui/actions/shortcut.js +18 -0
  55. package/dist/ui/actions/shortcut.js.map +1 -0
  56. package/dist/ui/actions/use-verb-invoker.d.ts +55 -0
  57. package/dist/ui/actions/use-verb-invoker.d.ts.map +1 -0
  58. package/dist/ui/actions/use-verb-invoker.js +98 -0
  59. package/dist/ui/actions/use-verb-invoker.js.map +1 -0
  60. package/dist/ui/atoms/cell-value/cell-value.d.ts.map +1 -1
  61. package/dist/ui/atoms/cell-value/cell-value.js +28 -14
  62. package/dist/ui/atoms/cell-value/cell-value.js.map +1 -1
  63. package/dist/ui/atoms/field-control/field-control.d.ts +1 -0
  64. package/dist/ui/atoms/field-control/field-control.d.ts.map +1 -1
  65. package/dist/ui/atoms/field-control/field-control.js +2 -0
  66. package/dist/ui/atoms/field-control/field-control.js.map +1 -1
  67. package/dist/ui/atoms/number-input/quantity-input.d.ts +8 -1
  68. package/dist/ui/atoms/number-input/quantity-input.d.ts.map +1 -1
  69. package/dist/ui/atoms/number-input/quantity-input.js +2 -1
  70. package/dist/ui/atoms/number-input/quantity-input.js.map +1 -1
  71. package/dist/ui/atoms/table/table.d.ts +12 -3
  72. package/dist/ui/atoms/table/table.d.ts.map +1 -1
  73. package/dist/ui/atoms/table/table.js +59 -10
  74. package/dist/ui/atoms/table/table.js.map +1 -1
  75. package/dist/ui/atoms/text/text.d.ts +2 -2
  76. package/dist/ui/atoms/text/text.d.ts.map +1 -1
  77. package/dist/ui/atoms/text/text.js.map +1 -1
  78. package/dist/ui/molecules/action-bar/action-bar.d.ts +76 -0
  79. package/dist/ui/molecules/action-bar/action-bar.d.ts.map +1 -0
  80. package/dist/ui/molecules/action-bar/action-bar.js +218 -0
  81. package/dist/ui/molecules/action-bar/action-bar.js.map +1 -0
  82. package/dist/ui/molecules/action-bar/index.d.ts +2 -0
  83. package/dist/ui/molecules/action-bar/index.d.ts.map +1 -0
  84. package/dist/ui/molecules/action-bar/index.js +3 -0
  85. package/dist/ui/molecules/action-bar/index.js.map +1 -0
  86. package/dist/ui/molecules/bulk-action-bar/bulk-action-bar.d.ts.map +1 -1
  87. package/dist/ui/molecules/bulk-action-bar/bulk-action-bar.js +8 -10
  88. package/dist/ui/molecules/bulk-action-bar/bulk-action-bar.js.map +1 -1
  89. package/dist/ui/molecules/bulk-result/bulk-result.d.ts +3 -3
  90. package/dist/ui/molecules/bulk-result/bulk-result.js +4 -4
  91. package/dist/ui/molecules/bulk-result/bulk-result.js.map +1 -1
  92. package/dist/ui/molecules/confirm-dialog/confirm-dialog.d.ts +24 -9
  93. package/dist/ui/molecules/confirm-dialog/confirm-dialog.d.ts.map +1 -1
  94. package/dist/ui/molecules/confirm-dialog/confirm-dialog.js +36 -11
  95. package/dist/ui/molecules/confirm-dialog/confirm-dialog.js.map +1 -1
  96. package/dist/ui/molecules/entity-record-card/entity-record-card.d.ts +33 -20
  97. package/dist/ui/molecules/entity-record-card/entity-record-card.d.ts.map +1 -1
  98. package/dist/ui/molecules/entity-record-card/entity-record-card.js +53 -20
  99. package/dist/ui/molecules/entity-record-card/entity-record-card.js.map +1 -1
  100. package/dist/ui/molecules/filter-builder/filter-builder.d.ts +8 -2
  101. package/dist/ui/molecules/filter-builder/filter-builder.d.ts.map +1 -1
  102. package/dist/ui/molecules/filter-builder/filter-builder.js +14 -5
  103. package/dist/ui/molecules/filter-builder/filter-builder.js.map +1 -1
  104. package/dist/ui/molecules/filter-chips/filter-chips.d.ts +16 -0
  105. package/dist/ui/molecules/filter-chips/filter-chips.d.ts.map +1 -0
  106. package/dist/ui/molecules/filter-chips/filter-chips.js +73 -0
  107. package/dist/ui/molecules/filter-chips/filter-chips.js.map +1 -0
  108. package/dist/ui/molecules/filter-chips/index.d.ts +2 -0
  109. package/dist/ui/molecules/filter-chips/index.d.ts.map +1 -0
  110. package/dist/ui/molecules/filter-chips/index.js +3 -0
  111. package/dist/ui/molecules/filter-chips/index.js.map +1 -0
  112. package/dist/ui/molecules/menu/menu.d.ts +11 -1
  113. package/dist/ui/molecules/menu/menu.d.ts.map +1 -1
  114. package/dist/ui/molecules/menu/menu.js +62 -2
  115. package/dist/ui/molecules/menu/menu.js.map +1 -1
  116. package/dist/ui/molecules/pagination/pagination.d.ts +5 -0
  117. package/dist/ui/molecules/pagination/pagination.d.ts.map +1 -1
  118. package/dist/ui/molecules/pagination/pagination.js +35 -18
  119. package/dist/ui/molecules/pagination/pagination.js.map +1 -1
  120. package/dist/ui/molecules/region-state/index.d.ts +2 -0
  121. package/dist/ui/molecules/region-state/index.d.ts.map +1 -0
  122. package/dist/ui/molecules/region-state/index.js +3 -0
  123. package/dist/ui/molecules/region-state/index.js.map +1 -0
  124. package/dist/ui/molecules/region-state/region-state.d.ts +42 -0
  125. package/dist/ui/molecules/region-state/region-state.d.ts.map +1 -0
  126. package/dist/ui/molecules/region-state/region-state.js +83 -0
  127. package/dist/ui/molecules/region-state/region-state.js.map +1 -0
  128. package/dist/ui/molecules/table-toolbar/table-toolbar.d.ts +3 -1
  129. package/dist/ui/molecules/table-toolbar/table-toolbar.d.ts.map +1 -1
  130. package/dist/ui/molecules/table-toolbar/table-toolbar.js +3 -2
  131. package/dist/ui/molecules/table-toolbar/table-toolbar.js.map +1 -1
  132. package/dist/ui/molecules/toast-region/toast-region.d.ts +63 -19
  133. package/dist/ui/molecules/toast-region/toast-region.d.ts.map +1 -1
  134. package/dist/ui/molecules/toast-region/toast-region.js +129 -42
  135. package/dist/ui/molecules/toast-region/toast-region.js.map +1 -1
  136. package/dist/ui/organisms/command-palette/command-palette.d.ts.map +1 -1
  137. package/dist/ui/organisms/command-palette/command-palette.js +19 -12
  138. package/dist/ui/organisms/command-palette/command-palette.js.map +1 -1
  139. package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.d.ts.map +1 -1
  140. package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.js +0 -1
  141. package/dist/ui/organisms/entity-detail-grid/entity-detail-grid.js.map +1 -1
  142. package/dist/ui/organisms/entity-form/entity-form.d.ts +1 -1
  143. package/dist/ui/organisms/entity-form/entity-form.d.ts.map +1 -1
  144. package/dist/ui/organisms/entity-form/entity-form.js +322 -214
  145. package/dist/ui/organisms/entity-form/entity-form.js.map +1 -1
  146. package/dist/ui/organisms/entity-form/entity-form.types.d.ts +16 -0
  147. package/dist/ui/organisms/entity-form/entity-form.types.d.ts.map +1 -1
  148. package/dist/ui/organisms/entity-form/entity-form.types.js.map +1 -1
  149. package/dist/ui/organisms/entity-table/entity-table.d.ts +1 -1
  150. package/dist/ui/organisms/entity-table/entity-table.d.ts.map +1 -1
  151. package/dist/ui/organisms/entity-table/entity-table.js +332 -197
  152. package/dist/ui/organisms/entity-table/entity-table.js.map +1 -1
  153. package/dist/ui/organisms/entity-table/entity-table.types.d.ts +19 -4
  154. package/dist/ui/organisms/entity-table/entity-table.types.d.ts.map +1 -1
  155. package/dist/ui/organisms/entity-table/entity-table.types.js.map +1 -1
  156. package/dist/ui/organisms/entity-table/header-sort.d.ts +18 -0
  157. package/dist/ui/organisms/entity-table/header-sort.d.ts.map +1 -0
  158. package/dist/ui/organisms/entity-table/header-sort.js +54 -0
  159. package/dist/ui/organisms/entity-table/header-sort.js.map +1 -0
  160. package/dist/ui/organisms/entity-table/pivot.d.ts +25 -0
  161. package/dist/ui/organisms/entity-table/pivot.d.ts.map +1 -0
  162. package/dist/ui/organisms/entity-table/pivot.js +106 -0
  163. package/dist/ui/organisms/entity-table/pivot.js.map +1 -0
  164. package/dist/ui/organisms/entity-table/use-result-announcement.d.ts +13 -0
  165. package/dist/ui/organisms/entity-table/use-result-announcement.d.ts.map +1 -0
  166. package/dist/ui/organisms/entity-table/use-result-announcement.js +31 -0
  167. package/dist/ui/organisms/entity-table/use-result-announcement.js.map +1 -0
  168. package/dist/ui/organisms/top-bar/live-indicator.d.ts +11 -0
  169. package/dist/ui/organisms/top-bar/live-indicator.d.ts.map +1 -0
  170. package/dist/ui/organisms/top-bar/live-indicator.js +46 -0
  171. package/dist/ui/organisms/top-bar/live-indicator.js.map +1 -0
  172. package/dist/ui/organisms/top-bar/top-bar.d.ts +11 -2
  173. package/dist/ui/organisms/top-bar/top-bar.d.ts.map +1 -1
  174. package/dist/ui/organisms/top-bar/top-bar.js +14 -5
  175. package/dist/ui/organisms/top-bar/top-bar.js.map +1 -1
  176. package/dist/ui/organisms/wizard/wizard.d.ts +1 -1
  177. package/dist/ui/organisms/wizard/wizard.d.ts.map +1 -1
  178. package/dist/ui/organisms/wizard/wizard.js +7 -1
  179. package/dist/ui/organisms/wizard/wizard.js.map +1 -1
  180. package/dist/ui/organisms/wizard/wizard.types.d.ts +8 -0
  181. package/dist/ui/organisms/wizard/wizard.types.d.ts.map +1 -1
  182. package/dist/ui/organisms/wizard/wizard.types.js.map +1 -1
  183. package/dist/ui/utils/live-updates.d.ts +24 -0
  184. package/dist/ui/utils/live-updates.d.ts.map +1 -0
  185. package/dist/ui/utils/live-updates.js +20 -0
  186. package/dist/ui/utils/live-updates.js.map +1 -0
  187. package/dist/ui/utils/use-focus-on-arrival.d.ts +14 -0
  188. package/dist/ui/utils/use-focus-on-arrival.d.ts.map +1 -0
  189. package/dist/ui/utils/use-focus-on-arrival.js +42 -0
  190. package/dist/ui/utils/use-focus-on-arrival.js.map +1 -0
  191. package/dist/ui/utils/use-hotkey.d.ts +44 -25
  192. package/dist/ui/utils/use-hotkey.d.ts.map +1 -1
  193. package/dist/ui/utils/use-hotkey.js +80 -48
  194. package/dist/ui/utils/use-hotkey.js.map +1 -1
  195. package/package.json +5 -5
  196. package/dist/ui/molecules/split-button/index.d.ts +0 -2
  197. package/dist/ui/molecules/split-button/index.d.ts.map +0 -1
  198. package/dist/ui/molecules/split-button/index.js +0 -3
  199. package/dist/ui/molecules/split-button/index.js.map +0 -1
  200. package/dist/ui/molecules/split-button/split-button.d.ts +0 -21
  201. package/dist/ui/molecules/split-button/split-button.d.ts.map +0 -1
  202. package/dist/ui/molecules/split-button/split-button.js +0 -35
  203. package/dist/ui/molecules/split-button/split-button.js.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/ui/organisms/entity-table/pivot.ts"],"sourcesContent":["import type { Entity, MetaAccessorType } from '@entifix/core';\n\nimport type {\n EntityTableColumn,\n EntityTablePivotBreakpoint,\n} from './entity-table.types.js';\n\n/**\n * The narrowest a column of each type reads at, in `rem`, when its member\n * declares no `minWidth`.\n */\nexport const TYPE_MIN_WIDTH: Partial<Record<MetaAccessorType, number>> = {\n id: 6,\n string: 10,\n number: 6,\n boolean: 4,\n date: 8,\n enum: 8,\n link: 10,\n};\n\n/** Any other type — a collection, a composition. */\nconst DEFAULT_MIN_WIDTH = 8;\n/** The selection column, and the row's actions. */\nexport const SELECTION_WIDTH = 3;\nexport const ACTIONS_WIDTH = 5;\n\n/** A breakpoint's width, now read against the table's own container. */\nconst BREAKPOINT_WIDTH: Record<EntityTablePivotBreakpoint, number> = {\n sm: 40,\n md: 48,\n lg: 64,\n};\n\n/**\n * The container widths a table can switch at — Tailwind's named container\n * sizes, in `rem` — each with its classes written out in full: Tailwind sees\n * only a class it can read in the source, never one built at runtime. The grid\n * is `display: none` below the width and the cards at and above it.\n */\nconst STEPS: readonly (readonly [number, string, string])[] = [\n [20, 'hidden @xs/entity-table:block', '@xs/entity-table:hidden'],\n [24, 'hidden @sm/entity-table:block', '@sm/entity-table:hidden'],\n [28, 'hidden @md/entity-table:block', '@md/entity-table:hidden'],\n [32, 'hidden @lg/entity-table:block', '@lg/entity-table:hidden'],\n [36, 'hidden @xl/entity-table:block', '@xl/entity-table:hidden'],\n [42, 'hidden @2xl/entity-table:block', '@2xl/entity-table:hidden'],\n [48, 'hidden @3xl/entity-table:block', '@3xl/entity-table:hidden'],\n [56, 'hidden @4xl/entity-table:block', '@4xl/entity-table:hidden'],\n [64, 'hidden @5xl/entity-table:block', '@5xl/entity-table:hidden'],\n [72, 'hidden @6xl/entity-table:block', '@6xl/entity-table:hidden'],\n [80, 'hidden @7xl/entity-table:block', '@7xl/entity-table:hidden'],\n];\n\n/**\n * The container width, in `rem`, at which a table's visible columns fit side\n * by side: the sum of their widths and of the selection and actions columns —\n * or a declared breakpoint's, which overrides it.\n */\nexport function pivotWidth<TEntity extends Entity>(\n columns: readonly EntityTableColumn<TEntity>[],\n extra: number,\n breakpoint?: EntityTablePivotBreakpoint,\n): number {\n if (breakpoint !== undefined) return BREAKPOINT_WIDTH[breakpoint];\n return columns.reduce(\n (sum, column) =>\n sum +\n (column.minWidth ?? TYPE_MIN_WIDTH[column.type] ?? DEFAULT_MIN_WIDTH),\n extra,\n );\n}\n\n/**\n * The classes that show the grid at and above `width`, and the cards below:\n * the first named size that holds it, or the widest.\n */\nexport function pivotClasses(width: number): { grid: string; cards: string } {\n const [, grid, cards] = (STEPS.find(([size]) => size >= width) ??\n STEPS[STEPS.length - 1]) as readonly [number, string, string];\n return { grid, cards };\n}\n"],"names":["TYPE_MIN_WIDTH","id","string","number","boolean","date","enum","link","DEFAULT_MIN_WIDTH","SELECTION_WIDTH","ACTIONS_WIDTH","BREAKPOINT_WIDTH","sm","md","lg","STEPS","pivotWidth","columns","extra","breakpoint","undefined","reduce","sum","column","minWidth","type","pivotClasses","width","grid","cards","find","size","length"],"mappings":"AAOA;;;CAGC,GACD,OAAO,MAAMA,iBAA4D;IACvEC,IAAI;IACJC,QAAQ;IACRC,QAAQ;IACRC,SAAS;IACTC,MAAM;IACNC,MAAM;IACNC,MAAM;AACR,EAAE;AAEF,kDAAkD,GAClD,MAAMC,oBAAoB;AAC1B,iDAAiD,GACjD,OAAO,MAAMC,kBAAkB,EAAE;AACjC,OAAO,MAAMC,gBAAgB,EAAE;AAE/B,sEAAsE,GACtE,MAAMC,mBAA+D;IACnEC,IAAI;IACJC,IAAI;IACJC,IAAI;AACN;AAEA;;;;;CAKC,GACD,MAAMC,QAAwD;IAC5D;QAAC;QAAI;QAAiC;KAA0B;IAChE;QAAC;QAAI;QAAiC;KAA0B;IAChE;QAAC;QAAI;QAAiC;KAA0B;IAChE;QAAC;QAAI;QAAiC;KAA0B;IAChE;QAAC;QAAI;QAAiC;KAA0B;IAChE;QAAC;QAAI;QAAkC;KAA2B;IAClE;QAAC;QAAI;QAAkC;KAA2B;IAClE;QAAC;QAAI;QAAkC;KAA2B;IAClE;QAAC;QAAI;QAAkC;KAA2B;IAClE;QAAC;QAAI;QAAkC;KAA2B;IAClE;QAAC;QAAI;QAAkC;KAA2B;CACnE;AAED;;;;CAIC,GACD,OAAO,SAASC,WACdC,OAA8C,EAC9CC,KAAa,EACbC,UAAuC;IAEvC,IAAIA,eAAeC,WAAW,OAAOT,gBAAgB,CAACQ,WAAW;IACjE,OAAOF,QAAQI,MAAM,CACnB,CAACC,KAAKC;YAEHA,MAAAA;eADDD,QACCC,QAAAA,mBAAAA,OAAOC,QAAQ,YAAfD,mBAAmBvB,cAAc,CAACuB,OAAOE,IAAI,CAAC,YAA9CF,OAAkDf;OACrDU;AAEJ;AAEA;;;CAGC,GACD,OAAO,SAASQ,aAAaC,KAAa;QACfZ;IAAzB,MAAM,GAAGa,MAAMC,MAAM,IAAId,cAAAA,MAAMe,IAAI,CAAC,CAAC,CAACC,KAAK,GAAKA,QAAQJ,kBAA/BZ,cACvBA,KAAK,CAACA,MAAMiB,MAAM,GAAG,EAAE;IACzB,OAAO;QAAEJ;QAAMC;IAAM;AACvB"}
@@ -0,0 +1,13 @@
1
+ /** How long the results settle before they are said: a burst of typing is one. */
2
+ export declare const RESULT_ANNOUNCEMENT_DELAY_MS = 500;
3
+ /**
4
+ * What a table's status region says after its results change — a filter, a
5
+ * sort or a page — and nothing on the first load, which the reader asked for
6
+ * by arriving rather than by acting.
7
+ *
8
+ * `signature` names the query the results answer, so a change that happens to
9
+ * keep the same count is still said: the text is nudged with a non-breaking
10
+ * space, because a live region repeats nothing that did not change.
11
+ */
12
+ export declare function useResultAnnouncement(settled: boolean, message: string, signature: string, delay?: number): string;
13
+ //# sourceMappingURL=use-result-announcement.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-result-announcement.d.ts","sourceRoot":"","sources":["../../../../src/ui/organisms/entity-table/use-result-announcement.ts"],"names":[],"mappings":"AAEA,kFAAkF;AAClF,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAEhD;;;;;;;;GAQG;AACH,wBAAgB,qBAAqB,CACnC,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,MAAM,EACf,SAAS,EAAE,MAAM,EACjB,KAAK,SAA+B,GACnC,MAAM,CAqBR"}
@@ -0,0 +1,31 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ /** How long the results settle before they are said: a burst of typing is one. */ export const RESULT_ANNOUNCEMENT_DELAY_MS = 500;
3
+ /**
4
+ * What a table's status region says after its results change — a filter, a
5
+ * sort or a page — and nothing on the first load, which the reader asked for
6
+ * by arriving rather than by acting.
7
+ *
8
+ * `signature` names the query the results answer, so a change that happens to
9
+ * keep the same count is still said: the text is nudged with a non-breaking
10
+ * space, because a live region repeats nothing that did not change.
11
+ */ export function useResultAnnouncement(settled, message, signature, delay = RESULT_ANNOUNCEMENT_DELAY_MS) {
12
+ const [said, setSaid] = useState('');
13
+ const firstSettled = useRef(true);
14
+ useEffect(()=>{
15
+ if (!settled) return;
16
+ if (firstSettled.current) {
17
+ firstSettled.current = false;
18
+ return;
19
+ }
20
+ const timer = setTimeout(()=>setSaid((previous)=>previous === message ? `${message}\u00a0` : message), delay);
21
+ return ()=>clearTimeout(timer);
22
+ }, [
23
+ settled,
24
+ message,
25
+ signature,
26
+ delay
27
+ ]);
28
+ return said;
29
+ }
30
+
31
+ //# sourceMappingURL=use-result-announcement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/ui/organisms/entity-table/use-result-announcement.ts"],"sourcesContent":["import { useEffect, useRef, useState } from 'react';\n\n/** How long the results settle before they are said: a burst of typing is one. */\nexport const RESULT_ANNOUNCEMENT_DELAY_MS = 500;\n\n/**\n * What a table's status region says after its results change — a filter, a\n * sort or a page — and nothing on the first load, which the reader asked for\n * by arriving rather than by acting.\n *\n * `signature` names the query the results answer, so a change that happens to\n * keep the same count is still said: the text is nudged with a non-breaking\n * space, because a live region repeats nothing that did not change.\n */\nexport function useResultAnnouncement(\n settled: boolean,\n message: string,\n signature: string,\n delay = RESULT_ANNOUNCEMENT_DELAY_MS,\n): string {\n const [said, setSaid] = useState('');\n const firstSettled = useRef(true);\n\n useEffect(() => {\n if (!settled) return;\n if (firstSettled.current) {\n firstSettled.current = false;\n return;\n }\n const timer = setTimeout(\n () =>\n setSaid(previous =>\n previous === message ? `${message}\\u00a0` : message,\n ),\n delay,\n );\n return () => clearTimeout(timer);\n }, [settled, message, signature, delay]);\n\n return said;\n}\n"],"names":["useEffect","useRef","useState","RESULT_ANNOUNCEMENT_DELAY_MS","useResultAnnouncement","settled","message","signature","delay","said","setSaid","firstSettled","current","timer","setTimeout","previous","clearTimeout"],"mappings":"AAAA,SAASA,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAQ;AAEpD,gFAAgF,GAChF,OAAO,MAAMC,+BAA+B,IAAI;AAEhD;;;;;;;;CAQC,GACD,OAAO,SAASC,sBACdC,OAAgB,EAChBC,OAAe,EACfC,SAAiB,EACjBC,QAAQL,4BAA4B;IAEpC,MAAM,CAACM,MAAMC,QAAQ,GAAGR,SAAS;IACjC,MAAMS,eAAeV,OAAO;IAE5BD,UAAU;QACR,IAAI,CAACK,SAAS;QACd,IAAIM,aAAaC,OAAO,EAAE;YACxBD,aAAaC,OAAO,GAAG;YACvB;QACF;QACA,MAAMC,QAAQC,WACZ,IACEJ,QAAQK,CAAAA,WACNA,aAAaT,UAAU,GAAGA,QAAQ,MAAM,CAAC,GAAGA,UAEhDE;QAEF,OAAO,IAAMQ,aAAaH;IAC5B,GAAG;QAACR;QAASC;QAASC;QAAWC;KAAM;IAEvC,OAAOC;AACT"}
@@ -0,0 +1,11 @@
1
+ import type { LiveConnection } from '../../utils/live-updates.js';
2
+ /**
3
+ * One polite region that is always there, so what changes in it is heard:
4
+ * "paused — reconnecting" on the drop, "resumed" on the recovery, and nothing
5
+ * while updates simply flow.
6
+ */
7
+ export declare function LiveIndicator({ connection, onReconnect, }: {
8
+ connection: LiveConnection;
9
+ onReconnect?: () => void;
10
+ }): import("react").JSX.Element;
11
+ //# sourceMappingURL=live-indicator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-indicator.d.ts","sourceRoot":"","sources":["../../../../src/ui/organisms/top-bar/live-indicator.tsx"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAElE;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,EAC5B,UAAU,EACV,WAAW,GACZ,EAAE;IACD,UAAU,EAAE,cAAc,CAAC;IAC3B,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B,+BAqCA"}
@@ -0,0 +1,46 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useState } from 'react';
4
+ import { useT } from '../../../i18n/index.js';
5
+ import { TONE_SURFACE } from '../../atoms/alert/tone.js';
6
+ import { Button } from '../../atoms/button/index.js';
7
+ import { cn } from '../../utils/cn.js';
8
+ /**
9
+ * One polite region that is always there, so what changes in it is heard:
10
+ * "paused — reconnecting" on the drop, "resumed" on the recovery, and nothing
11
+ * while updates simply flow.
12
+ */ export function LiveIndicator({ connection, onReconnect }) {
13
+ const t = useT();
14
+ const previous = useRef(connection);
15
+ const [resumed, setResumed] = useState(false);
16
+ useEffect(()=>{
17
+ if (previous.current === connection) return;
18
+ setResumed(connection === 'open');
19
+ previous.current = connection;
20
+ }, [
21
+ connection
22
+ ]);
23
+ return /*#__PURE__*/ _jsx("div", {
24
+ role: "status",
25
+ className: "flex items-center",
26
+ children: connection === 'open' ? resumed && /*#__PURE__*/ _jsx("span", {
27
+ className: "sr-only",
28
+ children: t('live.resumed')
29
+ }) : /*#__PURE__*/ _jsxs("span", {
30
+ "data-testid": "live-updates-paused",
31
+ className: cn('flex items-center gap-2xs rounded-md border px-xs py-3xs text-step-sm', TONE_SURFACE.warning),
32
+ children: [
33
+ t(connection === 'closed' ? 'live.stopped' : 'live.reconnecting'),
34
+ onReconnect !== undefined && /*#__PURE__*/ _jsx(Button, {
35
+ type: "button",
36
+ variant: "secondary",
37
+ size: "sm",
38
+ onClick: onReconnect,
39
+ children: t('live.reconnect')
40
+ })
41
+ ]
42
+ })
43
+ });
44
+ }
45
+
46
+ //# sourceMappingURL=live-indicator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../../src/ui/organisms/top-bar/live-indicator.tsx"],"sourcesContent":["'use client';\n\nimport { useEffect, useRef, useState } from 'react';\n\nimport { useT } from '../../../i18n/index.js';\nimport { TONE_SURFACE } from '../../atoms/alert/tone.js';\nimport { Button } from '../../atoms/button/index.js';\nimport { cn } from '../../utils/cn.js';\nimport type { LiveConnection } from '../../utils/live-updates.js';\n\n/**\n * One polite region that is always there, so what changes in it is heard:\n * \"paused — reconnecting\" on the drop, \"resumed\" on the recovery, and nothing\n * while updates simply flow.\n */\nexport function LiveIndicator({\n connection,\n onReconnect,\n}: {\n connection: LiveConnection;\n onReconnect?: () => void;\n}) {\n const t = useT();\n const previous = useRef(connection);\n const [resumed, setResumed] = useState(false);\n useEffect(() => {\n if (previous.current === connection) return;\n setResumed(connection === 'open');\n previous.current = connection;\n }, [connection]);\n\n return (\n <div role=\"status\" className=\"flex items-center\">\n {connection === 'open' ? (\n resumed && <span className=\"sr-only\">{t('live.resumed')}</span>\n ) : (\n <span\n data-testid=\"live-updates-paused\"\n className={cn(\n 'flex items-center gap-2xs rounded-md border px-xs py-3xs text-step-sm',\n TONE_SURFACE.warning,\n )}\n >\n {t(connection === 'closed' ? 'live.stopped' : 'live.reconnecting')}\n {onReconnect !== undefined && (\n <Button\n type=\"button\"\n variant=\"secondary\"\n size=\"sm\"\n onClick={onReconnect}\n >\n {t('live.reconnect')}\n </Button>\n )}\n </span>\n )}\n </div>\n );\n}\n"],"names":["useEffect","useRef","useState","useT","TONE_SURFACE","Button","cn","LiveIndicator","connection","onReconnect","t","previous","resumed","setResumed","current","div","role","className","span","data-testid","warning","undefined","type","variant","size","onClick"],"mappings":"AAAA;;AAEA,SAASA,SAAS,EAAEC,MAAM,EAAEC,QAAQ,QAAQ,QAAQ;AAEpD,SAASC,IAAI,QAAQ,yBAAyB;AAC9C,SAASC,YAAY,QAAQ,4BAA4B;AACzD,SAASC,MAAM,QAAQ,8BAA8B;AACrD,SAASC,EAAE,QAAQ,oBAAoB;AAGvC;;;;CAIC,GACD,OAAO,SAASC,cAAc,EAC5BC,UAAU,EACVC,WAAW,EAIZ;IACC,MAAMC,IAAIP;IACV,MAAMQ,WAAWV,OAAOO;IACxB,MAAM,CAACI,SAASC,WAAW,GAAGX,SAAS;IACvCF,UAAU;QACR,IAAIW,SAASG,OAAO,KAAKN,YAAY;QACrCK,WAAWL,eAAe;QAC1BG,SAASG,OAAO,GAAGN;IACrB,GAAG;QAACA;KAAW;IAEf,qBACE,KAACO;QAAIC,MAAK;QAASC,WAAU;kBAC1BT,eAAe,SACdI,yBAAW,KAACM;YAAKD,WAAU;sBAAWP,EAAE;2BAExC,MAACQ;YACCC,eAAY;YACZF,WAAWX,GACT,yEACAF,aAAagB,OAAO;;gBAGrBV,EAAEF,eAAe,WAAW,iBAAiB;gBAC7CC,gBAAgBY,2BACf,KAAChB;oBACCiB,MAAK;oBACLC,SAAQ;oBACRC,MAAK;oBACLC,SAAShB;8BAERC,EAAE;;;;;AAOjB"}
@@ -1,12 +1,21 @@
1
1
  import type { ComponentPropsWithoutRef, ReactNode } from 'react';
2
- export type TopBarProps = ComponentPropsWithoutRef<'header'>;
2
+ import type { LiveConnection } from '../../utils/live-updates.js';
3
+ export interface TopBarProps extends ComponentPropsWithoutRef<'header'> {
4
+ /**
5
+ * The tab's live updates. Anything but `open` shows that they are paused,
6
+ * and it is said on the drop and on the recovery only (ADR 0028).
7
+ */
8
+ connection?: LiveConnection;
9
+ /** Reopens the stream now — the indicator's Reconnect. */
10
+ onReconnect?: () => void;
11
+ }
3
12
  /**
4
13
  * The application top bar: a thin, recessive band carrying identity on the left
5
14
  * and actions on the right. Deliberately quiet — the workspace tab strip below
6
15
  * it is where the eye goes. Compose it from the slots: `TopBar.Brand`,
7
16
  * `TopBar.Context`, `TopBar.Actions`.
8
17
  */
9
- export declare function TopBar({ className, children, ...props }: TopBarProps): import("react").JSX.Element;
18
+ export declare function TopBar({ className, children, connection, onReconnect, ...props }: TopBarProps): import("react").JSX.Element;
10
19
  export declare namespace TopBar {
11
20
  var Brand: ({ children, className }: SlotProps) => import("react").JSX.Element;
12
21
  var Context: ({ children, className }: SlotProps) => import("react").JSX.Element;
@@ -1 +1 @@
1
- {"version":3,"file":"top-bar.d.ts","sourceRoot":"","sources":["../../../../src/ui/organisms/top-bar/top-bar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAIjE,MAAM,MAAM,WAAW,GAAG,wBAAwB,CAAC,QAAQ,CAAC,CAAC;AAE7D;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,EAAE,SAAS,EAAE,QAAQ,EAAE,GAAG,KAAK,EAAE,EAAE,WAAW,+BAYpE;yBAZe,MAAM;yCAmBkB,SAAS;2CAaP,SAAS;2CAST,SAAS;;AA3BnD,UAAU,SAAS;IACjB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
1
+ {"version":3,"file":"top-bar.d.ts","sourceRoot":"","sources":["../../../../src/ui/organisms/top-bar/top-bar.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,wBAAwB,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAGjE,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAC;AAGlE,MAAM,WAAW,WAAY,SAAQ,wBAAwB,CAAC,QAAQ,CAAC;IACrE;;;OAGG;IACH,UAAU,CAAC,EAAE,cAAc,CAAC;IAC5B,0DAA0D;IAC1D,WAAW,CAAC,EAAE,MAAM,IAAI,CAAC;CAC1B;AAED;;;;;GAKG;AACH,wBAAgB,MAAM,CAAC,EACrB,SAAS,EACT,QAAQ,EACR,UAAmB,EACnB,WAAW,EACX,GAAG,KAAK,EACT,EAAE,WAAW,+BAab;yBAnBe,MAAM;yCA0BkB,SAAS;2CAaP,SAAS;2CAST,SAAS;;AA3BnD,UAAU,SAAS;IACjB,QAAQ,EAAE,SAAS,CAAC;IACpB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
@@ -1,21 +1,30 @@
1
1
  import { _ as _extends } from "@swc/helpers/_/_extends";
2
2
  import { _ as _object_without_properties_loose } from "@swc/helpers/_/_object_without_properties_loose";
3
- import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
4
4
  import { cn } from '../../utils/cn.js';
5
+ import { LiveIndicator } from './live-indicator.js';
5
6
  /**
6
7
  * The application top bar: a thin, recessive band carrying identity on the left
7
8
  * and actions on the right. Deliberately quiet — the workspace tab strip below
8
9
  * it is where the eye goes. Compose it from the slots: `TopBar.Brand`,
9
10
  * `TopBar.Context`, `TopBar.Actions`.
10
11
  */ export function TopBar(_0) {
11
- let { className, children } = _0, props = _object_without_properties_loose(_0, [
12
+ let { className, children, connection = 'open', onReconnect } = _0, props = _object_without_properties_loose(_0, [
12
13
  "className",
13
- "children"
14
+ "children",
15
+ "connection",
16
+ "onReconnect"
14
17
  ]);
15
- return /*#__PURE__*/ _jsx("header", _extends({
18
+ return /*#__PURE__*/ _jsxs("header", _extends({
16
19
  className: cn('flex items-center gap-s border-b border-border bg-surface-elevated px-m py-2xs', className)
17
20
  }, props, {
18
- children: children
21
+ children: [
22
+ children,
23
+ /*#__PURE__*/ _jsx(LiveIndicator, {
24
+ connection: connection,
25
+ onReconnect: onReconnect
26
+ })
27
+ ]
19
28
  }));
20
29
  }
21
30
  function Brand({ children, className }) {
@@ -1 +1 @@
1
- {"version":3,"sources":["../../../../src/ui/organisms/top-bar/top-bar.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef, ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\n\nexport type TopBarProps = ComponentPropsWithoutRef<'header'>;\n\n/**\n * The application top bar: a thin, recessive band carrying identity on the left\n * and actions on the right. Deliberately quiet — the workspace tab strip below\n * it is where the eye goes. Compose it from the slots: `TopBar.Brand`,\n * `TopBar.Context`, `TopBar.Actions`.\n */\nexport function TopBar({ className, children, ...props }: TopBarProps) {\n return (\n <header\n className={cn(\n 'flex items-center gap-s border-b border-border bg-surface-elevated px-m py-2xs',\n className,\n )}\n {...props}\n >\n {children}\n </header>\n );\n}\n\ninterface SlotProps {\n children: ReactNode;\n className?: string;\n}\n\nfunction Brand({ children, className }: SlotProps) {\n return (\n <div\n className={cn(\n 'flex items-center gap-2xs text-step-1 font-semibold text-content',\n className,\n )}\n >\n {children}\n </div>\n );\n}\n\nfunction Context({ children, className }: SlotProps) {\n return (\n <div className={cn('text-step-sm text-content-muted', className)}>\n {children}\n </div>\n );\n}\n\n/** Right-aligned cluster — search, notifications, the user menu. */\nfunction Actions({ children, className }: SlotProps) {\n return (\n <div className={cn('ml-auto flex items-center gap-2xs', className)}>\n {children}\n </div>\n );\n}\n\nTopBar.Brand = Brand;\nTopBar.Context = Context;\nTopBar.Actions = Actions;\n"],"names":["cn","TopBar","className","children","props","header","Brand","div","Context","Actions"],"mappings":";;;AAEA,SAASA,EAAE,QAAQ,oBAAoB;AAIvC;;;;;CAKC,GACD,OAAO,SAASC,OAAO,EAA8C;QAA9C,EAAEC,SAAS,EAAEC,QAAQ,EAAyB,OAApBC;;;;IAC/C,qBACE,KAACC;QACCH,WAAWF,GACT,kFACAE;OAEEE;kBAEHD;;AAGP;AAOA,SAASG,MAAM,EAAEH,QAAQ,EAAED,SAAS,EAAa;IAC/C,qBACE,KAACK;QACCL,WAAWF,GACT,oEACAE;kBAGDC;;AAGP;AAEA,SAASK,QAAQ,EAAEL,QAAQ,EAAED,SAAS,EAAa;IACjD,qBACE,KAACK;QAAIL,WAAWF,GAAG,mCAAmCE;kBACnDC;;AAGP;AAEA,kEAAkE,GAClE,SAASM,QAAQ,EAAEN,QAAQ,EAAED,SAAS,EAAa;IACjD,qBACE,KAACK;QAAIL,WAAWF,GAAG,qCAAqCE;kBACrDC;;AAGP;AAEAF,OAAOK,KAAK,GAAGA;AACfL,OAAOO,OAAO,GAAGA;AACjBP,OAAOQ,OAAO,GAAGA"}
1
+ {"version":3,"sources":["../../../../src/ui/organisms/top-bar/top-bar.tsx"],"sourcesContent":["import type { ComponentPropsWithoutRef, ReactNode } from 'react';\n\nimport { cn } from '../../utils/cn.js';\nimport type { LiveConnection } from '../../utils/live-updates.js';\nimport { LiveIndicator } from './live-indicator.js';\n\nexport interface TopBarProps extends ComponentPropsWithoutRef<'header'> {\n /**\n * The tab's live updates. Anything but `open` shows that they are paused,\n * and it is said on the drop and on the recovery only (ADR 0028).\n */\n connection?: LiveConnection;\n /** Reopens the stream now — the indicator's Reconnect. */\n onReconnect?: () => void;\n}\n\n/**\n * The application top bar: a thin, recessive band carrying identity on the left\n * and actions on the right. Deliberately quiet — the workspace tab strip below\n * it is where the eye goes. Compose it from the slots: `TopBar.Brand`,\n * `TopBar.Context`, `TopBar.Actions`.\n */\nexport function TopBar({\n className,\n children,\n connection = 'open',\n onReconnect,\n ...props\n}: TopBarProps) {\n return (\n <header\n className={cn(\n 'flex items-center gap-s border-b border-border bg-surface-elevated px-m py-2xs',\n className,\n )}\n {...props}\n >\n {children}\n <LiveIndicator connection={connection} onReconnect={onReconnect} />\n </header>\n );\n}\n\ninterface SlotProps {\n children: ReactNode;\n className?: string;\n}\n\nfunction Brand({ children, className }: SlotProps) {\n return (\n <div\n className={cn(\n 'flex items-center gap-2xs text-step-1 font-semibold text-content',\n className,\n )}\n >\n {children}\n </div>\n );\n}\n\nfunction Context({ children, className }: SlotProps) {\n return (\n <div className={cn('text-step-sm text-content-muted', className)}>\n {children}\n </div>\n );\n}\n\n/** Right-aligned cluster — search, notifications, the user menu. */\nfunction Actions({ children, className }: SlotProps) {\n return (\n <div className={cn('ml-auto flex items-center gap-2xs', className)}>\n {children}\n </div>\n );\n}\n\nTopBar.Brand = Brand;\nTopBar.Context = Context;\nTopBar.Actions = Actions;\n"],"names":["cn","LiveIndicator","TopBar","className","children","connection","onReconnect","props","header","Brand","div","Context","Actions"],"mappings":";;;AAEA,SAASA,EAAE,QAAQ,oBAAoB;AAEvC,SAASC,aAAa,QAAQ,sBAAsB;AAYpD;;;;;CAKC,GACD,OAAO,SAASC,OAAO,EAMT;QANS,EACrBC,SAAS,EACTC,QAAQ,EACRC,aAAa,MAAM,EACnBC,WAAW,EAEC,OADTC;;;;;;IAEH,qBACE,MAACC;QACCL,WAAWH,GACT,kFACAG;OAEEI;;YAEHH;0BACD,KAACH;gBAAcI,YAAYA;gBAAYC,aAAaA;;;;AAG1D;AAOA,SAASG,MAAM,EAAEL,QAAQ,EAAED,SAAS,EAAa;IAC/C,qBACE,KAACO;QACCP,WAAWH,GACT,oEACAG;kBAGDC;;AAGP;AAEA,SAASO,QAAQ,EAAEP,QAAQ,EAAED,SAAS,EAAa;IACjD,qBACE,KAACO;QAAIP,WAAWH,GAAG,mCAAmCG;kBACnDC;;AAGP;AAEA,kEAAkE,GAClE,SAASQ,QAAQ,EAAER,QAAQ,EAAED,SAAS,EAAa;IACjD,qBACE,KAACO;QAAIP,WAAWH,GAAG,qCAAqCG;kBACrDC;;AAGP;AAEAF,OAAOO,KAAK,GAAGA;AACfP,OAAOS,OAAO,GAAGA;AACjBT,OAAOU,OAAO,GAAGA"}
@@ -16,5 +16,5 @@ import type { WizardProps } from './wizard.types.js';
16
16
  * is `<ol>`/`<li>` with `aria-current="step"`, and each step's status is
17
17
  * announced rather than carried by colour alone.
18
18
  */
19
- export declare function Wizard({ steps, activeStep, title, children, onNext, onFinish, onPrevious, onStepChange, canFinish, canAdvance, isSubmitting, recap, onDismissRecap, isLoading, skeleton, className, }: WizardProps): import("react").JSX.Element;
19
+ export declare function Wizard({ steps, activeStep, title, children, onNext, onFinish, onPrevious, onStepChange, canFinish, canAdvance, isSubmitting, error, onRetry, recap, onDismissRecap, isLoading, skeleton, className, }: WizardProps): import("react").JSX.Element;
20
20
  //# sourceMappingURL=wizard.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"wizard.d.ts","sourceRoot":"","sources":["../../../../src/ui/organisms/wizard/wizard.tsx"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAkCrE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CAAC,EACrB,KAAK,EACL,UAAU,EACV,KAAK,EACL,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,SAAiB,EACjB,UAAiB,EACjB,YAAoB,EACpB,KAAK,EACL,cAAc,EACd,SAAiB,EACjB,QAAe,EACf,SAAS,GACV,EAAE,WAAW,+BA+Gb"}
1
+ {"version":3,"file":"wizard.d.ts","sourceRoot":"","sources":["../../../../src/ui/organisms/wizard/wizard.tsx"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,WAAW,EAAkB,MAAM,mBAAmB,CAAC;AAkCrE;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,MAAM,CAAC,EACrB,KAAK,EACL,UAAU,EACV,KAAK,EACL,QAAQ,EACR,MAAM,EACN,QAAQ,EACR,UAAU,EACV,YAAY,EACZ,SAAiB,EACjB,UAAiB,EACjB,YAAoB,EACpB,KAAK,EACL,OAAO,EACP,KAAK,EACL,cAAc,EACd,SAAiB,EACjB,QAAe,EACf,SAAS,GACV,EAAE,WAAW,+BAiHb"}
@@ -7,6 +7,7 @@ import { Skeleton } from '../../atoms/skeleton/index.js';
7
7
  import { Text } from '../../atoms/text/index.js';
8
8
  import { Cluster } from '../../layout/cluster/index.js';
9
9
  import { Card } from '../../molecules/card/index.js';
10
+ import { RegionState } from '../../molecules/region-state/index.js';
10
11
  import { Stack } from '../../molecules/stack/index.js';
11
12
  import { cn } from '../../utils/cn.js';
12
13
  /** How many placeholder steps stand in when the caller has none yet. */ const SKELETON_STEP_COUNT = 4;
@@ -51,7 +52,7 @@ const LABEL_CLASS = {
51
52
  * user may choose between, and a wizard's steps are ordered and gated. So this
52
53
  * is `<ol>`/`<li>` with `aria-current="step"`, and each step's status is
53
54
  * announced rather than carried by colour alone.
54
- */ export function Wizard({ steps, activeStep, title, children, onNext, onFinish, onPrevious, onStepChange, canFinish = false, canAdvance = true, isSubmitting = false, recap, onDismissRecap, isLoading = false, skeleton = true, className }) {
55
+ */ export function Wizard({ steps, activeStep, title, children, onNext, onFinish, onPrevious, onStepChange, canFinish = false, canAdvance = true, isSubmitting = false, error, onRetry, recap, onDismissRecap, isLoading = false, skeleton = true, className }) {
55
56
  const t = useT();
56
57
  const heading = useRef(null);
57
58
  const focused = useRef(activeStep);
@@ -133,6 +134,11 @@ const LABEL_CLASS = {
133
134
  children
134
135
  ]
135
136
  }),
137
+ /*#__PURE__*/ _jsx(RegionState, {
138
+ plural: title,
139
+ error: error,
140
+ onRetry: onRetry
141
+ }),
136
142
  /*#__PURE__*/ _jsxs(Cluster, {
137
143
  justify: "between",
138
144
  children: [
@@ -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 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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"}
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 { RegionState } from '../../molecules/region-state/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 error,\n onRetry,\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 <RegionState plural={title} error={error} onRetry={onRetry} />\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","RegionState","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","onRetry","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","plural","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,5 +1,6 @@
1
1
  import type { WizardStepStatus } from '@entifix/core';
2
2
  import type { ReactNode } from 'react';
3
+ import type { RegionStateError } from '../../molecules/region-state/index.js';
3
4
  /**
4
5
  * One step as the stepper shows it.
5
6
  *
@@ -59,6 +60,13 @@ export interface WizardProps {
59
60
  * a resolution the caller never gets.
60
61
  */
61
62
  isSubmitting?: boolean;
63
+ /**
64
+ * The submit failed. Shown above the footer, with every answer kept, so
65
+ * the reader can try again without re-entering anything.
66
+ */
67
+ error?: RegionStateError;
68
+ /** Submits again — what a failed submit offers as Retry. */
69
+ onRetry?: () => void;
62
70
  /**
63
71
  * What was already decided, shown when a half-finished wizard is resumed.
64
72
  *
@@ -1 +1 @@
1
- {"version":3,"file":"wizard.types.d.ts","sourceRoot":"","sources":["../../../../src/ui/organisms/wizard/wizard.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B;;;;;;;OAOG;IACH,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IAEjC,2EAA2E;IAC3E,UAAU,EAAE,MAAM,CAAC;IAEnB;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd,8BAA8B;IAC9B,QAAQ,EAAE,SAAS,CAAC;IAEpB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAExC,4EAA4E;IAC5E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wEAAwE;IACxE,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IAEvB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAE5B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAE/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
1
+ {"version":3,"file":"wizard.types.d.ts","sourceRoot":"","sources":["../../../../src/ui/organisms/wizard/wizard.types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,uCAAuC,CAAC;AAE9E;;;;;;;GAOG;AACH,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,gBAAgB,CAAC;CACnC;AAED,MAAM,WAAW,WAAW;IAC1B;;;;;;;OAOG;IACH,KAAK,EAAE,SAAS,cAAc,EAAE,CAAC;IAEjC,2EAA2E;IAC3E,UAAU,EAAE,MAAM,CAAC;IAEnB;;;;;;OAMG;IACH,KAAK,EAAE,MAAM,CAAC;IAEd,8BAA8B;IAC9B,QAAQ,EAAE,SAAS,CAAC;IAEpB,MAAM,EAAE,MAAM,IAAI,CAAC;IACnB,QAAQ,EAAE,MAAM,IAAI,CAAC;IACrB,sEAAsE;IACtE,UAAU,CAAC,EAAE,MAAM,IAAI,CAAC;IACxB;;;;;OAKG;IACH,YAAY,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAExC,4EAA4E;IAC5E,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,wEAAwE;IACxE,UAAU,CAAC,EAAE,OAAO,CAAC;IAErB;;;;;;;OAOG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB;;;OAGG;IACH,KAAK,CAAC,EAAE,gBAAgB,CAAC;IACzB,4DAA4D;IAC5D,OAAO,CAAC,EAAE,MAAM,IAAI,CAAC;IAErB;;;;;;OAMG;IACH,KAAK,CAAC,EAAE,SAAS,CAAC;IAClB,6DAA6D;IAC7D,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;IAE5B,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB;;;;;;OAMG;IACH,QAAQ,CAAC,EAAE,OAAO,GAAG,SAAS,CAAC;IAE/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB"}
@@ -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,\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
+ {"version":3,"sources":["../../../../src/ui/organisms/wizard/wizard.types.ts"],"sourcesContent":["import type { WizardStepStatus } from '@entifix/core';\nimport type { ReactNode } from 'react';\n\nimport type { RegionStateError } from '../../molecules/region-state/index.js';\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 * The submit failed. Shown above the footer, with every answer kept, so\n * the reader can try again without re-entering anything.\n */\n error?: RegionStateError;\n /** Submits again — what a failed submit offers as Retry. */\n onRetry?: () => void;\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":"AAmBA,WAkFC"}
@@ -0,0 +1,24 @@
1
+ import { type ReactNode } from 'react';
2
+ /**
3
+ * Where the tab's live updates stand: `open`, or a dropped stream —
4
+ * `reconnecting` while the transport retries, `closed` once it gave up.
5
+ */
6
+ export type LiveConnection = 'open' | 'reconnecting' | 'closed';
7
+ /** The connection, and the way to try again now when the host has one. */
8
+ export interface LiveUpdates {
9
+ readonly connection: LiveConnection;
10
+ readonly reconnect?: () => void;
11
+ }
12
+ /**
13
+ * Publishes the push connection to the controls — the top bar's indicator, a
14
+ * pending write's label — which cannot read it themselves: the channel is
15
+ * `@entifix/react-integration`'s, and this package never imports it. A host's
16
+ * shell provides it; without one, updates read as live.
17
+ */
18
+ export declare function LiveUpdatesProvider({ value, children, }: {
19
+ value: LiveUpdates;
20
+ children: ReactNode;
21
+ }): import("react").JSX.Element;
22
+ /** The live connection as the nearest {@link LiveUpdatesProvider} states it. */
23
+ export declare const useLiveUpdates: () => LiveUpdates;
24
+ //# sourceMappingURL=live-updates.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"live-updates.d.ts","sourceRoot":"","sources":["../../../src/ui/utils/live-updates.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAiB,KAAK,SAAS,EAAc,MAAM,OAAO,CAAC;AAElE;;;GAGG;AACH,MAAM,MAAM,cAAc,GAAG,MAAM,GAAG,cAAc,GAAG,QAAQ,CAAC;AAEhE,0EAA0E;AAC1E,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,UAAU,EAAE,cAAc,CAAC;IACpC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,IAAI,CAAC;CACjC;AAID;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,EAClC,KAAK,EACL,QAAQ,GACT,EAAE;IACD,KAAK,EAAE,WAAW,CAAC;IACnB,QAAQ,EAAE,SAAS,CAAC;CACrB,+BAMA;AAED,gFAAgF;AAChF,eAAO,MAAM,cAAc,QAAO,WAA6C,CAAC"}
@@ -0,0 +1,20 @@
1
+ 'use client';
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import { createContext, useContext } from 'react';
4
+ const LiveUpdatesContext = /*#__PURE__*/ createContext({
5
+ connection: 'open'
6
+ });
7
+ /**
8
+ * Publishes the push connection to the controls — the top bar's indicator, a
9
+ * pending write's label — which cannot read it themselves: the channel is
10
+ * `@entifix/react-integration`'s, and this package never imports it. A host's
11
+ * shell provides it; without one, updates read as live.
12
+ */ export function LiveUpdatesProvider({ value, children }) {
13
+ return /*#__PURE__*/ _jsx(LiveUpdatesContext.Provider, {
14
+ value: value,
15
+ children: children
16
+ });
17
+ }
18
+ /** The live connection as the nearest {@link LiveUpdatesProvider} states it. */ export const useLiveUpdates = ()=>useContext(LiveUpdatesContext);
19
+
20
+ //# sourceMappingURL=live-updates.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/ui/utils/live-updates.tsx"],"sourcesContent":["'use client';\n\nimport { createContext, type ReactNode, useContext } from 'react';\n\n/**\n * Where the tab's live updates stand: `open`, or a dropped stream —\n * `reconnecting` while the transport retries, `closed` once it gave up.\n */\nexport type LiveConnection = 'open' | 'reconnecting' | 'closed';\n\n/** The connection, and the way to try again now when the host has one. */\nexport interface LiveUpdates {\n readonly connection: LiveConnection;\n readonly reconnect?: () => void;\n}\n\nconst LiveUpdatesContext = createContext<LiveUpdates>({ connection: 'open' });\n\n/**\n * Publishes the push connection to the controls — the top bar's indicator, a\n * pending write's label — which cannot read it themselves: the channel is\n * `@entifix/react-integration`'s, and this package never imports it. A host's\n * shell provides it; without one, updates read as live.\n */\nexport function LiveUpdatesProvider({\n value,\n children,\n}: {\n value: LiveUpdates;\n children: ReactNode;\n}) {\n return (\n <LiveUpdatesContext.Provider value={value}>\n {children}\n </LiveUpdatesContext.Provider>\n );\n}\n\n/** The live connection as the nearest {@link LiveUpdatesProvider} states it. */\nexport const useLiveUpdates = (): LiveUpdates => useContext(LiveUpdatesContext);\n"],"names":["createContext","useContext","LiveUpdatesContext","connection","LiveUpdatesProvider","value","children","Provider","useLiveUpdates"],"mappings":"AAAA;;AAEA,SAASA,aAAa,EAAkBC,UAAU,QAAQ,QAAQ;AAclE,MAAMC,mCAAqBF,cAA2B;IAAEG,YAAY;AAAO;AAE3E;;;;;CAKC,GACD,OAAO,SAASC,oBAAoB,EAClCC,KAAK,EACLC,QAAQ,EAIT;IACC,qBACE,KAACJ,mBAAmBK,QAAQ;QAACF,OAAOA;kBACjCC;;AAGP;AAEA,8EAA8E,GAC9E,OAAO,MAAME,iBAAiB,IAAmBP,WAAWC,oBAAoB"}
@@ -0,0 +1,14 @@
1
+ import { type RefObject } from 'react';
2
+ /** For specs: the document as it was before anyone touched it. */
3
+ export declare const resetArrivalForTesting: () => void;
4
+ /**
5
+ * Moves focus to a region's heading when the region arrives under a reader
6
+ * whose focus was dropped — a save that returned to the list, a delete, a
7
+ * wizard handing off to the record it made — so it never rests on `<body>`
8
+ * (ADR 0028). The heading needs `tabIndex={-1}`.
9
+ *
10
+ * Only then: on the page the reader loaded nothing has been dropped, and where
11
+ * focus is on something — a tab they clicked — it stays there.
12
+ */
13
+ export declare function useFocusOnArrival(heading: RefObject<HTMLElement | null>): void;
14
+ //# sourceMappingURL=use-focus-on-arrival.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-focus-on-arrival.d.ts","sourceRoot":"","sources":["../../../src/ui/utils/use-focus-on-arrival.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,SAAS,EAAa,MAAM,OAAO,CAAC;AAkBlD,kEAAkE;AAClE,eAAO,MAAM,sBAAsB,YAElC,CAAC;AAEF;;;;;;;;GAQG;AACH,wBAAgB,iBAAiB,CAC/B,OAAO,EAAE,SAAS,CAAC,WAAW,GAAG,IAAI,CAAC,GACrC,IAAI,CAON"}
@@ -0,0 +1,42 @@
1
+ 'use client';
2
+ import { useEffect } from 'react';
3
+ /**
4
+ * Whether the reader has pressed a key or a pointer in this document yet. A
5
+ * page that mounts before that is the one they loaded, and its focus is the
6
+ * browser's to place; one that mounts after was reached by doing something.
7
+ */ let interacted = false;
8
+ /* v8 ignore next -- a server render has no document to listen on */ if (typeof document !== 'undefined') {
9
+ const mark = ()=>{
10
+ interacted = true;
11
+ };
12
+ document.addEventListener('pointerdown', mark, {
13
+ capture: true
14
+ });
15
+ document.addEventListener('keydown', mark, {
16
+ capture: true
17
+ });
18
+ }
19
+ /** For specs: the document as it was before anyone touched it. */ export const resetArrivalForTesting = ()=>{
20
+ interacted = false;
21
+ };
22
+ /**
23
+ * Moves focus to a region's heading when the region arrives under a reader
24
+ * whose focus was dropped — a save that returned to the list, a delete, a
25
+ * wizard handing off to the record it made — so it never rests on `<body>`
26
+ * (ADR 0028). The heading needs `tabIndex={-1}`.
27
+ *
28
+ * Only then: on the page the reader loaded nothing has been dropped, and where
29
+ * focus is on something — a tab they clicked — it stays there.
30
+ */ export function useFocusOnArrival(heading) {
31
+ useEffect(()=>{
32
+ var _heading_current;
33
+ if (!interacted) return;
34
+ const active = document.activeElement;
35
+ if (active !== null && active !== document.body) return;
36
+ (_heading_current = heading.current) == null ? void 0 : _heading_current.focus();
37
+ }, [
38
+ heading
39
+ ]);
40
+ }
41
+
42
+ //# sourceMappingURL=use-focus-on-arrival.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../../src/ui/utils/use-focus-on-arrival.ts"],"sourcesContent":["'use client';\n\nimport { type RefObject, useEffect } from 'react';\n\n/**\n * Whether the reader has pressed a key or a pointer in this document yet. A\n * page that mounts before that is the one they loaded, and its focus is the\n * browser's to place; one that mounts after was reached by doing something.\n */\nlet interacted = false;\n\n/* v8 ignore next -- a server render has no document to listen on */\nif (typeof document !== 'undefined') {\n const mark = () => {\n interacted = true;\n };\n document.addEventListener('pointerdown', mark, { capture: true });\n document.addEventListener('keydown', mark, { capture: true });\n}\n\n/** For specs: the document as it was before anyone touched it. */\nexport const resetArrivalForTesting = () => {\n interacted = false;\n};\n\n/**\n * Moves focus to a region's heading when the region arrives under a reader\n * whose focus was dropped — a save that returned to the list, a delete, a\n * wizard handing off to the record it made — so it never rests on `<body>`\n * (ADR 0028). The heading needs `tabIndex={-1}`.\n *\n * Only then: on the page the reader loaded nothing has been dropped, and where\n * focus is on something — a tab they clicked — it stays there.\n */\nexport function useFocusOnArrival(\n heading: RefObject<HTMLElement | null>,\n): void {\n useEffect(() => {\n if (!interacted) return;\n const active = document.activeElement;\n if (active !== null && active !== document.body) return;\n heading.current?.focus();\n }, [heading]);\n}\n"],"names":["useEffect","interacted","document","mark","addEventListener","capture","resetArrivalForTesting","useFocusOnArrival","heading","active","activeElement","body","current","focus"],"mappings":"AAAA;AAEA,SAAyBA,SAAS,QAAQ,QAAQ;AAElD;;;;CAIC,GACD,IAAIC,aAAa;AAEjB,kEAAkE,GAClE,IAAI,OAAOC,aAAa,aAAa;IACnC,MAAMC,OAAO;QACXF,aAAa;IACf;IACAC,SAASE,gBAAgB,CAAC,eAAeD,MAAM;QAAEE,SAAS;IAAK;IAC/DH,SAASE,gBAAgB,CAAC,WAAWD,MAAM;QAAEE,SAAS;IAAK;AAC7D;AAEA,gEAAgE,GAChE,OAAO,MAAMC,yBAAyB;IACpCL,aAAa;AACf,EAAE;AAEF;;;;;;;;CAQC,GACD,OAAO,SAASM,kBACdC,OAAsC;IAEtCR,UAAU;YAIRQ;QAHA,IAAI,CAACP,YAAY;QACjB,MAAMQ,SAASP,SAASQ,aAAa;QACrC,IAAID,WAAW,QAAQA,WAAWP,SAASS,IAAI,EAAE;SACjDH,mBAAAA,QAAQI,OAAO,qBAAfJ,iBAAiBK,KAAK;IACxB,GAAG;QAACL;KAAQ;AACd"}