@parallelworks/ui 0.77.0 → 0.78.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 (262) hide show
  1. package/README.md +30 -5
  2. package/dist/access/AccessDrawer.d.ts +28 -7
  3. package/dist/access/AccessDrawer.js +216 -35
  4. package/dist/access/AccessList.d.ts +19 -1
  5. package/dist/access/AccessList.js +220 -9
  6. package/dist/access/GrantView.d.ts +3 -1
  7. package/dist/access/GrantView.js +17 -4
  8. package/dist/access/PrincipalPicker.js +5 -5
  9. package/dist/access/focus.js +1 -1
  10. package/dist/access/index.d.ts +2 -2
  11. package/dist/access/index.js +2 -2
  12. package/dist/access/strings.d.ts +18 -0
  13. package/dist/access/strings.js +20 -2
  14. package/dist/access/types.d.ts +26 -0
  15. package/dist/ai/components/AllocationSelector.js +1 -1
  16. package/dist/ai/components/AttachmentManager.js +5 -5
  17. package/dist/ai/components/AttachmentUpload.js +2 -2
  18. package/dist/ai/components/BranchNavigator.js +1 -1
  19. package/dist/ai/components/ChatActivity.js +1 -1
  20. package/dist/ai/components/ChatEmptyState.js +3 -3
  21. package/dist/ai/components/ChatInput.js +3 -5
  22. package/dist/ai/components/ChatLayout.js +1 -1
  23. package/dist/ai/components/ChatMessage.js +1 -1
  24. package/dist/ai/components/ChatMessageList.js +2 -2
  25. package/dist/ai/components/ChatSidebar.js +3 -3
  26. package/dist/ai/components/ChatThread.js +4 -4
  27. package/dist/ai/components/ComposerAddMenu.js +9 -20
  28. package/dist/ai/components/ModelSelector.js +3 -3
  29. package/dist/ai/components/PastedText.js +5 -5
  30. package/dist/ai/components/SessionView.js +1 -1
  31. package/dist/ai/components/ShareDialog.js +3 -3
  32. package/dist/ai/components/SlashMenu.d.ts +31 -4
  33. package/dist/ai/components/SlashMenu.js +36 -63
  34. package/dist/ai/components/agent/ActivityLine.js +1 -1
  35. package/dist/ai/components/agent/AgentMessageParts.js +13 -13
  36. package/dist/ai/components/conversationActions.js +3 -3
  37. package/dist/ai/core/ChatProvider.js +1 -1
  38. package/dist/ai/core/config.js +1 -1
  39. package/dist/ai/ui/ConversationSidebar.js +3 -3
  40. package/dist/ai/ui/sidebar.js +3 -3
  41. package/dist/assets/yaml.worker-DOBV2lND.js +884 -0
  42. package/dist/components/ActionMenu.d.ts +38 -9
  43. package/dist/components/ActionMenu.js +48 -22
  44. package/dist/components/Avatar.d.ts +66 -6
  45. package/dist/components/Avatar.js +130 -18
  46. package/dist/components/Breadcrumbs.d.ts +4 -0
  47. package/dist/components/Breadcrumbs.js +26 -10
  48. package/dist/components/Button.d.ts +3 -2
  49. package/dist/components/Button.js +11 -1
  50. package/dist/components/Callout.d.ts +3 -2
  51. package/dist/components/Callout.js +3 -2
  52. package/dist/components/Card.d.ts +33 -9
  53. package/dist/components/Card.js +24 -8
  54. package/dist/components/Checkbox.d.ts +3 -1
  55. package/dist/components/Checkbox.js +4 -2
  56. package/dist/components/ColorInput.js +1 -1
  57. package/dist/components/Combobox.d.ts +23 -0
  58. package/dist/components/Combobox.js +252 -86
  59. package/dist/components/ConfirmModal.d.ts +26 -2
  60. package/dist/components/ConfirmModal.js +81 -15
  61. package/dist/components/ContextMenu.d.ts +29 -2
  62. package/dist/components/ContextMenu.js +60 -8
  63. package/dist/components/CopyButton.d.ts +21 -8
  64. package/dist/components/CopyButton.js +29 -9
  65. package/dist/components/CreateModal.d.ts +16 -4
  66. package/dist/components/CreateModal.js +27 -16
  67. package/dist/components/DatePicker.d.ts +31 -1
  68. package/dist/components/DatePicker.js +178 -47
  69. package/dist/components/DescriptionList.d.ts +13 -3
  70. package/dist/components/DescriptionList.js +43 -11
  71. package/dist/components/Disclosure.d.ts +10 -2
  72. package/dist/components/Disclosure.js +18 -7
  73. package/dist/components/Drawer.js +5 -5
  74. package/dist/components/EmptyState.d.ts +12 -4
  75. package/dist/components/EmptyState.js +18 -10
  76. package/dist/components/ErrorPage.d.ts +40 -0
  77. package/dist/components/ErrorPage.js +101 -0
  78. package/dist/components/ExternalLink.d.ts +4 -1
  79. package/dist/components/ExternalLink.js +4 -5
  80. package/dist/components/Field.d.ts +21 -6
  81. package/dist/components/Field.js +32 -16
  82. package/dist/components/FileButton.d.ts +19 -0
  83. package/dist/components/FileButton.js +32 -0
  84. package/dist/components/HoverCard.d.ts +26 -6
  85. package/dist/components/HoverCard.js +11 -5
  86. package/dist/components/IconButton.js +4 -2
  87. package/dist/components/InfoTip.js +3 -3
  88. package/dist/components/Input.d.ts +33 -3
  89. package/dist/components/Input.js +149 -14
  90. package/dist/components/Kbd.d.ts +40 -0
  91. package/dist/components/Kbd.js +168 -0
  92. package/dist/components/Listbox.d.ts +103 -0
  93. package/dist/components/Listbox.js +190 -0
  94. package/dist/components/Loader.d.ts +8 -2
  95. package/dist/components/Loader.js +16 -1
  96. package/dist/components/Markdown.js +1 -1
  97. package/dist/components/MarkdownImpl.js +1 -1
  98. package/dist/components/Menu.d.ts +20 -2
  99. package/dist/components/Menu.js +48 -19
  100. package/dist/components/MenuEntries.d.ts +38 -2
  101. package/dist/components/MenuEntries.js +266 -39
  102. package/dist/components/Meter.d.ts +50 -11
  103. package/dist/components/Meter.js +71 -21
  104. package/dist/components/Modal.d.ts +9 -3
  105. package/dist/components/Modal.js +68 -29
  106. package/dist/components/OneTimeCodeInput.js +1 -1
  107. package/dist/components/PageHeader.js +111 -7
  108. package/dist/components/PageHeaderAction.d.ts +8 -3
  109. package/dist/components/PageHeaderAction.js +122 -34
  110. package/dist/components/Pagination.d.ts +13 -1
  111. package/dist/components/Pagination.js +90 -19
  112. package/dist/components/Popover.d.ts +13 -2
  113. package/dist/components/Popover.js +20 -21
  114. package/dist/components/Provider.d.ts +140 -3
  115. package/dist/components/Provider.js +142 -12
  116. package/dist/components/RefreshButton.d.ts +13 -6
  117. package/dist/components/RefreshButton.js +17 -9
  118. package/dist/components/RunStatusIcon.d.ts +16 -7
  119. package/dist/components/RunStatusIcon.js +21 -8
  120. package/dist/components/Section.d.ts +11 -4
  121. package/dist/components/Section.js +33 -22
  122. package/dist/components/SegmentedControl.d.ts +16 -3
  123. package/dist/components/SegmentedControl.js +7 -3
  124. package/dist/components/Select.d.ts +19 -3
  125. package/dist/components/Select.js +114 -68
  126. package/dist/components/SelectionCopy.d.ts +10 -0
  127. package/dist/components/SelectionCopy.js +105 -0
  128. package/dist/components/SettingModeOption.js +1 -1
  129. package/dist/components/SettingsCard.js +1 -1
  130. package/dist/components/Skeleton.js +1 -1
  131. package/dist/components/StatusBadge.d.ts +55 -10
  132. package/dist/components/StatusBadge.js +91 -15
  133. package/dist/components/StickyBar.js +1 -1
  134. package/dist/components/Table.d.ts +22 -4
  135. package/dist/components/Table.js +46 -24
  136. package/dist/components/Tabs.d.ts +9 -4
  137. package/dist/components/Tabs.js +9 -4
  138. package/dist/components/Tag.d.ts +49 -0
  139. package/dist/components/Tag.js +199 -0
  140. package/dist/components/ThemeEditor.js +1 -1
  141. package/dist/components/Toast.js +1 -1
  142. package/dist/components/ToggleGroup.d.ts +4 -1
  143. package/dist/components/ToggleGroup.js +30 -13
  144. package/dist/components/Tooltip.d.ts +9 -3
  145. package/dist/components/Tooltip.js +55 -39
  146. package/dist/components/buttonClasses.d.ts +5 -2
  147. package/dist/components/buttonClasses.js +11 -10
  148. package/dist/components/chart.d.ts +69 -0
  149. package/dist/components/chart.js +145 -0
  150. package/dist/components/comboboxItems.d.ts +7 -1
  151. package/dist/components/comboboxItems.js +6 -2
  152. package/dist/components/dismiss.d.ts +5 -1
  153. package/dist/components/dismiss.js +6 -2
  154. package/dist/components/floating.d.ts +4 -4
  155. package/dist/components/floating.js +5 -5
  156. package/dist/components/headerActionsFit.d.ts +37 -0
  157. package/dist/components/headerActionsFit.js +81 -0
  158. package/dist/components/pageHeaderStore.d.ts +3 -0
  159. package/dist/components/pageHeaderStore.js +3 -0
  160. package/dist/components/pathBreaks.js +2 -2
  161. package/dist/components/popupContainer.d.ts +17 -0
  162. package/dist/components/popupContainer.js +69 -0
  163. package/dist/components/relativeTime.d.ts +21 -10
  164. package/dist/components/relativeTime.js +50 -22
  165. package/dist/components/segmented.d.ts +18 -4
  166. package/dist/components/segmented.js +48 -6
  167. package/dist/components/typedDate.d.ts +5 -0
  168. package/dist/components/typedDate.js +50 -0
  169. package/dist/components/useClosingValue.d.ts +4 -0
  170. package/dist/components/useClosingValue.js +12 -0
  171. package/dist/components/useCopy.d.ts +6 -0
  172. package/dist/components/useCopy.js +3 -3
  173. package/dist/components/useNarrow.d.ts +2 -0
  174. package/dist/components/useNarrow.js +16 -0
  175. package/dist/editor/Editor.js +1 -1
  176. package/dist/editor/FormCodePanel.js +1 -1
  177. package/dist/editor/Monaco.js +1 -1
  178. package/dist/editor/workers.js +1 -1
  179. package/dist/file-explorer/FileExplorer.js +16 -15
  180. package/dist/file-explorer/FileExplorerContext.js +2 -2
  181. package/dist/file-explorer/FilePreview/Viewer.js +3 -3
  182. package/dist/file-explorer/FilePreview/index.js +2 -2
  183. package/dist/file-explorer/FileRowsWindow.js +3 -3
  184. package/dist/file-explorer/FileTable.js +1 -1
  185. package/dist/file-explorer/LoadMoreRow.js +1 -1
  186. package/dist/file-explorer/TreeView.js +5 -5
  187. package/dist/file-explorer/helpers.js +2 -2
  188. package/dist/file-explorer/toasts.js +2 -2
  189. package/dist/form/Fields/CheckboxGroupField.js +1 -1
  190. package/dist/form/Fields/DropdownField.js +1 -1
  191. package/dist/form/Fields/DurationField.js +2 -2
  192. package/dist/form/Fields/MultiDropdownField.js +1 -1
  193. package/dist/form/Fields/NumberField.js +1 -1
  194. package/dist/form/Fields/ObjectField.js +1 -1
  195. package/dist/form/Fields/RangeField.js +1 -1
  196. package/dist/form/Fields/StringField.js +1 -1
  197. package/dist/form/Form.js +3 -3
  198. package/dist/form/FormLayout.js +1 -1
  199. package/dist/form/Wizard/WizardContainer.js +1 -1
  200. package/dist/form/Wizard/WizardNavigation.js +1 -1
  201. package/dist/form/Wizard/WizardStepContent.js +1 -1
  202. package/dist/form/fieldRegistry.js +1 -1
  203. package/dist/form/formEditing.js +1 -1
  204. package/dist/format.d.ts +17 -7
  205. package/dist/format.js +39 -16
  206. package/dist/graph/AnnotationBanner.js +2 -2
  207. package/dist/graph/Connectors.js +5 -5
  208. package/dist/graph/DependencyGraph.js +10 -9
  209. package/dist/graph/GraphEditor.js +4 -4
  210. package/dist/graph/GraphEditorDialogs.js +13 -12
  211. package/dist/graph/GraphNode.js +1 -1
  212. package/dist/graph/InputDialog.js +5 -5
  213. package/dist/graph/InputsEditor.js +7 -7
  214. package/dist/graph/MatrixGroup.js +2 -2
  215. package/dist/graph/ProblemsButton.js +3 -3
  216. package/dist/graph/Reveal.js +1 -1
  217. package/dist/graph/RunWarningsList.js +1 -1
  218. package/dist/graph/ShortcutsButton.js +25 -8
  219. package/dist/graph/StepSummaries.js +1 -1
  220. package/dist/graph/TreeView.js +2 -2
  221. package/dist/graph/WorkflowSettingsDialog.js +3 -3
  222. package/dist/graph/editorFields.js +3 -3
  223. package/dist/graph/editorPrimitives.js +1 -1
  224. package/dist/graph/editorRows.js +5 -5
  225. package/dist/graph/fieldProblems.js +2 -2
  226. package/dist/graph/inputRefs.js +7 -6
  227. package/dist/graph/stepWith.js +2 -2
  228. package/dist/index.d.ts +30 -21
  229. package/dist/index.js +20 -12
  230. package/dist/list/ActionToolbar.d.ts +4 -0
  231. package/dist/list/ActionToolbar.js +35 -12
  232. package/dist/list/ListFilterBar.d.ts +42 -0
  233. package/dist/list/ListFilterBar.js +789 -0
  234. package/dist/list/ListPage.d.ts +42 -4
  235. package/dist/list/ListPage.js +56 -22
  236. package/dist/list/ListTable.d.ts +27 -3
  237. package/dist/list/ListTable.js +48 -23
  238. package/dist/list/ListToolbar.d.ts +31 -0
  239. package/dist/list/ListToolbar.js +43 -0
  240. package/dist/list/ListViewControls.d.ts +8 -4
  241. package/dist/list/ListViewControls.js +22 -14
  242. package/dist/list/UserHoverCard.d.ts +8 -2
  243. package/dist/list/UserHoverCard.js +26 -13
  244. package/dist/list/filterModel.d.ts +122 -0
  245. package/dist/list/filterModel.js +308 -0
  246. package/dist/list/index.d.ts +6 -2
  247. package/dist/list/index.js +6 -2
  248. package/dist/list/listSearch.d.ts +1 -1
  249. package/dist/list/listSearch.js +8 -4
  250. package/dist/list/listView.d.ts +24 -2
  251. package/dist/list/listView.js +47 -45
  252. package/dist/list/rowMenu.d.ts +10 -5
  253. package/dist/list/rowMenu.js +17 -7
  254. package/dist/list/useListFilters.d.ts +68 -0
  255. package/dist/list/useListFilters.js +252 -0
  256. package/dist/logviewer/LogViewer.js +4 -4
  257. package/dist/styles/theme.css +94 -0
  258. package/dist/styles.css +1 -1
  259. package/package.json +11 -11
  260. package/dist/assets/yaml.worker-k9AdOqzf.js +0 -1441
  261. package/dist/components/fullscreen.d.ts +0 -4
  262. package/dist/components/fullscreen.js +0 -18
