@ostack.tech/ui-kform 0.3.4 → 0.4.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.
@@ -1,8 +1,8 @@
1
1
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
- import { usePrefix as usePrefix$1, useControllableState, combineEventHandlers, LocalizationProvider as LocalizationProvider$1, useLatestValues, useConstant, useSpacing, useCssVars, NATIVE_CONTROLS, useResponsiveValues, useCombinedRef, cx, useIsInRoot, warnOnce, PrefixProvider, Root, computed, TabContent, ErrorBoundary, DocumentTitle, Spinner, useToastManager, Tabs, useErrorReporter, EMPTY_STORE, Field, Feedback, FeedbackList, FeedbackPopover, useMediaBreakpointUp, DropdownMenu, DropdownMenuTrigger, Button, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuGroup, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent, DropdownMenuItem, useAlertDialog, DropdownMenuRadioItem, controlStatusToAccent, VisuallyHidden, IconButton, Tab, boolDataAttr, TabList, useIsInTableCell, useDataTableColumnLabel, useOnFieldLabelChange, Checkbox, OptionsGroup, Option, CheckboxGroup, useDateTransformer, DateInput, DateRangeInput, Input, Dialog, Tooltip, DialogTrigger, ControlAddon, Icon, DialogContent, DialogHeader, DialogTitle, DialogBody, Alert, Stack, Popover, PopoverTrigger, PopoverContent, useMeasure, Container, Select, MenuListItem, MenuList, StepContent, Step, StepList, Stepper, CloseButton, useScrollPosition, setBoolDataAttr, Card, CardHeader, CardTitle, CardBody, useIntersectionObserver, useKeyboardShortcut, NumericInput, Link, RadioGroup, Radio, ButtonGroup, AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogBody, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, DataTableCell, DataTableRow, useDataTableApiRef, DataTable, Slot, DataTableContent, DataTablePagination, DataTableRowsPerPage, TextArea, PortalContext } from "@ostack.tech/ui";
3
- import { AbsolutePathFragment, PathMultimap, AbsolutePath, sliceTable, listableSize, ValidationFailure, Path, nullableSchemaInnerSchema, compareSchemaPaths, PromiseCancellationException, arrayToTable, indexOfTableRowId, isComputedSchema } from "@ostack.tech/kform";
4
- import { equals, useFormController, useResolvedPath, useForm, FormContext, CurrentPath, useCurrentPath, useFormManager, useFormattedValue, InvalidPathError, AtPathError, useIssuesTracker, useFormContext, useInput, useListableInput, formatTemporalAsString, useTemporalInput, useFileInput, useController, useNumericInput, formatNumericAsString, useFormatter } from "@ostack.tech/kform-react";
5
- import { createContext, useContext, useCallback, useMemo, useRef, useEffect, forwardRef, useState, useImperativeHandle, startTransition, Suspense, useDeferredValue, createElement, memo } from "react";
2
+ import { usePrefix as usePrefix$1, useControllableState, useLatestValues, combineEventHandlers, LocalizationProvider as LocalizationProvider$1, useConstant, useSpacing, useCssVars, NATIVE_CONTROLS, useResponsiveValues, useCombinedRef, cx, useIsInRoot, warnOnce, PrefixProvider, Root, computed, TabContent, ErrorBoundary, DocumentTitle, Spinner, useToastManager, Tabs, useErrorReporter, EMPTY_STORE, Field, Feedback, FeedbackList, FeedbackPopover, useMediaBreakpointUp, DropdownMenu, DropdownMenuTrigger, Button, DropdownMenuContent, DropdownMenuRadioGroup, DropdownMenuGroup, DropdownMenuSub, DropdownMenuSubTrigger, DropdownMenuSubContent, DropdownMenuItem, useAlertDialog, DropdownMenuRadioItem, controlStatusToAccent, VisuallyHidden, IconButton, Tab, boolDataAttr, TabList, useIsInTableCell, useDataTableColumnLabel, useOnFieldLabelChange, Checkbox, OptionsGroup, Option, CheckboxGroup, useDateTransformer, DateInput, DateRangeInput, Input, Dialog, Tooltip, DialogTrigger, ControlAddon, Icon, DialogContent, DialogHeader, DialogTitle, DialogBody, Alert, Stack, Popover, PopoverTrigger, PopoverContent, useMeasure, Container, Select, MenuListItem, MenuList, StepContent, Step, StepList, Stepper, CloseButton, useScrollPosition, setBoolDataAttr, Card, CardHeader, CardTitle, CardBody, useIntersectionObserver, useKeyboardShortcut, NumericInput, Link, RadioGroup, Radio, ButtonGroup, AlertDialog, AlertDialogTrigger, AlertDialogContent, AlertDialogHeader, AlertDialogTitle, AlertDialogBody, AlertDialogDescription, AlertDialogFooter, AlertDialogCancel, AlertDialogAction, DataTableCell, DataTableRow, useDataTableApiRef, DataTable, Slot, DataTableContent, DataTablePagination, DataTableRowsPerPage, TextArea, PortalContext } from "@ostack.tech/ui";
3
+ import { AbsolutePath, AbsolutePathFragment, PathMultimap, sliceTable, listableSize, Path, ValidationFailure, nullableSchemaInnerSchema, compareSchemaPaths, PromiseCancellationException, arrayToTable, indexOfTableRowId, isComputedSchema } from "@ostack.tech/kform";
4
+ import { useResolvedPath, equals, useFormController, useForm, FormContext, CurrentPath, useCurrentPath, useFormManager, useFormattedValue, InvalidPathError, AtPathError, useIssuesTracker, useFormContext, useInput, useListableInput, formatTemporalAsString, useTemporalInput, useFileInput, useController, useNumericInput, formatNumericAsString, useFormatter } from "@ostack.tech/kform-react";
5
+ import { createContext, useContext, useCallback, useMemo, useRef, useEffect, forwardRef, useState, useImperativeHandle, startTransition, Suspense, useDeferredValue, Children, isValidElement, createElement, memo } from "react";
6
6
  import { createStore, useStore } from "zustand";
7
7
  import { subscribeWithSelector } from "zustand/middleware";
8
8
  import { useShallow } from "zustand/react/shallow";