@@ -6,11 +6,11 @@ var enterExit = "origin-(--transform-origin) transition-[transform,opacity] dura
6
6
  var panelClassName = `rounded-lg border border-(--theme-border) bg-(--theme-app-bg) text-sm text-(--theme-app) shadow-(--theme-shadow-lg) outline-none ${enterExit}`;
7
7
  /** A row in a panel, inside its `panelListClassName` gutter; its text lines up with a field's. */
8
8
  var panelItemClassName = "flex w-full cursor-pointer select-none items-center gap-2 rounded-md px-2 py-1.5 text-left outline-none data-highlighted:bg-(--theme-hover) data-highlighted:text-(--theme-app) data-disabled:cursor-not-allowed data-disabled:opacity-50";
9
- /** A tooltip's or infotip's small inverted surface. */
10
- var tipClassName = `rounded-md bg-(--theme-app) px-2 py-1 text-xs text-(--theme-app-bg) shadow-(--theme-shadow) whitespace-pre-line break-words data-instant:transition-none ${enterExit}`;
11
- var tipArrowClassName = "size-2 rotate-45 bg-(--theme-app) data-[side=top]:-bottom-1 data-[side=bottom]:-top-1 data-[side=left]:-right-1 data-[side=right]:-left-1";
9
+ /** A tooltip's, infotip's or popover tip's surface: a small panel in the page's colors, so a
10
+ * hint reads as part of the page rather than a dark patch over it. */
11
+ var tipClassName = `max-w-64 rounded-lg border border-(--theme-border) bg-(--theme-panel-bg) px-2.5 py-1.5 text-xs text-(--theme-app) shadow-(--theme-shadow-lg) whitespace-pre-line break-words data-instant:transition-none ${enterExit}`;
12
12
  /** A hover card's surface; the card sets its own width and padding. */
13
- var cardClassName = `overflow-hidden rounded-xl border border-(--theme-border) bg-(--theme-panel-bg) text-(--theme-app) shadow-(--theme-shadow-lg) outline-none ${enterExit}`;
13
+ var cardClassName = `overflow-hidden rounded-lg border border-(--theme-border) bg-(--theme-panel-bg) text-(--theme-app) shadow-(--theme-shadow-lg) outline-none ${enterExit}`;
14
14
  function pointRect(x, y) {
15
15
  return {
16
16
  x,
@@ -40,4 +40,4 @@ function toPositionerAnchor(at) {
40
40
  };
41
41
  }
42
42
  //#endregion
43
- export { cardClassName, panelClassName, panelItemClassName, positionerClassName, tipArrowClassName, tipClassName, toPositionerAnchor };
43
+ export { cardClassName, panelClassName, panelItemClassName, positionerClassName, tipClassName, toPositionerAnchor };
@@ -0,0 +1,37 @@
1
+ import type { MenuEntry } from './MenuEntries.js';
2
+ /**
3
+ * Which of the page header's actions have moved into its "…" menu, and what
4
+ * each would be as a row of that menu. The actions render in the page's tree
5
+ * and the bar measures them in its own, so they meet here: each action
6
+ * registers its menu row and hides while it has moved, and the bar decides
7
+ * which move and lists them.
8
+ */
9
+ export declare class HeaderActionsFit {
10
+ private entries;
11
+ private overflow;
12
+ private version;
13
+ private listeners;
14
+ subscribe: (listener: () => void) => (() => void);
15
+ /** Changes whenever the menu would: what moved, or a moved action's row. */
16
+ getVersion: () => number;
17
+ register(id: string, entry: MenuEntry): void;
18
+ unregister(id: string): void;
19
+ isOverflowed(id: string): boolean;
20
+ /** The moved actions' rows, in the bar's order. */
21
+ menu(): MenuEntry[];
22
+ setOverflow(ids: readonly string[]): void;
23
+ private changed;
24
+ }
25
+ /** Set around the header's `actions`, so the actions there can move into its
26
+ * menu; title actions sit beside the title and never do. */
27
+ export declare const HeaderActionsFitContext: import("react").Context<HeaderActionsFit | null>;
28
+ /**
29
+ * The trailing actions to move into the "…" menu so the rest fit in `room`
30
+ * pixels, last first, or none when everything fits. `shown` is the actions'
31
+ * width as they are now, with `moved` already in the menu, so what they would
32
+ * take with nothing moved is worked out from it and each action's width.
33
+ */
34
+ export declare function overflowingActions(items: readonly {
35
+ id: string;
36
+ width: number;
37
+ }[], shown: number, moved: readonly string[], room: number): string[];
@@ -0,0 +1,81 @@
1
+ import { createContext } from "react";
2
+ //#region src/components/headerActionsFit.ts
3
+ /**
4
+ * Which of the page header's actions have moved into its "…" menu, and what
5
+ * each would be as a row of that menu. The actions render in the page's tree
6
+ * and the bar measures them in its own, so they meet here: each action
7
+ * registers its menu row and hides while it has moved, and the bar decides
8
+ * which move and lists them.
9
+ */
10
+ var HeaderActionsFit = class {
11
+ entries = /* @__PURE__ */ new Map();
12
+ overflow = [];
13
+ version = 0;
14
+ listeners = /* @__PURE__ */ new Set();
15
+ subscribe = (listener) => {
16
+ this.listeners.add(listener);
17
+ return () => {
18
+ this.listeners.delete(listener);
19
+ };
20
+ };
21
+ /** Changes whenever the menu would: what moved, or a moved action's row. */
22
+ getVersion = () => this.version;
23
+ register(id, entry) {
24
+ this.entries.set(id, entry);
25
+ if (this.overflow.includes(id)) this.changed();
26
+ }
27
+ unregister(id) {
28
+ this.entries.delete(id);
29
+ if (this.overflow.includes(id)) {
30
+ this.overflow = this.overflow.filter((other) => other !== id);
31
+ this.changed();
32
+ }
33
+ }
34
+ isOverflowed(id) {
35
+ return this.overflow.includes(id);
36
+ }
37
+ /** The moved actions' rows, in the bar's order. */
38
+ menu() {
39
+ return this.overflow.flatMap((id) => {
40
+ const entry = this.entries.get(id);
41
+ return entry ? [entry] : [];
42
+ });
43
+ }
44
+ setOverflow(ids) {
45
+ if (ids.length === this.overflow.length && ids.every((id, i) => this.overflow[i] === id)) return;
46
+ this.overflow = ids;
47
+ this.changed();
48
+ }
49
+ changed() {
50
+ this.version++;
51
+ for (const listener of this.listeners) listener();
52
+ }
53
+ };
54
+ /** Set around the header's `actions`, so the actions there can move into its
55
+ * menu; title actions sit beside the title and never do. */
56
+ var HeaderActionsFitContext = createContext(null);
57
+ var ACTION_GAP = 4;
58
+ /**
59
+ * The trailing actions to move into the "…" menu so the rest fit in `room`
60
+ * pixels, last first, or none when everything fits. `shown` is the actions'
61
+ * width as they are now, with `moved` already in the menu, so what they would
62
+ * take with nothing moved is worked out from it and each action's width.
63
+ */
64
+ function overflowingActions(items, shown, moved, room) {
65
+ const width = new Map(items.map((item) => [item.id, item.width]));
66
+ let total = shown;
67
+ if (moved.length > 0) {
68
+ total += moved.reduce((sum, id) => sum + (width.get(id) ?? 0) + ACTION_GAP, 0);
69
+ total -= 28;
70
+ }
71
+ const next = [];
72
+ for (let i = items.length - 1; i >= 0 && total > room + .5; i--) {
73
+ const item = items[i];
74
+ if (next.length === 0) total += 28;
75
+ total -= item.width + ACTION_GAP;
76
+ next.unshift(item.id);
77
+ }
78
+ return next;
79
+ }
80
+ //#endregion
81
+ export { HeaderActionsFit, HeaderActionsFitContext, overflowingActions };
@@ -1,4 +1,5 @@
1
1
  import type { Breadcrumb } from './Breadcrumbs.js';
2
+ import { HeaderActionsFit } from './headerActionsFit.js';
2
3
  export type PageHeaderRegion = 'titleActions' | 'actions' | 'toolbar';
3
4
  /** The nodes a declaration portals its regions into. They are created with the
4
5
  * declaration, before any bar exists, so its content renders on the first pass
@@ -25,6 +26,8 @@ export declare function sameCrumbs(a: readonly Breadcrumb[] | undefined, b: read
25
26
  * provider.
26
27
  */
27
28
  export declare class PageHeaderStore {
29
+ /** Which of the declared actions the bar has moved into its "…" menu. */
30
+ readonly actionsFit: HeaderActionsFit;
28
31
  private declarations;
29
32
  private listeners;
30
33
  private snapshot;
@@ -1,3 +1,4 @@
1
+ import { HeaderActionsFit } from "./headerActionsFit.js";
1
2
  //#region src/components/pageHeaderStore.ts
2
3
  var NO_NODES = [];
3
4
  var NO_CRUMBS = [];
@@ -26,6 +27,8 @@ function sameNodes(a, b) {
26
27
  * provider.
27
28
  */
28
29
  var PageHeaderStore = class {
30
+ /** Which of the declared actions the bar has moved into its "…" menu. */
31
+ actionsFit = new HeaderActionsFit();
29
32
  declarations = /* @__PURE__ */ new Map();
30
33
  listeners = /* @__PURE__ */ new Set();
31
34
  snapshot = EMPTY;
@@ -1,5 +1,5 @@
1
+ import { Children, Fragment, cloneElement, isValidElement } from "react";
1
2
  import { jsx, jsxs } from "react/jsx-runtime";
2
- import { Children, Fragment as Fragment$1, cloneElement, isValidElement } from "react";
3
3
  //#region src/components/pathBreaks.tsx
4
4
  var SEPARATOR = /(?<=[/\\])(?![/\\])/;
5
5
  /** The pieces of `text` with a break allowed after each, split after each path
@@ -10,7 +10,7 @@ function pathPieces(text) {
10
10
  function breakText(text) {
11
11
  const pieces = pathPieces(text);
12
12
  if (pieces.length < 2) return text;
13
- return pieces.map((piece, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [piece, i < pieces.length - 1 && /* @__PURE__ */ jsx("wbr", {})] }, i));
13
+ return pieces.map((piece, i) => /* @__PURE__ */ jsxs(Fragment, { children: [piece, i < pieces.length - 1 && /* @__PURE__ */ jsx("wbr", {})] }, i));
14
14
  }