@@ -41,6 +41,58 @@ function usePrefix() {
41
41
  [prefix, prefixSuffix]
42
42
  );
43
43
  }
44
+ const ActivePathContext = createContext(
45
+ null
46
+ );
47
+ function useActivePathContext() {
48
+ const activePathContext = useContext(ActivePathContext);
49
+ if (!activePathContext) {
50
+ throw new Error("Active path context not in scope");
51
+ }
52
+ return activePathContext;
53
+ }
54
+ function ActivePathProvider({
55
+ defaultActivePath,
56
+ activePath: controlledActivePath,
57
+ onActivePathChange,
58
+ children
59
+ }) {
60
+ const [activePath, setActivePath] = useControllableState(
61
+ defaultActivePath,
62
+ controlledActivePath
63
+ );
64
+ const resolvedActivePath = useResolvedPath(activePath);
65
+ const latest = useLatestValues({ resolvedActivePath, onActivePathChange });
66
+ const handleActivePathChange = useCallback(
67
+ (newActivePath) => {
68
+ const activePath2 = latest.resolvedActivePath;
69
+ if (typeof newActivePath === "string") {
70
+ newActivePath = new AbsolutePath(newActivePath);
71
+ } else {
72
+ newActivePath ??= AbsolutePath.ROOT;
73
+ }
74
+ if (!activePath2.equals(newActivePath) && !newActivePath.append(AbsolutePathFragment.RecursiveWildcard).contains(activePath2)) {
75
+ const replace = activePath2.append(AbsolutePathFragment.RecursiveWildcard).contains(newActivePath);
76
+ setActivePath(newActivePath);
77
+ latest.onActivePathChange?.(newActivePath, { replace });
78
+ }
79
+ },
80
+ [latest, setActivePath]
81
+ );
82
+ return /* @__PURE__ */ jsx(
83
+ ActivePathContext,
84
+ {
85
+ value: useMemo(
86
+ () => ({
87
+ activePath: resolvedActivePath,
88
+ onActivePathChange: handleActivePathChange
89
+ }),
90
+ [handleActivePathChange, resolvedActivePath]
91
+ ),
92
+ children
93
+ }
94
+ );
95
+ }
44
96
  const LocalizationContext = createContext(null);