15
15
  /** `node` with a place to wrap after each path separator in its text: a string,
16
16
  * or an element, such as a link, whose child is one. Deeper content is left as
@@ -0,0 +1,17 @@
1
+ import { type RefObject } from 'react';
2
+ /** Where a popup opening now from `trigger` belongs. A modal dialog sits in the
3
+ * browser's top layer, above any z-index, and leaves the rest of the page inert,
4
+ * so a popup portaled to the body would open under it, out of reach. Focus stays
5
+ * inside the dialog, so it also places a popup opened by code rather than by the
6
+ * reader. A fullscreen element is all the browser shows. Null keeps the default:
7
+ * the popup it opened from, or the body. */
8
+ export declare function popupContainerFor(trigger: Element | null): HTMLElement | null;
9
+ export interface PopupPortalProps {
10
+ /** Read when the portal mounts, which is when its popup opens. */
11
+ container: RefObject<HTMLElement | null>;
12
+ className: string;
13
+ }
14
+ /** Props for a popup's portal. The popup opens inside the innermost open modal
15
+ * `<dialog>` that holds its trigger, or else the fullscreen element, or else
16
+ * the body, so it shows above whatever the trigger sits in. */
17
+ export declare function usePopupContainer(): PopupPortalProps;
@@ -0,0 +1,69 @@
1
+ import { useSyncExternalStore } from "react";
2
+ //#region src/components/popupContainer.ts
3
+ var lastTarget = null;
4
+ var tracking = false;
5
+ var remember = (event) => {
6
+ if (event.target instanceof Element) lastTarget = event.target;
7
+ };
8
+ function track() {
9
+ if (tracking) return;
10
+ tracking = true;
11
+ for (const type of [
12
+ "pointerdown",
13
+ "pointerover",
14
+ "focusin",
15
+ "keydown",
16
+ "contextmenu"
17
+ ]) document.addEventListener(type, remember, {
18
+ capture: true,
19
+ passive: true
20
+ });
21
+ }
22
+ function subscribe(onChange) {
23
+ track();
24
+ document.addEventListener("fullscreenchange", onChange);
25
+ return () => document.removeEventListener("fullscreenchange", onChange);
26
+ }
27
+ function fullscreenElement() {
28
+ const element = document.fullscreenElement;
29
+ return element instanceof HTMLElement ? element : void 0;
30
+ }
31
+ /** The innermost `<dialog>` shown with `showModal()` that holds the node. */
32
+ function modalDialogAround(node) {
33
+ for (let dialog = node?.closest("dialog"); dialog; dialog = dialog.parentElement?.closest("dialog")) if (dialog.open && dialog.matches(":modal")) return dialog;
34
+ }
35
+ /** Where a popup opening now from `trigger` belongs. A modal dialog sits in the
36
+ * browser's top layer, above any z-index, and leaves the rest of the page inert,
37
+ * so a popup portaled to the body would open under it, out of reach. Focus stays
38
+ * inside the dialog, so it also places a popup opened by code rather than by the
39
+ * reader. A fullscreen element is all the browser shows. Null keeps the default:
40
+ * the popup it opened from, or the body. */
41
+ function popupContainerFor(trigger) {
42
+ return modalDialogAround(trigger?.isConnected ? trigger : null) ?? modalDialogAround(document.activeElement) ?? fullscreenElement() ?? null;
43
+ }
44
+ var portalClassName = "[dialog>&]:fixed [dialog>&]:top-0 [dialog>&]:left-0 [dialog>&]:z-(--layer-popover)";
45
+ function portalProps() {
46
+ return {
47
+ container: { get current() {
48
+ return popupContainerFor(lastTarget);
49
+ } },
50
+ className: portalClassName
51
+ };
52
+ }
53
+ var onPage = portalProps();
54
+ var inFullscreen = /* @__PURE__ */ new WeakMap();
55
+ /** Props for a popup's portal. The popup opens inside the innermost open modal
56
+ * `<dialog>` that holds its trigger, or else the fullscreen element, or else
57
+ * the body, so it shows above whatever the trigger sits in. */
58
+ function usePopupContainer() {
59
+ const fullscreen = useSyncExternalStore(subscribe, fullscreenElement, () => void 0);
60
+ if (!fullscreen) return onPage;
61
+ let props = inFullscreen.get(fullscreen);
62
+ if (!props) {
63
+ props = portalProps();
64
+ inFullscreen.set(fullscreen, props);
65
+ }
66
+ return props;
67
+ }
68
+ //#endregion
69
+ export { popupContainerFor, usePopupContainer };
@@ -1,4 +1,5 @@
1
- import { type TimeValue } from '../format.js';
1
+ import { type ReactNode } from 'react';
2
+ import { type DateTimeStyle, type TimeValue } from '../format.js';
2
3
  export type { TimeValue };