45
97
  function LocalizationProvider({
46
98
  defaultLocale,
@@ -128,7 +180,7 @@ function useCreateFormAppContext({
128
180
  leftSidebarWidth: 0,
129
181
  bottomPanelHeight: 0,
130
182
  startIssuesNavigation: void 0,
131
- activePath: AbsolutePath.ROOT,
183
+ latestInteraction: AbsolutePath.ROOT,
132
184
  registeredControllers: [new PathMultimap()],
133
185
  registeredIssueMessages: [new PathMultimap()],
134
186
  deferredIssueMessageRemovalEntryIds: [],
@@ -210,8 +262,8 @@ function useCreateFormAppContext({
210
262
  };
211
263
  },
212
264
  isRegistered: (path) => get().registeredControllers[0].containsPath(path),
213
- setActivePath: (path) => {
214
- set({ activePath: path });
265
+ setLatestInteraction: (path) => {
266
+ set({ latestInteraction: path });
215
267
  },
216
268
  registerIssueMessages: (basePath, issueMessages, priority = 0) => {
217
269
  if (issueMessages == null) {
@@ -406,13 +458,16 @@ function useIsRegistered(path) {
406
458
  (state) => path != null && state.actions.isRegistered(path)
407
459
  );
408
460
  }
409
- function useActivePath() {
410
- return useStore(useFormAppContext().store, (state) => state.activePath);
461
+ function useLatestInteraction() {
462
+ return useStore(
463
+ useFormAppContext().store,
464
+ (state) => state.latestInteraction
465
+ );
411
466
  }
412
- function useSetActivePath() {
467
+ function useSetLatestInteraction() {
413
468
  return useStore(
414
469
  useFormAppContext().store,
415
- (state) => state.actions.setActivePath
470
+ (state) => state.actions.setLatestInteraction
416
471
  );
417
472
  }
418
473
  function useRegisterIssueMessages(path, issueMessages, priority) {
@@ -697,6 +752,10 @@ const FormApp = forwardRef(function FormApp2({
697
752
  issueMessages,
698
753
  issuesDisplayMode = "inline",
699
754
  displayIssueCodes = false,
755
+ // activePathSearchParam,
756
+ defaultActivePath,
757
+ activePath,
758
+ onActivePathChange,
700
759
  onPathFocus,
701
760
  minHeight,
702
761
  apiRef,
@@ -850,14 +909,22 @@ const FormApp = forwardRef(function FormApp2({
850
909
  locale: controlledLocale,
851
910
  onLocaleChange,
852
911
  children: /* @__PURE__ */ jsx(FormContext.Provider, { value: formContext, children: /* @__PURE__ */ jsx(FormAppContext.Provider, { value: formAppContextValue, children: /* @__PURE__ */ jsx(
853
- FormAppIssueMessages,
912
+ ActivePathProvider,
854
913
  {
855
- issueMessages: locale.FormApp.issueMessages,
856
- priority: -1,
857
- children: /* @__PURE__ */ jsxs(FormAppIssueMessages, { issueMessages, children: [
858
- /* @__PURE__ */ jsx(FormAppAutoFocus, {}),
859
- isInRoot ? formAppEl : /* @__PURE__ */ jsx(Root, { asChild: true, ...rootProps, children: formAppEl })
860
- ] })
914
+ defaultActivePath,
915
+ activePath,
916
+ onActivePathChange,
917
+ children: /* @__PURE__ */ jsx(
918
+ FormAppIssueMessages,
919
+ {
920
+ issueMessages: locale.FormApp.issueMessages,
921
+ priority: -1,
922
+ children: /* @__PURE__ */ jsxs(FormAppIssueMessages, { issueMessages, children: [
923
+ /* @__PURE__ */ jsx(FormAppAutoFocus, {}),
924
+ isInRoot ? formAppEl : /* @__PURE__ */ jsx(Root, { asChild: true, ...rootProps, children: formAppEl })
925
+ ] })
926
+ }
927
+ )
861
928
  }
862
929
  ) }) })
863
930
  }
@@ -916,9 +983,12 @@ function useCreateAnnexesContext({
916
983
  onAnnexAdd,
917
984
  onAnnexRemove
918
985
  }) {
919
- const setActivePath = useSetActivePath();
986
+ const setLatestInteraction = useSetLatestInteraction();
987
+ const { activePath, onActivePathChange } = useActivePathContext();
988
+ activeAnnex ??= activePath;
920
989
  const latest = useLatestValues({
921
990
  formManager,
991
+ onActivePathChange,
922
992
  onActiveAnnexChange,
923
993
  onAnnexAdd,
924
994
  onAnnexRemove
@@ -1066,10 +1136,11 @@ function useCreateAnnexesContext({
1066
1136
  startTransition(() => {
1067
1137
  set({ activeAnnex: newActiveAnnex });
1068
1138
  if (newActiveAnnex != null) {
1069
- setActivePath(newActiveAnnex);
1139
+ setLatestInteraction(newActiveAnnex);
1070
1140
  }
1141
+ latest.onActivePathChange(newActiveAnnex);
1142
+ latest.onActiveAnnexChange?.(newActiveAnnex);
1071
1143
  });
1072
- latest.onActiveAnnexChange?.(newActiveAnnex);
1073
1144
  }
1074
1145
  },
1075
1146
  updateActiveAnnex: (oldActiveAnnexIndex) => {
@@ -1188,7 +1259,20 @@ function useAnnexState(path, selector) {
1188
1259
  );
1189
1260
  }
1190
1261
  const Annex = forwardRef(
1191
- function Annex2({ path, ...otherProps }, forwardedRef) {
1262
+ function Annex2({
1263
+ path,
1264
+ // `AnnexObject` props
1265
+ title: _title,
1266
+ subtitle: _subtitle,
1267
+ description: _description,
1268
+ issuesPanelLabel: _issuesPanelLabel,
1269
+ itemTitle: _itemTitle,
1270
+ itemIssuesPanelLabel: _itemIssuesPanelLabel,
1271
+ documentTitle: _documentTitle,
1272
+ maxAnnexes: _maxAnnexes,
1273
+ getValue: _getValue,
1274
+ ...otherProps
1275
+ }, forwardedRef) {
1192
1276
  const annexPath = useResolvedPath(path);
1193
1277
  const store = useAnnexesContext();
1194
1278
  const activeAnnex = useStore(store, (state) => state.activeAnnex);
@@ -1210,39 +1294,37 @@ const Annex = forwardRef(
1210
1294
  });
1211
1295
  }
1212
1296
  );
1213
- const AnnexTabContent = forwardRef(
1214
- function AnnexTabContent2({
1215
- disabled = false,
1216
- readOnly = false,
1217
- issueMessages,
1218
- children,
1219
- className,
1220
- errorBoundaryProps,
1221
- ...otherProps
1222
- }, forwardedRef) {
1223
- const prefix = usePrefix();
1224
- const annexPath = useCurrentPath();
1225
- const documentTitle = useAnnexState(
1226
- annexPath,
1227
- (state) => state === null ? null : state.documentTitle
1228
- );
1229
- if (documentTitle === void 0) {
1230
- warnOnce(
1231
- `Annex: At '${annexPath.toString()}': \`documentTitle\` should be set manually since either \`title\` or \`subtitle\` is not a string.`
1232
- );
1233
- }
1234
- return /* @__PURE__ */ jsx(
1235
- TabContent,
1236
- {
1237
- className: cx(prefix("annexes__annex"), className),
1238
- value: annexPath.toString(),
1239
- ...otherProps,
1240
- ref: forwardedRef,
1241
- children: /* @__PURE__ */ jsx(ErrorBoundary, { ...errorBoundaryProps, children: /* @__PURE__ */ jsx(DocumentTitle, { title: documentTitle ?? void 0, children: /* @__PURE__ */ jsx(FormAppStatus, { disabled, readOnly, children: /* @__PURE__ */ jsx(FormAppIssueMessages, { issueMessages, children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(Spinner, { size: "xl", color: "primary" }), children }) }) }) }) })
1242
- }
1297
+ const AnnexTabContent = forwardRef(function AnnexTabContent2({
1298
+ disabled = false,
1299
+ readOnly = false,
1300
+ issueMessages,
1301
+ children,
1302
+ className,
1303
+ errorBoundaryProps,
1304
+ ...otherProps
1305
+ }, forwardedRef) {
1306
+ const prefix = usePrefix();
1307
+ const annexPath = useCurrentPath();
1308
+ const documentTitle = useAnnexState(
1309
+ annexPath,
1310
+ (state) => state === null ? null : state.documentTitle
1311
+ );
1312
+ if (documentTitle === void 0) {
1313
+ warnOnce(
1314
+ `Annex: At '${annexPath.toString()}': \`documentTitle\` should be set manually since either \`title\` or \`subtitle\` is not a string.`
1243
1315
  );
1244
1316
  }
1245
- );
1317
+ return /* @__PURE__ */ jsx(
1318
+ TabContent,
1319
+ {
1320
+ className: cx(prefix("annexes__annex"), className),
1321
+ value: annexPath.toString(),
1322
+ ...otherProps,
1323
+ ref: forwardedRef,
1324
+ children: /* @__PURE__ */ jsx(ErrorBoundary, { ...errorBoundaryProps, children: /* @__PURE__ */ jsx(DocumentTitle, { title: documentTitle ?? void 0, children: /* @__PURE__ */ jsx(FormAppStatus, { disabled, readOnly, children: /* @__PURE__ */ jsx(FormAppIssueMessages, { issueMessages, children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx(Spinner, { size: "xl", color: "primary" }), children }) }) }) }) })
1325
+ }
1326
+ );
1327
+ });
1246
1328
  function useFormLoader({
1247
1329
  decode,
1248
1330
  path,
@@ -1375,92 +1457,6 @@ function useFormLoader({
1375
1457
  function useFormIsLoading() {
1376
1458
  return useFormController().useState((state) => state.loading);
1377
1459
  }
1378
- const Annexes = forwardRef(
1379
- function Annexes2({
1380
- path,
1381
- annexes,
1382
- defaultActiveAnnex,
1383
- activeAnnex,
1384
- onActiveAnnexChange,
1385
- onAnnexAdd,
1386
- onAnnexRemove,
1387
- variant = "annexes",
1388
- className,
1389
- ...otherProps
1390
- }, forwardedRef) {
1391
- const prefix = usePrefix();
1392
- const formManager = useFormManager();
1393
- const absolutePath = useResolvedPath(path);
1394
- const resolvedAnnexes = useMemo(
1395
- () => annexes.map((annex) => ({
1396
- ...annex,
1397
- path: absolutePath.resolve(annex.path)
1398
- })),
1399
- [absolutePath, annexes]
1400
- );
1401
- if (!formManager.isValidPath(absolutePath)) {
1402
- throw new Error(`Invalid path: '${absolutePath.toString()}'`);
1403
- }
1404
- if (absolutePath.fragments.some(
1405
- (frag) => !(frag instanceof AbsolutePathFragment.Id)
1406
- )) {
1407
- throw new Error("Annexes base path must only contain ids.");
1408
- }
1409
- for (const annex of resolvedAnnexes) {
1410
- if (!formManager.isValidPath(annex.path)) {
1411
- throw new Error(`Invalid path: '${annex.path.toString()}'`);
1412
- }
1413
- if (annex.path.fragments.some(
1414
- (frag, i, { length }) => !(frag instanceof AbsolutePathFragment.Id) && (frag !== AbsolutePathFragment.Wildcard || i !== length - 1)
1415
- )) {
1416
- throw new Error(
1417
- "Annex path must either only contain ids, or ids followed by a single wildcard at the end."
1418
- );
1419
- }
1420
- }
1421
- const store = useCreateAnnexesContext({
1422
- formManager,
1423
- basePath: absolutePath,
1424
- resolvedAnnexes,
1425
- defaultActiveAnnex,
1426
- activeAnnex,
1427
- onActiveAnnexChange,
1428
- onAnnexAdd,
1429
- onAnnexRemove
1430
- });
1431
- const activeAnnexString = useStore(
1432
- store,
1433
- (state) => state.activeAnnex?.toString() ?? ""
1434
- );
1435
- const handleValueChange = useCallback(
1436
- (value) => store.getState().actions.setActiveAnnex(value),
1437
- [store]
1438
- );
1439
- return /* @__PURE__ */ jsx(CurrentPath, { path: absolutePath, children: /* @__PURE__ */ jsxs(AnnexesContext.Provider, { value: store, children: [
1440
- resolvedAnnexes.map(
1441
- (annex) => annex.path.lastFragment === AbsolutePathFragment.Wildcard ? /* @__PURE__ */ jsx(
1442
- RepetitiveAnnexRegistrar,
1443
- {
1444
- ...annex
1445
- },
1446
- annex.path.toString()
1447
- ) : /* @__PURE__ */ jsx(AnnexRegistrar, { ...annex }, annex.path.toString())
1448
- ),
1449
- /* @__PURE__ */ jsx(
1450
- Tabs,
1451
- {
1452
- className: cx(prefix("annexes"), className),
1453
- variant,
1454
- activationMode: "manual",
1455
- value: activeAnnexString,
1456
- onValueChange: handleValueChange,
1457
- ...otherProps,
1458
- ref: forwardedRef
1459
- }
1460
- )
1461
- ] }) });
1462
- }
1463
- );
1464
1460
  function AnnexRegistrar({
1465
1461
  path,
1466
1462
  title,
@@ -1610,6 +1606,108 @@ function RepetitiveAnnexRegistrar({
1610
1606
  rowId
1611
1607
  ));
1612
1608
  }
1609
+ const Annexes = forwardRef(
1610
+ function Annexes2({
1611
+ path,
1612
+ annexes,
1613
+ defaultActiveAnnex,
1614
+ activeAnnex,
1615
+ onActiveAnnexChange,
1616
+ onAnnexAdd,
1617
+ onAnnexRemove,
1618
+ variant = "annexes",
1619
+ className,
1620
+ children,
1621
+ ...otherProps
1622
+ }, forwardedRef) {
1623
+ const prefix = usePrefix();
1624
+ const formManager = useFormManager();
1625
+ const absolutePath = useResolvedPath(path);
1626
+ annexes ??= Children.toArray(children).filter(
1627
+ (child) => isValidElement(child) && child.type === Annex
1628
+ ).map((annex) => ({
1629
+ path: annex.props.path,
1630
+ title: annex.props.title,
1631
+ subtitle: annex.props.subtitle,
1632
+ description: annex.props.description,
1633
+ issuesPanelLabel: annex.props.issuesPanelLabel,
1634
+ itemTitle: annex.props.itemTitle,
1635
+ itemIssuesPanelLabel: annex.props.itemIssuesPanelLabel,
1636
+ documentTitle: annex.props.documentTitle,
1637
+ maxAnnexes: annex.props.maxAnnexes,
1638
+ getValue: annex.props.getValue
1639
+ }));
1640
+ const resolvedAnnexes = useMemo(
1641
+ () => annexes.map((annex) => ({
1642
+ ...annex,
1643
+ path: absolutePath.resolve(annex.path ?? Path.CURRENT)
1644
+ })),
1645
+ [absolutePath, annexes]
1646
+ );
1647
+ if (!formManager.isValidPath(absolutePath)) {
1648
+ throw new Error(`Invalid path: '${absolutePath.toString()}'`);
1649
+ }
1650
+ if (absolutePath.fragments.some(
1651
+ (frag) => !(frag instanceof AbsolutePathFragment.Id)
1652
+ )) {
1653
+ throw new Error("Annexes base path must only contain ids.");
1654
+ }
1655
+ for (const annex of resolvedAnnexes) {
1656
+ if (!formManager.isValidPath(annex.path)) {
1657
+ throw new Error(`Invalid path: '${annex.path.toString()}'`);
1658
+ }
1659
+ if (annex.path.fragments.some(
1660
+ (frag, i, { length }) => !(frag instanceof AbsolutePathFragment.Id) && (frag !== AbsolutePathFragment.Wildcard || i !== length - 1)
1661
+ )) {
1662
+ throw new Error(
1663
+ "Annex path must either only contain ids, or ids followed by a single wildcard at the end."
1664
+ );
1665
+ }
1666
+ }
1667
+ const store = useCreateAnnexesContext({
1668
+ formManager,
1669
+ basePath: absolutePath,
1670
+ resolvedAnnexes,
1671
+ defaultActiveAnnex,
1672
+ activeAnnex,
1673
+ onActiveAnnexChange,
1674
+ onAnnexAdd,
1675
+ onAnnexRemove
1676
+ });
1677
+ const activeAnnexString = useStore(
1678
+ store,
1679
+ (state) => state.activeAnnex?.toString() ?? ""
1680
+ );
1681
+ const handleValueChange = useCallback(
1682
+ (value) => store.getState().actions.setActiveAnnex(value),
1683
+ [store]
1684
+ );
1685
+ return /* @__PURE__ */ jsx(CurrentPath, { path: absolutePath, children: /* @__PURE__ */ jsxs(AnnexesContext.Provider, { value: store, children: [
1686
+ resolvedAnnexes.map(
1687
+ (annex) => annex.path.lastFragment === AbsolutePathFragment.Wildcard ? /* @__PURE__ */ jsx(
1688
+ RepetitiveAnnexRegistrar,
1689
+ {
1690
+ ...annex
1691
+ },
1692
+ annex.path.toString()
1693
+ ) : /* @__PURE__ */ jsx(AnnexRegistrar, { ...annex }, annex.path.toString())
1694
+ ),
1695
+ /* @__PURE__ */ jsx(
1696
+ Tabs,
1697
+ {
1698
+ className: cx(prefix("annexes"), className),
1699
+ variant,
1700
+ activationMode: "manual",
1701
+ value: activeAnnexString,
1702
+ onValueChange: handleValueChange,
1703
+ ...otherProps,
1704
+ ref: forwardedRef,
1705
+ children
1706
+ }
1707
+ )
1708
+ ] }) });
1709
+ }
1710
+ );
1613
1711
  class ValidationFailureError extends AtPathError {
1614
1712
  constructor(path, issue) {
1615
1713
  super(path, `Failed to run validation '${issue.validation}'`);
@@ -2472,14 +2570,14 @@ function useRegisterControl({
2472
2570
  controller,
2473
2571
  !preventAutoFocus
2474
2572
  );
2475
- const setActivePath = useSetActivePath();
2573
+ const setLatestInteraction = useSetLatestInteraction();
2476
2574
  const handleFocus = useCallback(
2477
2575
  (event) => {
2478
2576
  if (event.target === event.currentTarget && !event.defaultPrevented) {
2479
- setActivePath(controller.getState().path);
2577
+ setLatestInteraction(controller.getState().path);
2480
2578
  }
2481
2579
  },
2482
- [controller, setActivePath]
2580
+ [controller, setLatestInteraction]
2483
2581
  );
2484
2582
  return useMemo(
2485
2583
  () => ({
@@ -3425,8 +3523,10 @@ function useCreateFormPagesContext({
3425
3523
  activePage,
3426
3524
  onActivePageChange
3427
3525
  }) {
3428
- const setActivePath = useSetActivePath();
3429
- const latest = useLatestValues({ onActivePageChange });
3526
+ const setLatestInteraction = useSetLatestInteraction();
3527
+ const { activePath, onActivePathChange } = useActivePathContext();
3528
+ activePage ??= activePath;
3529
+ const latest = useLatestValues({ onActivePathChange, onActivePageChange });
3430
3530
  const store = useConstant(
3431
3531
  () => createStore()(
3432
3532
  subscribeWithSelector((set, get) => ({
@@ -3459,10 +3559,11 @@ function useCreateFormPagesContext({
3459
3559
  startTransition(() => {
3460
3560
  set({ activePage: newActivePage });
3461
3561
  if (newActivePage != null) {
3462
- setActivePath(newActivePage);
3562
+ setLatestInteraction(newActivePage);
3463
3563
  }
3564
+ latest.onActivePathChange(newActivePage);
3565
+ latest.onActivePageChange?.(newActivePage);
3464
3566
  });
3465
- latest.onActivePageChange?.(newActivePage);
3466
3567
  }
3467
3568
  },
3468
3569
  updateActivePage: (oldActivePageIndex) => {
@@ -3676,6 +3777,11 @@ const FormPageHeader = forwardRef(function FormPageHeader2({
3676
3777
  const FormPage = forwardRef(
3677
3778
  function FormPage2({
3678
3779
  path,
3780
+ // `FormPageObject` props
3781
+ title: _title,
3782
+ documentTitle: _documentTitle,
3783
+ code: _code,
3784
+ issuesPanelLabel: _issuesPanelLabel,
3679
3785
  disabled = false,
3680
3786
  readOnly = false,
3681
3787
  className,
@@ -3786,6 +3892,60 @@ const FormPage = forwardRef(
3786
3892
  ) }) }) : null;
3787
3893
  }
3788
3894
  );
3895
+ function FormPageRegistrar({
3896
+ path,
3897
+ title,
3898
+ documentTitle,
3899
+ code,
3900
+ issuesPanelLabel
3901
+ }) {
3902
+ const store = useFormPagesContext();
3903
+ const controller = useController(
3904
+ path.append(AbsolutePathFragment.RecursiveWildcard),
3905
+ { enabled: !useFormIsLoading() }
3906
+ );
3907
+ const absolutePath = controller.usePath();
3908
+ const exists = controller.useState(
3909
+ (state) => state.initialized && state.exists
3910
+ );
3911
+ const deferredValue = useDeferredValue(controller.useValue());
3912
+ const deferredIssuesToDisplay = useDeferredValue(
3913
+ controller.useState((state) => state.touched ? state.issues : [], {
3914
+ equalityFn: shallow
3915
+ })
3916
+ );
3917
+ const deferredDisplayStatus = useDeferredValue(controller.useDisplayStatus());
3918
+ const actualTitle = typeof title === "function" ? deferredValue === void 0 ? null : title(deferredValue) : title;
3919
+ const actualDocumentTitle = typeof documentTitle === "function" ? deferredValue === void 0 ? null : documentTitle(deferredValue) : documentTitle === void 0 ? actualTitle === null || typeof actualTitle === "string" ? actualTitle : void 0 : documentTitle;
3920
+ useEffect(() => {
3921
+ const { registerPageState } = store.getState().actions;
3922
+ registerPageState(absolutePath, {
3923
+ exists,
3924
+ path: absolutePath,
3925
+ deferredIssuesToDisplay,
3926
+ deferredDisplayStatus,
3927
+ title: actualTitle,
3928
+ documentTitle: actualDocumentTitle,
3929
+ code
3930
+ });
3931
+ }, [
3932
+ absolutePath,
3933
+ actualDocumentTitle,
3934
+ actualTitle,
3935
+ code,
3936
+ deferredDisplayStatus,
3937
+ deferredIssuesToDisplay,
3938
+ exists,
3939
+ store,
3940
+ title
3941
+ ]);
3942
+ useRegisterController(controller);
3943
+ useRegisterLabel(
3944
+ absolutePath,
3945
+ /* @__PURE__ */ jsx(CurrentPath, { path: absolutePath, children: typeof issuesPanelLabel === "function" ? deferredValue === void 0 ? null : issuesPanelLabel(deferredValue) : issuesPanelLabel || actualTitle })
3946
+ );
3947
+ return null;
3948
+ }
3789
3949
  const FormPages = forwardRef(
3790
3950
  function FormPages2({
3791
3951
  path,
@@ -3794,6 +3954,7 @@ const FormPages = forwardRef(
3794
3954
  activePage,
3795
3955
  onActivePageChange,
3796
3956
  className,
3957
+ children,
3797
3958
  ...otherProps
3798
3959
  }, forwardedRef) {
3799
3960
  const prefix = usePrefix();
@@ -3801,6 +3962,15 @@ const FormPages = forwardRef(
3801
3962
  const formManager = useFormManager();
3802
3963
  const absolutePath = useResolvedPath(path);
3803
3964
  const isLargeScreen = useMediaBreakpointUp("sm");
3965
+ pages ??= Children.toArray(children).filter(
3966
+ (child) => isValidElement(child) && child.type === FormPage
3967
+ ).map((child) => ({
3968
+ path: child.props.path,
3969
+ title: child.props.title,
3970
+ documentTitle: child.props.documentTitle,
3971
+ code: child.props.code,
3972
+ issuesPanelLabel: child.props.issuesPanelLabel
3973
+ }));
3804
3974
  const resolvedPages = useMemo(
3805
3975
  () => pages.map((page) => ({
3806
3976
  ...page,
@@ -3865,66 +4035,13 @@ const FormPages = forwardRef(
3865
4035
  className: cx(prefix("form-pages"), className),
3866
4036
  "data-navigation-mode": isLargeScreen ? "sidebar" : "select",
3867
4037
  ...otherProps,
3868
- ref: combinedFormPagesRef
4038
+ ref: combinedFormPagesRef,
4039
+ children
3869
4040
  }
3870
4041
  )
3871
4042
  ] }) });
3872
4043
  }
3873
4044
  );
3874
- function FormPageRegistrar({
3875
- path,
3876
- title,
3877
- documentTitle,
3878
- code,
3879
- issuesPanelLabel
3880
- }) {
3881
- const store = useFormPagesContext();
3882
- const controller = useController(
3883
- path.append(AbsolutePathFragment.RecursiveWildcard),
3884
- { enabled: !useFormIsLoading() }
3885
- );
3886
- const absolutePath = controller.usePath();
3887
- const exists = controller.useState(
3888
- (state) => state.initialized && state.exists
3889
- );
3890
- const deferredValue = useDeferredValue(controller.useValue());
3891
- const deferredIssuesToDisplay = useDeferredValue(
3892
- controller.useState((state) => state.touched ? state.issues : [], {
3893
- equalityFn: shallow
3894
- })
3895
- );
3896
- const deferredDisplayStatus = useDeferredValue(controller.useDisplayStatus());
3897
- const actualTitle = typeof title === "function" ? deferredValue === void 0 ? null : title(deferredValue) : title;
3898
- const actualDocumentTitle = typeof documentTitle === "function" ? deferredValue === void 0 ? null : documentTitle(deferredValue) : documentTitle === void 0 ? actualTitle === null || typeof actualTitle === "string" ? actualTitle : void 0 : documentTitle;
3899
- useEffect(() => {
3900
- const { registerPageState } = store.getState().actions;
3901
- registerPageState(absolutePath, {
3902
- exists,
3903
- path: absolutePath,
3904
- deferredIssuesToDisplay,
3905
- deferredDisplayStatus,
3906
- title: actualTitle,
3907
- documentTitle: actualDocumentTitle,
3908
- code
3909
- });
3910
- }, [
3911
- absolutePath,
3912
- actualDocumentTitle,
3913
- actualTitle,
3914
- code,
3915
- deferredDisplayStatus,
3916
- deferredIssuesToDisplay,
3917
- exists,
3918
- store,
3919
- title
3920
- ]);
3921
- useRegisterController(controller);
3922
- useRegisterLabel(
3923
- absolutePath,
3924
- /* @__PURE__ */ jsx(CurrentPath, { path: absolutePath, children: typeof issuesPanelLabel === "function" ? deferredValue === void 0 ? null : issuesPanelLabel(deferredValue) : issuesPanelLabel || actualTitle })
3925
- );
3926
- return null;
3927
- }
3928
4045
  function FormPagesSelectOption({ path }) {
3929
4046
  const prefix = usePrefix();
3930
4047
  const disabled = useFormPageState(path, (state) => !state?.exists);
@@ -4168,9 +4285,12 @@ function useCreateFormStepperContext({
4168
4285
  preventFocusOnError
4169
4286
  }) {
4170
4287
  const focus = useFocus();
4171
- const setActivePath = useSetActivePath();
4288
+ const setLatestInteraction = useSetLatestInteraction();
4289
+ const { activePath, onActivePathChange } = useActivePathContext();
4290
+ activeStep ??= activePath;
4172
4291
  const latest = useLatestValues({
4173
4292
  formManager,
4293
+ onActivePathChange,
4174
4294
  onActiveStepChange,
4175
4295
  onStepValidation,
4176
4296
  skipStepValidationOnForwardNavigation,
@@ -4208,15 +4328,16 @@ function useCreateFormStepperContext({
4208
4328
  startTransition(() => {
4209
4329
  set({ activeStep: step });
4210
4330
  if (step != null) {
4211
- setActivePath(step.path);
4331
+ setLatestInteraction(step.path);
4332
+ }
4333
+ latest.onActivePathChange(step?.path ?? null);
4334
+ if (callListener) {
4335
+ latest.onActiveStepChange?.(
4336
+ step?.path ?? null,
4337
+ step?.index ?? -1
4338
+ );
4212
4339
  }
4213
4340
  });
4214
- if (callListener) {
4215
- latest.onActiveStepChange?.(
4216
- step?.path ?? null,
4217
- step?.index ?? -1
4218
- );
4219
- }
4220
4341
  }
4221
4342
  },
4222
4343
  updateActiveStep: () => {
@@ -4400,6 +4521,50 @@ const FormStepList = forwardRef(function FormStepperSidebar({ className, ...othe
4400
4521
  }
4401
4522
  );
4402
4523
  });
4524
+ function FormStepRegistrar({
4525
+ index,
4526
+ path,
4527
+ title,
4528
+ documentTitle,
4529
+ issuesPanelLabel
4530
+ }) {
4531
+ const store = useFormStepperContext();
4532
+ const controller = useController(path, { enabled: !useFormIsLoading() });
4533
+ const absolutePath = controller.usePath();
4534
+ const exists = controller.useState(
4535
+ (state) => state.initialized && state.exists
4536
+ );
4537
+ const deferredValue = useDeferredValue(controller.useValue());
4538
+ const deferredDisplayStatus = useDeferredValue(controller.useDisplayStatus());
4539
+ const actualTitle = typeof title === "function" ? deferredValue === void 0 ? null : title(deferredValue) : title;
4540
+ const actualDocumentTitle = typeof documentTitle === "function" ? deferredValue === void 0 ? null : documentTitle(deferredValue) : documentTitle === void 0 ? actualTitle === null || typeof actualTitle === "string" ? actualTitle : void 0 : documentTitle;
4541
+ useEffect(() => {
4542
+ const { registerStepState } = store.getState().actions;
4543
+ registerStepState(absolutePath, {
4544
+ exists,
4545
+ path: absolutePath,
4546
+ deferredDisplayStatus,
4547
+ index,
4548
+ title: actualTitle,
4549
+ documentTitle: actualDocumentTitle
4550
+ });
4551
+ }, [
4552
+ absolutePath,
4553
+ actualDocumentTitle,
4554
+ actualTitle,
4555
+ deferredDisplayStatus,
4556
+ exists,
4557
+ index,
4558
+ store,
4559
+ title
4560
+ ]);
4561
+ useRegisterController(controller);
4562
+ useRegisterLabel(
4563
+ absolutePath,
4564
+ /* @__PURE__ */ jsx(CurrentPath, { path: absolutePath, children: typeof issuesPanelLabel === "function" ? deferredValue === void 0 ? null : issuesPanelLabel(deferredValue) : issuesPanelLabel || actualTitle })
4565
+ );
4566
+ return null;
4567
+ }
4403
4568
  const FormStepper = forwardRef(function FormStepper2({
4404
4569
  path,
4405
4570
  steps,
@@ -4470,50 +4635,6 @@ const FormStepper = forwardRef(function FormStepper2({
4470
4635
  )
4471
4636
  ] }) });
4472
4637
  });
4473
- function FormStepRegistrar({
4474
- index,
4475
- path,
4476
- title,
4477
- documentTitle,
4478
- issuesPanelLabel
4479
- }) {
4480
- const store = useFormStepperContext();
4481
- const controller = useController(path, { enabled: !useFormIsLoading() });
4482
- const absolutePath = controller.usePath();
4483
- const exists = controller.useState(
4484
- (state) => state.initialized && state.exists
4485
- );
4486
- const deferredValue = useDeferredValue(controller.useValue());
4487
- const deferredDisplayStatus = useDeferredValue(controller.useDisplayStatus());
4488
- const actualTitle = typeof title === "function" ? deferredValue === void 0 ? null : title(deferredValue) : title;
4489
- const actualDocumentTitle = typeof documentTitle === "function" ? deferredValue === void 0 ? null : documentTitle(deferredValue) : documentTitle === void 0 ? actualTitle === null || typeof actualTitle === "string" ? actualTitle : void 0 : documentTitle;
4490
- useEffect(() => {
4491
- const { registerStepState } = store.getState().actions;
4492
- registerStepState(absolutePath, {
4493
- exists,
4494
- path: absolutePath,
4495
- deferredDisplayStatus,
4496
- index,
4497
- title: actualTitle,
4498
- documentTitle: actualDocumentTitle
4499
- });
4500
- }, [
4501
- absolutePath,
4502
- actualDocumentTitle,
4503
- actualTitle,
4504
- deferredDisplayStatus,
4505
- exists,
4506
- index,
4507
- store,
4508
- title
4509
- ]);
4510
- useRegisterController(controller);
4511
- useRegisterLabel(
4512
- absolutePath,
4513
- /* @__PURE__ */ jsx(CurrentPath, { path: absolutePath, children: typeof issuesPanelLabel === "function" ? deferredValue === void 0 ? null : issuesPanelLabel(deferredValue) : issuesPanelLabel || actualTitle })
4514
- );
4515
- return null;
4516
- }
4517
4638
  const IssueAlert = forwardRef(function IssueMessage2({ path, code, children, ...otherProps }, forwardedRef) {
4518
4639
  const { info } = useIssuesTracker(path);
4519
4640
  const relevantIssues = (info?.flatMap((info2) => info2.issues) ?? []).filter(
@@ -5052,7 +5173,7 @@ const IssuesPanel = forwardRef(function ValidationPanel({
5052
5173
  issuesOrderCompareFn
5053
5174
  });
5054
5175
  useReportValidationFailures(issuesTrackerResult.info);
5055
- const activePath = useActivePath();
5176
+ const latestInteraction = useLatestInteraction();
5056
5177
  const deferredIssuesTrackerResult = useDeferredValue(issuesTrackerResult);
5057
5178
  const { info } = deferredIssuesTrackerResult;
5058
5179
  const nPages = info?.length ?? 0;
@@ -5119,7 +5240,7 @@ const IssuesPanel = forwardRef(function ValidationPanel({
5119
5240
  goToPath(
5120
5241
  mostRelevantIssuePath(
5121
5242
  issuesTrackerResult.info,
5122
- activePath,
5243
+ latestInteraction,
5123
5244
  formSchema,
5124
5245
  issuesOrderCompareFnRef.current
5125
5246
  )
@@ -5128,11 +5249,11 @@ const IssuesPanel = forwardRef(function ValidationPanel({
5128
5249
  setStarting(false);
5129
5250
  }
5130
5251
  }, [
5131
- activePath,
5132
5252
  formSchema,
5133
5253
  goToPath,
5134
5254
  issuesTrackerResult.info,
5135
5255
  issuesTrackerResult.initialized,
5256
+ latestInteraction,
5136
5257
  starting
5137
5258
  ]);
5138
5259
  const listeners = useLatestValues({ onOpenChange });
@@ -5263,21 +5384,21 @@ const IssuesPanel = forwardRef(function ValidationPanel({
5263
5384
  }
5264
5385
  );
5265
5386
  });
5266
- function mostRelevantIssuePath(info, activePath, formSchema, issuesOrderCompareFn) {
5387
+ function mostRelevantIssuePath(info, latestInteraction, formSchema, issuesOrderCompareFn) {
5267
5388
  if (issuesOrderCompareFn) {
5268
5389
  for (const infoIssues of info) {
5269
- if (activePath.equals(infoIssues.path)) {
5270
- return activePath;
5390
+ if (latestInteraction.equals(infoIssues.path)) {
5391
+ return latestInteraction;
5271
5392
  }
5272
5393
  }
5273
5394
  }
5274
5395
  let indexOfInfoPathGteActivePath = info.findIndex(
5275
- (issuesInfo) => (issuesOrderCompareFn?.(issuesInfo.path, activePath) || compareSchemaPaths(formSchema, issuesInfo.path, activePath)) >= 0
5396
+ (issuesInfo) => (issuesOrderCompareFn?.(issuesInfo.path, latestInteraction) || compareSchemaPaths(formSchema, issuesInfo.path, latestInteraction)) >= 0
5276
5397
  );
5277
5398
  if (indexOfInfoPathGteActivePath === -1) {
5278
5399
  indexOfInfoPathGteActivePath = info.length;
5279
5400
  }
5280
- let curPath = activePath;
5401
+ let curPath = latestInteraction;
5281
5402
  while (!curPath.isRoot) {
5282
5403
  const curPathWithDescendants = curPath.append(
5283
5404
  AbsolutePathFragment.RecursiveWildcard
@@ -6745,10 +6866,10 @@ const TableControlAddRowTrigger = forwardRef(function TableControlAddRowTrigger2
6745
6866
  const { controller, size, maxRows } = useTableControlContext();
6746
6867
  const controlIsDisabled = useFormIsDisabled();
6747
6868
  const controlIsReadOnly = useFormIsReadOnly();
6748
- const setActivePath = useSetActivePath();
6869
+ const setLatestInteraction = useSetLatestInteraction();
6749
6870
  const handleClick = async () => {
6750
6871
  await controller.addRow();
6751
- setActivePath(controller.getState().path);
6872
+ setLatestInteraction(controller.getState().path);
6752
6873
  onRowAdded?.();
6753
6874
  };
6754
6875
  const shouldDisable = controller.useState(
@@ -6800,7 +6921,7 @@ const TableControlRemoveRowTrigger = forwardRef(function TableControlRemoveRowTr
6800
6921
  const { getState, index, useInitialized, useExists } = useTableControlRowContext();
6801
6922
  const initialized = useInitialized();
6802
6923
  const exists = useExists();
6803
- const setActivePath = useSetActivePath();
6924
+ const setLatestInteraction = useSetLatestInteraction();
6804
6925
  const [removing, setRemoving] = useState(false);
6805
6926
  const handleClick = async () => {
6806
6927
  const { path, dirty } = getState();
@@ -6815,7 +6936,7 @@ const TableControlRemoveRowTrigger = forwardRef(function TableControlRemoveRowTr
6815
6936
  setRemoving(true);
6816
6937
  onConfirmRowRemoval?.();
6817
6938
  await controller.removeRow(path);
6818
- setActivePath(controller.getState().path);
6939
+ setLatestInteraction(controller.getState().path);
6819
6940
  onRowRemoved?.();
6820
6941
  };
6821
6942
  const As = Slot;
@@ -7103,7 +7224,10 @@ function ValidateAction({
7103
7224
  );
7104
7225
  }
7105
7226
  export {
7227
+ ActivePathContext,
7228
+ ActivePathProvider,
7106
7229
  Annex,
7230
+ AnnexRegistrar,
7107
7231
  Annexes,
7108
7232
  AnnexesManager,
7109
7233
  CheckboxControl,
@@ -7116,10 +7240,12 @@ export {
7116
7240
  FormAppIssueMessages,
7117
7241
  FormAppStatus,
7118
7242
  FormPage,
7243
+ FormPageRegistrar,
7119
7244
  FormPages,
7120
7245
  FormPagesNavigation,
7121
7246
  FormStepContent,
7122
7247
  FormStepList,
7248
+ FormStepRegistrar,
7123
7249
  FormStepper,
7124
7250
  IssueAlert,
7125
7251
  IssueMessages,
@@ -7133,6 +7259,7 @@ export {
7133
7259
  PrefixSuffixContext,
7134
7260
  PrefixSuffixProvider,
7135
7261
  RadioGroupControl,
7262
+ RepetitiveAnnexRegistrar,
7136
7263
  SaveAction,
7137
7264
  SelectControl,
7138
7265
  SelectMultipleControl,
@@ -7158,6 +7285,7 @@ export {
7158
7285
  tableControlActionsColumn,
7159
7286
  tableControlIndexColumn,
7160
7287
  useActiveIssuesPanelBreadcrumb,
7288
+ useActivePathContext,
7161
7289
  useControlIssues,
7162
7290
  useDisplayIssueCodesInIssueMessages,
7163
7291
  useFocus,