3
4
  /** Something worked out from the time, kept current by one clock that ticks
4
5
  * each second for every component reading it. A component re-renders only
@@ -7,20 +8,30 @@ export type { TimeValue };
7
8
  * `running` is false the component leaves the clock and `select` sees the
8
9
  * last tick's time. */
9
10
  export declare function useClock<T>(select: (now: number) => T, running?: boolean): T;
10
- /** `long` reads "5 minutes ago" in the host's locale; `compact` reads "5m",
11
- * for a row with little room. */
11
+ /** `long` reads "5 minutes ago" or "yesterday" in the host's locale (UIProvider's `locale`);
12
+ * `compact` reads "5m", "in 3h", then a date such as "Sep 4" once a week away, for a row with
13
+ * little room. */
12
14
  export type RelativeTimeFormat = 'long' | 'compact';
13
15
  /** How long ago, or until, `value` is, kept current as time passes. Empty for
14
16
  * a missing or invalid time. */
15
17
  export declare function useRelativeTime(value: TimeValue, format?: RelativeTimeFormat): string;
16
- /** A time shown relative to now ("5 minutes ago", or "5m" compact), counting
17
- * on while it stays on screen, with the exact time on hover. Renders nothing
18
- * for a missing time. */
19
- export declare function RelativeTime({ value, format, className, }: {
18
+ export interface RelativeTimeProps {
20
19
  value: TimeValue;
21
- format?: RelativeTimeFormat;
22
- className?: string;
23
- }): import("react").JSX.Element | null;
20
+ format?: RelativeTimeFormat | undefined;
21
+ /** Shown in place of a missing or invalid time, such as "Never" or "—". Default nothing. */
22
+ fallback?: ReactNode;
23
+ /** How the exact time reads: in the tooltip, or in the text with `show="absolute"`. Default
24
+ * `seconds`; `full` adds the time zone. False leaves out the tooltip. */
25
+ exact?: DateTimeStyle | false | undefined;
26
+ /** `absolute` shows the exact time, with the relative one on hover, for a record read for
27
+ * its moment rather than its age. Default `relative`. */
28
+ show?: 'relative' | 'absolute' | undefined;
29
+ className?: string | undefined;
30
+ }
31
+ /** A time shown relative to now ("5 minutes ago", or "5m" compact), counting
32
+ * on while it stays on screen, with the exact time on hover. Renders the
33
+ * `fallback`, or nothing, for a missing time. */
34
+ export declare function RelativeTime({ value, format, fallback, exact, show, className, }: RelativeTimeProps): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null;
24
35
  /** Milliseconds from `start` to `end`, or to now while `running`, in whole
25
36
  * seconds so it changes at most once a second. Running defaults to having no
26
37
  * end; pass false for something that stopped without recording when. Null
@@ -1,14 +1,15 @@
1
1
  import { Tooltip } from "./Tooltip.js";
2
- import { useStrings } from "./Provider.js";
2
+ import { useLocale, useStrings } from "./Provider.js";
3
3
  import { formatDateTime, formatDuration, toDateTime } from "../format.js";
4
- import { jsx } from "react/jsx-runtime";
5
4
  import { useRef, useState, useSyncExternalStore } from "react";
5
+ import { jsx } from "react/jsx-runtime";
6
6
  import { DateTime, Duration } from "luxon";
7
7
  //#region src/components/relativeTime.tsx
8
8
  var SECOND = 1e3;
9
9
  var MINUTE = 60 * SECOND;
10
10
  var HOUR = 60 * MINUTE;
11
11
  var DAY = 24 * HOUR;
12
+ var WEEK = 7 * DAY;
12
13
  var listeners = /* @__PURE__ */ new Set();
13
14
  var timer;
14
15
  var now = Date.now();
@@ -40,14 +41,35 @@ function useClock(select, running = true) {
40
41
  const snapshot = () => select(now);
41
42
  return useSyncExternalStore(running ? subscribe : stopped, snapshot, snapshot);
42
43
  }
43
- function compactLabel(age, t) {
44
- const minutes = Math.floor(Math.abs(age) / MINUTE);
44
+ function compactLabel(at, age, t, locale) {
45
+ const size = Math.abs(age);
46
+ if (size >= WEEK) return t.date(new Date(at), locale);
47
+ const minutes = Math.floor(size / MINUTE);
45
48
  if (minutes < 1) return t.now;
46
- if (minutes < 60) return t.minutes(minutes);
47
49
  const hours = Math.floor(minutes / 60);
48
- if (hours < 24) return t.hours(hours);
49
- const days = Math.floor(hours / 24);
50
- return days < 365 ? t.days(days) : t.years(Math.floor(days / 365));
50
+ const label = minutes < 60 ? t.minutes(minutes) : hours < 24 ? t.hours(hours) : t.days(Math.floor(hours / 24));
51
+ return age < 0 ? t.inFuture(label) : label;
52
+ }
53
+ var LONG_UNITS = [
54
+ ["year", 365 * DAY],
55
+ ["month", 30 * DAY],
56
+ ["week", WEEK],
57
+ ["day", DAY],
58
+ ["hour", HOUR],
59
+ ["minute", MINUTE],
60
+ ["second", SECOND]
61
+ ];
62
+ var relativeFormats = /* @__PURE__ */ new Map();
63
+ function longLabel(age, locale) {
64
+ const tag = locale ?? DateTime.now().locale ?? "en-US";
65
+ let format = relativeFormats.get(tag);
66
+ if (!format) {
67
+ format = new Intl.RelativeTimeFormat(tag, { numeric: "auto" });
68
+ relativeFormats.set(tag, format);
69
+ }
70
+ const size = Math.abs(age);
71
+ const [unit, length] = LONG_UNITS.find(([, length]) => size >= length) ?? ["second", SECOND];
72
+ return format.format(-Math.trunc(age / length), unit);
51
73
  }
52
74
  function unitOf(age) {
53
75
  const size = Math.abs(age);
@@ -57,15 +79,16 @@ function unitOf(age) {
57
79
  * a missing or invalid time. */
58
80
  function useRelativeTime(value, format = "long") {
59
81
  const t = useStrings().time;
82
+ const locale = useLocale();
60
83
  const at = toDateTime(value)?.toMillis() ?? null;
61
84
  const last = useRef(null);
62
85
  return useClock((current) => {
63
86
  if (at === null) return "";
64
87
  const age = current - at;
65
88
  const unit = unitOf(age);
66
- const key = `${format}:${at}:${unit}:${Math.trunc(age / unit)}`;
89
+ const key = `${format}:${locale}:${at}:${unit}:${Math.trunc(age / unit)}`;
67
90
  if (last.current?.key !== key) {
68
- const label = format === "compact" ? compactLabel(age, t) : Math.abs(age) < SECOND ? t.now : DateTime.fromMillis(at).toRelative({ base: DateTime.fromMillis(current) }) ?? "";
91
+ const label = format === "compact" ? compactLabel(at, age, t, locale) : Math.abs(age) < SECOND ? t.now : longLabel(age, locale);
69
92
  last.current = {
70
93
  key,
71
94
  label
@@ -75,20 +98,24 @@ function useRelativeTime(value, format = "long") {
75
98
  }, at !== null);
76
99
  }
77
100
  /** A time shown relative to now ("5 minutes ago", or "5m" compact), counting
78
- * on while it stays on screen, with the exact time on hover. Renders nothing
79
- * for a missing time. */
80
- function RelativeTime({ value, format = "long", className }) {
101
+ * on while it stays on screen, with the exact time on hover. Renders the
102
+ * `fallback`, or nothing, for a missing time. */
103
+ function RelativeTime({ value, format = "long", fallback = null, exact = "seconds", show = "relative", className }) {
81
104
  const label = useRelativeTime(value, format);
105
+ const locale = useLocale();
82
106
  const time = toDateTime(value);
83
- if (!label || !time) return null;
84
- return /* @__PURE__ */ jsx(Tooltip, {
85
- content: formatDateTime(time, "seconds"),
86
- children: /* @__PURE__ */ jsx("time", {
87
- dateTime: time.toISO() ?? void 0,
88
- className,
89
- children: label
90
- })
107
+ if (!label || !time) return fallback;
108
+ const absolute = formatDateTime(time, exact || "seconds", locale);
109
+ const [text, tip] = show === "absolute" ? [absolute, label] : [label, exact ? absolute : null];
110
+ const element = /* @__PURE__ */ jsx("time", {
111
+ dateTime: time.toISO() ?? void 0,
112
+ className,
113
+ children: text
91
114
  });
115
+ return tip ? /* @__PURE__ */ jsx(Tooltip, {
116
+ content: tip,
117
+ children: element
118
+ }) : element;
92
119
  }
93
120
  /** Milliseconds from `start` to `end`, or to now while `running`, in whole
94
121
  * seconds so it changes at most once a second. Running defaults to having no
@@ -104,11 +131,12 @@ function useElapsed(start, end, running = toDateTime(end) === null) {
104
131
  * Renders nothing without a start. */
105
132
  function Elapsed({ start, end, running, className }) {
106
133
  const elapsed = useElapsed(start, end, running);
134
+ const locale = useLocale();
107
135
  if (elapsed === null) return null;
108
136
  return /* @__PURE__ */ jsx("time", {
109
137
  dateTime: Duration.fromMillis(elapsed).toISO() ?? void 0,
110
138
  className,
111
- children: formatDuration(elapsed)
139
+ children: formatDuration(elapsed, locale)
112
140
  });
113
141
  }
114
142
  //#endregion
@@ -2,16 +2,30 @@ import type { ReactNode } from 'react';
2
2
  /** The parts SegmentedControl and segmented Tabs share, so a picker and a set
3
3
  * of views beside it look the same. */
4
4
  export interface SegmentLayout {
5
- /** Segments share the container's width equally. */
6
- fill?: boolean | undefined;
5
+ /** Segments share the container's width equally. `narrow` does so only on a
6
+ * phone-width screen and sits at its own width from the `sm` breakpoint,
7
+ * such as a choice at the head of a form. */
8
+ fill?: boolean | 'narrow' | undefined;
7
9
  /** Segments stack as icons with their labels in tooltips, for a collapsed rail. */
8
10
  iconOnly?: boolean | undefined;
11
+ /** `sm` fits a page header, a toolbar or a pane's head beside other small
12
+ * controls; `md` stands on its own. */
13
+ size?: 'sm' | 'md' | undefined;
9
14
  }
15
+ /** A segment's or tab's tone, for a view that needs attention, such as what failed. */
16
+ export type SegmentTone = 'warning' | 'danger';
10
17
  export declare function segmentGroupClassName({ fill, iconOnly }: SegmentLayout): string;
11
- export declare function segmentClassName(current: boolean, { fill, iconOnly }: SegmentLayout): string;
12
- export declare function SegmentContent({ label, icon, badge, iconOnly, }: {
18
+ export declare function segmentClassName(current: boolean, { fill, iconOnly, size }: SegmentLayout, tone?: SegmentTone | undefined): string;
19
+ /** How many things a tab or segment holds, as a pill after its label. */
20
+ export declare function CountPill({ count, tone }: {
21
+ count: number;
22
+ tone?: SegmentTone | undefined;
23
+ }): import("react").JSX.Element;
24
+ export declare function SegmentContent({ label, icon, badge, count, tone, iconOnly, }: {
13
25
  label: string;
14
26
  icon?: ReactNode;
15
27
  badge?: ReactNode;
28
+ count?: number | undefined;
29
+ tone?: SegmentTone | undefined;
16
30
  iconOnly?: boolean | undefined;
17
31
  }): import("react").JSX.Element;
@@ -1,22 +1,64 @@
1
1
  import { cx } from "../cx.js";
2
+ import { useLocale } from "./Provider.js";
3
+ import { formatCount } from "../format.js";
2
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
3
5
  //#region src/components/segmented.tsx
6
+ var fillKey = (fill) => fill === "narrow" ? "narrow" : fill ? "fill" : "none";
7
+ var fillGroup = {
8
+ fill: "grid auto-cols-fr grid-flow-col",
9
+ narrow: "grid auto-cols-fr grid-flow-col sm:inline-flex",
10
+ none: "inline-flex"
11
+ };
12
+ var segmentSizes = {
13
+ md: {
14
+ fill: "h-7 gap-1.5 px-1.5 text-[13px]",
15
+ narrow: "h-7 gap-1.5 px-1.5 text-[13px] sm:px-2.5",
16
+ none: "h-7 gap-1.5 px-2.5 text-[13px]"
17
+ },
18
+ sm: {
19
+ fill: "h-6 gap-1 px-1.5 text-xs",
20
+ narrow: "h-6 gap-1 px-1.5 text-xs sm:px-2",
21
+ none: "h-6 gap-1 px-2 text-xs"
22
+ }
23
+ };
24
+ var toneText = {
25
+ warning: "text-(--theme-warning)",
26
+ danger: "text-(--theme-error-text)"
27
+ };
28
+ var tonePill = {
29
+ warning: "bg-(--theme-warning-muted) text-(--theme-warning)",
30
+ danger: "bg-(--theme-error-muted) text-(--theme-error-text)",
31
+ none: "bg-(--theme-muted-text-color)/12 text-(--theme-muted-text-color)"
32
+ };
4
33
  function segmentGroupClassName({ fill, iconOnly }) {
5
- return cx("gap-0.5", iconOnly ? "flex flex-col items-center" : cx("rounded-lg theme-muted-panel p-0.5", fill ? "grid auto-cols-fr grid-flow-col" : "inline-flex"));
34
+ return cx("gap-0.5", iconOnly ? "flex flex-col items-center" : cx("rounded-lg theme-muted-panel p-0.5", fillGroup[fillKey(fill)]));
6
35
  }
7
- function segmentClassName(current, { fill, iconOnly }) {
8
- return cx("relative flex min-w-0 cursor-pointer items-center justify-center rounded-md font-medium transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-(--theme-focus) data-disabled:cursor-not-allowed data-disabled:opacity-50", current ? "bg-(--theme-panel-bg) theme-text shadow-sm ring-1 ring-(--theme-border)" : "theme-muted-text hover:theme-text hover:bg-(--theme-hover)", iconOnly ? "h-8 w-8" : cx("h-7 gap-1.5 text-[13px]", fill ? "px-1.5" : "px-2.5"));
36
+ function segmentClassName(current, { fill, iconOnly, size = "md" }, tone) {
37
+ return cx("relative flex min-w-0 cursor-pointer items-center justify-center rounded-md font-medium transition-colors duration-150 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-(--theme-focus) data-disabled:cursor-not-allowed data-disabled:opacity-50", current ? "bg-(--theme-panel-bg) shadow-sm ring-1 ring-(--theme-border)" : "hover:not-data-disabled:bg-(--theme-hover)", tone ? toneText[tone] : current ? "theme-text" : "theme-muted-text hover:not-data-disabled:theme-text", iconOnly ? "h-8 w-8" : segmentSizes[size][fillKey(fill)]);
9
38
  }
10
- function SegmentContent({ label, icon, badge, iconOnly }) {
39
+ /** How many things a tab or segment holds, as a pill after its label. */
40
+ function CountPill({ count, tone }) {
41
+ const locale = useLocale();
42
+ return /* @__PURE__ */ jsx("span", {
43
+ className: cx("inline-flex h-4 min-w-4 shrink-0 items-center justify-center rounded-full px-1.5 text-[11px] font-medium tabular-nums", tonePill[tone ?? "none"]),
44
+ children: formatCount(count, locale)
45
+ });
46
+ }
47
+ function SegmentContent({ label, icon, badge, count, tone, iconOnly }) {
48
+ const locale = useLocale();
11
49
  return /* @__PURE__ */ jsxs(Fragment, { children: [
12
50
  icon,
13
51
  iconOnly ? /* @__PURE__ */ jsx("span", {
14
52
  className: "sr-only",
15
- children: label
53
+ children: count === void 0 ? label : `${label} ${formatCount(count, locale)}`
16
54
  }) : /* @__PURE__ */ jsx("span", {
17
55
  className: "truncate",
18
56
  children: label
19
57
  }),
58
+ !iconOnly && count !== void 0 && /* @__PURE__ */ jsx(CountPill, {
59
+ count,
60
+ tone
61
+ }),
20
62
  badge && (iconOnly ? /* @__PURE__ */ jsx("span", {
21
63
  className: "absolute top-1 right-1 inline-flex",
22
64
  children: badge
@@ -24,4 +66,4 @@ function SegmentContent({ label, icon, badge, iconOnly }) {
24
66
  ] });
25
67
  }
26
68
  //#endregion
27
- export { SegmentContent, segmentClassName, segmentGroupClassName };
69
+ export { CountPill, SegmentContent, segmentClassName, segmentGroupClassName };
@@ -0,0 +1,5 @@
1
+ /** The local day typed text names: `yyyy-mm-dd`, or the locale's numeric date in its own
2
+ * order, such as `10/30/2026` or `30.10.26`; a day and month alone fall in this year. */
3
+ export declare function parseTypedDate(text: string, locale: string | undefined, today?: Date): Date | undefined;
4
+ /** A day as the locale writes it in numbers, the form `parseTypedDate` reads back. */
5
+ export declare function formatNumericDate(date: Date, locale: string | undefined): string;
@@ -0,0 +1,50 @@
1
+ //#region src/components/typedDate.ts
2
+ var orders = /* @__PURE__ */ new Map();
3
+ function partOrder(locale) {
4
+ let order = orders.get(locale);
5
+ if (!order) {
6
+ order = new Intl.DateTimeFormat(locale, {
7
+ year: "numeric",
8
+ month: "numeric",
9
+ day: "numeric"
10
+ }).formatToParts(new Date(2001, 10, 22)).map((part) => part.type).filter((type) => type === "year" || type === "month" || type === "day");
11
+ orders.set(locale, order);
12
+ }
13
+ return order;
14
+ }
15
+ function calendarDay(year, month, day) {
16
+ const date = new Date(year, month - 1, day);
17
+ return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day ? date : void 0;
18
+ }
19
+ /** The local day typed text names: `yyyy-mm-dd`, or the locale's numeric date in its own
20
+ * order, such as `10/30/2026` or `30.10.26`; a day and month alone fall in this year. */
21
+ function parseTypedDate(text, locale, today = /* @__PURE__ */ new Date()) {
22
+ const trimmed = text.trim();
23
+ const iso = trimmed.match(/^(\d{4})-(\d{1,2})-(\d{1,2})$/);
24
+ if (iso) return calendarDay(Number(iso[1]), Number(iso[2]), Number(iso[3]));
25
+ const numbers = trimmed.split(/\D+/).filter(Boolean).map(Number);
26
+ if (numbers.length < 2 || numbers.length > 3) return;
27
+ const order = partOrder(locale);
28
+ const parts = numbers.length === 3 ? order : order.filter((part) => part !== "year");
29
+ const value = {
30
+ year: today.getFullYear(),
31
+ month: 0,
32
+ day: 0
33
+ };
34
+ parts.forEach((part, index) => {
35
+ value[part] = numbers[index] ?? 0;
36
+ });
37
+ if (numbers.length === 3 && value.year < 100) value.year += 2e3;
38
+ return calendarDay(value.year, value.month, value.day);
39
+ }
40
+ /** A day as the locale writes it in numbers, the form `parseTypedDate` reads back. */
41
+ function formatNumericDate(date, locale) {
42
+ return new Intl.DateTimeFormat(locale, {
43
+ year: "numeric",
44
+ month: "2-digit",
45
+ day: "2-digit",
46
+ numberingSystem: "latn"
47
+ }).format(date);
48
+ }
49
+ //#endregion
50
+ export { formatNumericDate, parseTypedDate };
@@ -0,0 +1,4 @@
1
+ /** The last value that wasn't null or undefined, so a modal or drawer keeps showing what it was
2
+ * about while it animates closed, after the host has already let go of it:
3
+ * `<Drawer open={item !== null}>{useClosingValue(item)?.name}</Drawer>`. */
4
+ export declare function useClosingValue<T>(value: T | null | undefined): T | undefined;
@@ -0,0 +1,12 @@
1
+ import { useState } from "react";
2
+ //#region src/components/useClosingValue.ts
3
+ /** The last value that wasn't null or undefined, so a modal or drawer keeps showing what it was
4
+ * about while it animates closed, after the host has already let go of it:
5
+ * `<Drawer open={item !== null}>{useClosingValue(item)?.name}</Drawer>`. */
6
+ function useClosingValue(value) {
7
+ const [last, setLast] = useState(value ?? void 0);
8
+ if (value !== null && value !== void 0 && !Object.is(value, last)) setLast(value);
9
+ return value ?? last;
10
+ }
11
+ //#endregion
12
+ export { useClosingValue };