@cosmicdrift/kumiko-renderer 0.387.0 → 0.389.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.
- package/dist/app/action-form-body.js +10 -2
- package/dist/app/action-form-support.d.ts +1 -0
- package/dist/app/action-form-support.js +1 -1
- package/dist/app/base-path-context.d.ts +9 -0
- package/dist/app/base-path-context.js +13 -0
- package/dist/app/base-path.d.ts +3 -0
- package/dist/app/base-path.js +27 -0
- package/dist/app/config-edit-body.d.ts +1 -1
- package/dist/app/config-edit-body.js +19 -6
- package/dist/app/config-edit-without-secret-fields.d.ts +2 -0
- package/dist/app/config-edit-without-secret-fields.js +28 -0
- package/dist/app/entity-edit-prefill.d.ts +11 -0
- package/dist/app/entity-edit-prefill.js +9 -4
- package/dist/app/entity-list-facet-specs.d.ts +3 -0
- package/dist/app/entity-list-facet-specs.js +65 -0
- package/dist/app/entity-list-screen.js +74 -206
- package/dist/app/extension-sections.d.ts +5 -3
- package/dist/app/form-schema.js +1 -1
- package/dist/app/header-actions-bar.js +1 -1
- package/dist/app/kumiko-screen.js +6 -14
- package/dist/app/list-bulk-actions.d.ts +18 -0
- package/dist/app/list-bulk-actions.js +54 -0
- package/dist/app/locked-screen-header.d.ts +9 -0
- package/dist/app/locked-screen-header.js +33 -0
- package/dist/app/projection-list-body.js +51 -46
- package/dist/app/row-actions.d.ts +3 -0
- package/dist/app/row-actions.js +10 -5
- package/dist/app/screen-shared.js +9 -2
- package/dist/app/use-entity-range-facets.d.ts +24 -0
- package/dist/app/use-entity-range-facets.js +64 -0
- package/dist/app/use-projection-date-range.d.ts +12 -0
- package/dist/app/use-projection-date-range.js +21 -0
- package/dist/app/use-screen-list-rows.d.ts +37 -0
- package/dist/app/use-screen-list-rows.js +104 -0
- package/dist/app/use-screen-list-table.d.ts +24 -0
- package/dist/app/use-screen-list-table.js +46 -0
- package/dist/components/edit-mounts.d.ts +2 -2
- package/dist/components/embedded-list-field.js +1 -1
- package/dist/components/list-group-page-bridges.d.ts +3 -0
- package/dist/components/list-group-page-bridges.js +31 -0
- package/dist/components/list-state-store.d.ts +16 -0
- package/dist/components/list-state-store.js +31 -0
- package/dist/components/related-list-section.js +51 -90
- package/dist/components/render-edit-action-button.d.ts +1 -3
- package/dist/components/render-edit-action-button.js +2 -2
- package/dist/components/render-edit.js +11 -8
- package/dist/components/render-field.js +11 -7
- package/dist/components/render-list-toolbar-action.d.ts +27 -0
- package/dist/components/render-list-toolbar-action.js +40 -0
- package/dist/components/render-list.d.ts +13 -29
- package/dist/components/render-list.js +72 -67
- package/dist/current-user.d.ts +2 -0
- package/dist/data-table-aggregate-types.d.ts +47 -0
- package/dist/data-table-aggregate-types.js +1 -0
- package/dist/data-table-bulk-types.d.ts +27 -0
- package/dist/data-table-bulk-types.js +1 -0
- package/dist/data-table-facet-types.d.ts +36 -0
- package/dist/data-table-facet-types.js +1 -0
- package/dist/data-table-view-types.d.ts +73 -0
- package/dist/data-table-view-types.js +1 -0
- package/dist/flow-diagram/render.js +9 -1
- package/dist/headless/form/form-controller.js +8 -0
- package/dist/headless/form/types.d.ts +1 -0
- package/dist/headless/format/index.js +89 -64
- package/dist/headless/view-model/edit.js +5 -1
- package/dist/headless/view-model/list.js +184 -149
- package/dist/headless/view-model/types.d.ts +7 -2
- package/dist/hooks/list-column-layout.d.ts +19 -0
- package/dist/hooks/list-column-layout.js +36 -0
- package/dist/hooks/list-group-pages.d.ts +26 -0
- package/dist/hooks/list-group-pages.js +37 -0
- package/dist/hooks/list-reset.d.ts +8 -0
- package/dist/hooks/list-reset.js +15 -0
- package/dist/hooks/list-server-groups.d.ts +39 -0
- package/dist/hooks/list-server-groups.js +84 -0
- package/dist/hooks/list-state-schema.d.ts +28 -0
- package/dist/hooks/list-state-schema.js +76 -0
- package/dist/hooks/live-refetch-scheduler.js +4 -0
- package/dist/hooks/use-bulk-run.d.ts +41 -0
- package/dist/hooks/use-bulk-run.js +65 -0
- package/dist/hooks/use-group-load-state.d.ts +11 -0
- package/dist/hooks/use-group-load-state.js +31 -0
- package/dist/hooks/use-list-aggregates.d.ts +38 -0
- package/dist/hooks/use-list-aggregates.js +107 -0
- package/dist/hooks/use-list-table.d.ts +38 -0
- package/dist/hooks/use-list-table.js +91 -0
- package/dist/hooks/use-list-view-state.d.ts +43 -0
- package/dist/hooks/use-list-view-state.js +161 -0
- package/dist/hooks/use-reference-lookup.d.ts +9 -1
- package/dist/hooks/use-reference-lookup.js +17 -3
- package/dist/hooks/use-related-list-rows.d.ts +27 -0
- package/dist/hooks/use-related-list-rows.js +81 -0
- package/dist/hooks/use-related-list-state.d.ts +15 -0
- package/dist/hooks/use-related-list-state.js +31 -0
- package/dist/hooks/use-remembered-list-params.d.ts +13 -0
- package/dist/hooks/use-remembered-list-params.js +53 -0
- package/dist/hooks/use-remembered-local-list-state.d.ts +26 -0
- package/dist/hooks/use-remembered-local-list-state.js +63 -0
- package/dist/i18n-defaults.js +59 -2
- package/dist/index.d.ts +4 -1
- package/dist/index.js +3 -0
- package/dist/primitives.d.ts +30 -47
- package/dist/web/app/create-app.d.ts +5 -0
- package/dist/web/app/create-app.js +15 -7
- package/dist/web/app/dashboard-body.js +17 -1
- package/dist/web/app/dashboard-panels.js +12 -2
- package/dist/web/app/history-leave-guard.d.ts +6 -0
- package/dist/web/app/history-leave-guard.js +115 -0
- package/dist/web/app/list-state-store-boot.d.ts +6 -0
- package/dist/web/app/list-state-store-boot.js +15 -0
- package/dist/web/app/nav.js +7 -54
- package/dist/web/app/session-list-state-store.d.ts +15 -0
- package/dist/web/app/session-list-state-store.js +117 -0
- package/dist/web/index.d.ts +2 -1
- package/dist/web/index.js +2 -1
- package/dist/web/layout/shell-header.js +2 -1
- package/dist/web/layout/uniform-bar-controls.d.ts +1 -0
- package/dist/web/layout/uniform-bar-controls.js +4 -0
- package/dist/web/lib/export-xlsx-request.d.ts +11 -0
- package/dist/web/lib/export-xlsx-request.js +46 -0
- package/dist/web/lib/file-ref-download.js +4 -1
- package/dist/web/primitives/action-menu.js +1 -1
- package/dist/web/primitives/code-input.d.ts +6 -0
- package/dist/web/primitives/code-input.js +31 -0
- package/dist/web/primitives/data-table/actions-column.d.ts +16 -0
- package/dist/web/primitives/data-table/actions-column.js +34 -0
- package/dist/web/primitives/data-table/bulk-selection-bar.d.ts +20 -0
- package/dist/web/primitives/data-table/bulk-selection-bar.js +37 -0
- package/dist/web/primitives/data-table/card-sort-select.d.ts +14 -0
- package/dist/web/primitives/data-table/card-sort-select.js +40 -0
- package/dist/web/primitives/data-table/column-layout-mode.d.ts +5 -0
- package/dist/web/primitives/data-table/column-layout-mode.js +10 -0
- package/dist/web/primitives/data-table/column-picker-section.d.ts +7 -0
- package/dist/web/primitives/data-table/column-picker-section.js +35 -0
- package/dist/web/primitives/data-table/column-resize-handle.d.ts +14 -0
- package/dist/web/primitives/data-table/column-resize-handle.js +46 -0
- package/dist/web/primitives/data-table/density.d.ts +14 -0
- package/dist/web/primitives/data-table/density.js +20 -0
- package/dist/web/primitives/data-table/export-button.d.ts +5 -0
- package/dist/web/primitives/data-table/export-button.js +37 -0
- package/dist/web/primitives/data-table/facet-cluster.d.ts +8 -0
- package/dist/web/primitives/data-table/facet-cluster.js +37 -0
- package/dist/web/primitives/data-table/group-band.d.ts +28 -0
- package/dist/web/primitives/data-table/group-band.js +53 -0
- package/dist/web/primitives/data-table/measure-value.d.ts +10 -0
- package/dist/web/primitives/data-table/measure-value.js +28 -0
- package/dist/web/primitives/data-table/pin-section.d.ts +8 -0
- package/dist/web/primitives/data-table/pin-section.js +20 -0
- package/dist/web/primitives/data-table/reset-all-button.d.ts +4 -0
- package/dist/web/primitives/data-table/reset-all-button.js +8 -0
- package/dist/web/primitives/data-table/server-group-header.d.ts +11 -0
- package/dist/web/primitives/data-table/server-group-header.js +51 -0
- package/dist/web/primitives/data-table/sortable-header.d.ts +17 -0
- package/dist/web/primitives/data-table/sortable-header.js +41 -0
- package/dist/web/primitives/data-table/table-chrome.d.ts +13 -0
- package/dist/web/primitives/data-table/table-chrome.js +30 -0
- package/dist/web/primitives/data-table/table-footer.d.ts +7 -0
- package/dist/web/primitives/data-table/table-footer.js +20 -0
- package/dist/web/primitives/data-table/toolbar-end-cluster.d.ts +10 -0
- package/dist/web/primitives/data-table/toolbar-end-cluster.js +13 -0
- package/dist/web/primitives/data-table/totals-footer.d.ts +24 -0
- package/dist/web/primitives/data-table/totals-footer.js +48 -0
- package/dist/web/primitives/data-table/use-bulk-selection.d.ts +21 -0
- package/dist/web/primitives/data-table/use-bulk-selection.js +66 -0
- package/dist/web/primitives/data-table/use-column-pins.d.ts +32 -0
- package/dist/web/primitives/data-table/use-column-pins.js +117 -0
- package/dist/web/primitives/data-table/use-column-sizing.d.ts +22 -0
- package/dist/web/primitives/data-table/use-column-sizing.js +44 -0
- package/dist/web/primitives/data-table/use-measured-column-widths.d.ts +15 -0
- package/dist/web/primitives/data-table/use-measured-column-widths.js +83 -0
- package/dist/web/primitives/data-table/use-row-keyboard-navigation.d.ts +22 -0
- package/dist/web/primitives/data-table/use-row-keyboard-navigation.js +65 -0
- package/dist/web/primitives/data-table/use-row-selection.d.ts +14 -0
- package/dist/web/primitives/data-table/use-row-selection.js +48 -0
- package/dist/web/primitives/data-table/use-view-columns.d.ts +6 -0
- package/dist/web/primitives/data-table/use-view-columns.js +17 -0
- package/dist/web/primitives/data-table/view-menu.d.ts +8 -0
- package/dist/web/primitives/data-table/view-menu.js +45 -0
- package/dist/web/primitives/data-table-cell.d.ts +0 -22
- package/dist/web/primitives/data-table-cell.js +1 -58
- package/dist/web/primitives/data-table.d.ts +1 -1
- package/dist/web/primitives/data-table.js +125 -146
- package/dist/web/primitives/facet-filter.js +1 -1
- package/dist/web/primitives/file-thumbnail.d.ts +3 -0
- package/dist/web/primitives/file-thumbnail.js +5 -0
- package/dist/web/primitives/file-value-list.d.ts +1 -1
- package/dist/web/primitives/file-value-list.js +5 -5
- package/dist/web/primitives/input.js +6 -1
- package/dist/web/primitives/layout-elements.js +6 -4
- package/dist/web/primitives/number-range-facet.d.ts +7 -0
- package/dist/web/primitives/number-range-facet.js +33 -0
- package/dist/web/primitives/page-header.js +3 -1
- package/dist/web/primitives/row-actions.js +3 -3
- package/dist/web/primitives/use-focus-on-mount.d.ts +2 -0
- package/dist/web/primitives/use-focus-on-mount.js +9 -0
- package/dist/web/widgets/bounded-load-cache.d.ts +5 -1
- package/dist/web/widgets/bounded-load-cache.js +49 -21
- package/dist/web/widgets/choice-cards.d.ts +17 -0
- package/dist/web/widgets/choice-cards.js +12 -0
- package/dist/web/widgets/collapsible-section.js +1 -1
- package/dist/web/widgets/detail-list.d.ts +5 -1
- package/dist/web/widgets/detail-list.js +5 -1
- package/dist/web/widgets/index.d.ts +1 -0
- package/dist/web/widgets/index.js +1 -0
- package/dist/web/widgets/pdf-preview.js +10 -5
- package/dist/web/widgets/plan-card.d.ts +2 -0
- package/dist/web/widgets/plan-card.js +1 -1
- package/dist/web/widgets/upload-zone.js +13 -5
- package/package.json +5 -5
- package/src/changes.json +158 -0
|
@@ -11,13 +11,15 @@ import { usePageHeaderCompact, usePageHeaderSlotAvailable } from "../page-header
|
|
|
11
11
|
import { shouldRenderActionsIconOnly, usePrimitives, } from "../primitives.js";
|
|
12
12
|
import { ListEmptyStateBody } from "./list-empty-state.js";
|
|
13
13
|
import { needsActionConfirm } from "./render-edit-action-button.js";
|
|
14
|
+
import { ToolbarActionView } from "./render-list-toolbar-action.js";
|
|
14
15
|
const SEARCH_DEBOUNCE_MS = 300;
|
|
15
|
-
// Icon-less actions are promoted too
|
|
16
|
+
// Icon-less actions are promoted too; on phones the call site falls back to a
|
|
17
|
+
// "plus" icon, on desktop the label carries the action.
|
|
16
18
|
function isHeaderPromotableToolbarAction(action) {
|
|
17
19
|
return action.style === "primary" && !needsActionConfirm(action);
|
|
18
20
|
}
|
|
19
21
|
export function RenderList(props) {
|
|
20
|
-
const { screen, entity, rows, featureName, translate: translateProp, onRowClick, emptyState, onCreate, createLabel, searchable = false, searchPlaceholder, description, emptyContent, narrowed = false, defaultFilterActive: defaultFilterActiveProp = false, itemNounKey, searchValue, onSearchChange, sort, onSortChange, pager, onReachEnd, loadingMore, hasMore, rowActions, rowActionMode,
|
|
22
|
+
const { screen, entity, rows, featureName, translate: translateProp, onRowClick, emptyState, onCreate, createLabel, searchable = false, searchPlaceholder, description, emptyContent, narrowed = false, defaultFilterActive: defaultFilterActiveProp = false, itemNounKey, searchValue, onSearchChange, sort, onSortChange, pager, onReachEnd, loadingMore, hasMore, rowActions, rowActionMode, bulk, rowTone, toolbarActions, filterFacets, filterValues, onFilterChange, onFilterReset, dateRangeFacets, onDateRangeChange, chromeless, scrollBody, screenPadding, expandedRowIds, onToggleRowExpanded, renderExpandedRow, ...tableBlockProps } = props;
|
|
21
23
|
// As in RenderEdit, fall back to the i18next context for translation;
|
|
22
24
|
// otherwise column headers would display raw i18n keys.
|
|
23
25
|
const t = useTranslation();
|
|
@@ -26,6 +28,10 @@ export function RenderList(props) {
|
|
|
26
28
|
const pageHeaderSlotAvailable = usePageHeaderSlotAvailable();
|
|
27
29
|
const pageHeaderCompact = usePageHeaderCompact();
|
|
28
30
|
const createInPageHeader = onCreate !== undefined && PageHeader !== undefined && pageHeaderSlotAvailable;
|
|
31
|
+
// Promoted actions that moved into the phone header's overflow menu have no button of
|
|
32
|
+
// their own, so their busy and error state lives here.
|
|
33
|
+
const [menuActionBusyId, setMenuActionBusyId] = useState(null);
|
|
34
|
+
const [menuActionError, setMenuActionError] = useState(null);
|
|
29
35
|
// Local search buffer + debounce. External changes (browser back,
|
|
30
36
|
// cross-component reset) are mirrored back; typing only fires
|
|
31
37
|
// onSearchChange after 300ms without further keystrokes.
|
|
@@ -37,24 +43,31 @@ export function RenderList(props) {
|
|
|
37
43
|
// The parent may echo our own debounced emits back late (router/URL round
|
|
38
44
|
// trip), possibly after a newer emit already went out; adopting such a stale
|
|
39
45
|
// echo would erase keys typed in between. All unechoed emits are kept, and a
|
|
40
|
-
// matching echo drops itself and every older one.
|
|
46
|
+
// matching echo drops itself and every older one. The render phase only reads
|
|
47
|
+
// the list (a discarded or repeated render must find the echo again); the
|
|
48
|
+
// matched entries are dropped in an effect.
|
|
41
49
|
const pendingEmitsRef = useRef([]);
|
|
42
50
|
if (searchValue !== syncedSearchValue) {
|
|
43
51
|
setSyncedSearchValue(searchValue);
|
|
44
|
-
|
|
45
|
-
if (echoIndex >= 0) {
|
|
46
|
-
pendingEmitsRef.current.splice(0, echoIndex + 1);
|
|
47
|
-
}
|
|
48
|
-
else {
|
|
52
|
+
if (!pendingEmitsRef.current.includes(searchValue ?? "")) {
|
|
49
53
|
setLocalQ(searchValue ?? "");
|
|
50
54
|
pendingEmitsRef.current = [];
|
|
51
55
|
}
|
|
52
56
|
}
|
|
57
|
+
useEffect(() => {
|
|
58
|
+
const echoIndex = pendingEmitsRef.current.indexOf(syncedSearchValue ?? "");
|
|
59
|
+
if (echoIndex >= 0)
|
|
60
|
+
pendingEmitsRef.current.splice(0, echoIndex + 1);
|
|
61
|
+
}, [syncedSearchValue]);
|
|
53
62
|
useEffect(() => {
|
|
54
63
|
if (onSearchChange === undefined)
|
|
55
64
|
return;
|
|
56
65
|
if (localQ === (searchValue ?? ""))
|
|
57
66
|
return;
|
|
67
|
+
// A late echo of an older emit changes searchValue but must not re-send
|
|
68
|
+
// the value that is already in flight.
|
|
69
|
+
if (pendingEmitsRef.current.at(-1) === localQ)
|
|
70
|
+
return;
|
|
58
71
|
const timer = setTimeout(() => {
|
|
59
72
|
pendingEmitsRef.current.push(localQ);
|
|
60
73
|
onSearchChange(localQ);
|
|
@@ -79,14 +92,15 @@ export function RenderList(props) {
|
|
|
79
92
|
labelField: c.refLabelField ?? "id",
|
|
80
93
|
})), [vm.columns, featureName]);
|
|
81
94
|
const [referenceLookups, setReferenceLookups] = useState({});
|
|
82
|
-
const handleLookupMap = useCallback((field,
|
|
95
|
+
const handleLookupMap = useCallback((field, lookup) => {
|
|
83
96
|
setReferenceLookups((prev) => {
|
|
84
|
-
// Skip the update when the
|
|
97
|
+
// Skip the update when the lookup is identical. Otherwise the bridge
|
|
85
98
|
// would re-render and send the same map back until React entered
|
|
86
99
|
// an infinite loop.
|
|
87
|
-
|
|
100
|
+
const current = prev[field];
|
|
101
|
+
if (current?.map === lookup.map && current.complete === lookup.complete)
|
|
88
102
|
return prev;
|
|
89
|
-
return { ...prev, [field]:
|
|
103
|
+
return { ...prev, [field]: lookup };
|
|
90
104
|
});
|
|
91
105
|
}, []);
|
|
92
106
|
const resolveReferenceLink = useReferenceLinkResolver();
|
|
@@ -100,15 +114,19 @@ export function RenderList(props) {
|
|
|
100
114
|
// the default is used only when none is set.
|
|
101
115
|
if (col.renderer !== undefined)
|
|
102
116
|
return col;
|
|
103
|
-
const
|
|
117
|
+
const published = referenceLookups[col.field];
|
|
118
|
+
const map = published?.map;
|
|
104
119
|
const unknownKey = UNRESOLVED_REFERENCE_LABEL_KEYS[`${col.refFeature ?? featureName}:${col.refEntity ?? ""}`];
|
|
105
120
|
// No raw id for entities that opt in: a placeholder while the lookup
|
|
106
|
-
// loads, the "unknown" label once
|
|
121
|
+
// loads, the "unknown" label once the complete lookup found nothing. A
|
|
122
|
+
// capped lookup page cannot prove absence, so it keeps the raw id.
|
|
107
123
|
const unresolvedLabel = (id) => unknownKey === undefined
|
|
108
124
|
? id
|
|
109
|
-
:
|
|
125
|
+
: published === undefined
|
|
110
126
|
? EMPTY_VALUE_PLACEHOLDER
|
|
111
|
-
:
|
|
127
|
+
: published.complete
|
|
128
|
+
? translate(unknownKey)
|
|
129
|
+
: id;
|
|
112
130
|
const labelField = col.refLabelField ?? "id";
|
|
113
131
|
const refEntity = col.refEntity ?? "";
|
|
114
132
|
// Only a row the user can see is linkable: resolved by the server
|
|
@@ -201,16 +219,41 @@ export function RenderList(props) {
|
|
|
201
219
|
const hasHeaderSlot = screen.slots?.header !== undefined;
|
|
202
220
|
const toolbarIconOnly = hasToolbarActions && shouldRenderActionsIconOnly(toolbarActions);
|
|
203
221
|
const createButton = onCreate !== undefined && createInPageHeader && pageHeaderCompact ? (_jsx(Button, { variant: "primary", icon: "plus", size: "icon", ariaLabel: effectiveCreateLabel, title: effectiveCreateLabel, onClick: onCreate, testId: "render-list-create" })) : onCreate !== undefined ? (_jsx(Button, { variant: "primary", icon: "plus", onClick: onCreate, testId: "render-list-create", className: "px-3.5", children: effectiveCreateLabel })) : undefined;
|
|
204
|
-
//
|
|
205
|
-
//
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
const
|
|
222
|
+
// Every primary toolbar action takes the page-header slot, left of the create
|
|
223
|
+
// button (which stays the rightmost primary action): labelled on desktop, so
|
|
224
|
+
// projection lists look like entity lists. On phones an icon button, or, next to
|
|
225
|
+
// the create button, an entry in the header's overflow menu: the header has no
|
|
226
|
+
// room for a labelled action beside it.
|
|
227
|
+
const promotedToolbarActions = PageHeader !== undefined && pageHeaderSlotAvailable
|
|
228
|
+
? (toolbarActions?.filter(isHeaderPromotableToolbarAction) ?? [])
|
|
229
|
+
: [];
|
|
230
|
+
const promotedInOverflowMenu = pageHeaderCompact && createInPageHeader && promotedToolbarActions.length > 0;
|
|
231
|
+
const promotedMenuItems = promotedInOverflowMenu
|
|
232
|
+
? promotedToolbarActions.map((a) => ({
|
|
233
|
+
id: a.id,
|
|
234
|
+
label: a.label,
|
|
235
|
+
...(a.icon !== undefined && { icon: a.icon }),
|
|
236
|
+
...(menuActionBusyId !== null && { disabled: true }),
|
|
237
|
+
testId: `render-list-toolbar-action-${a.id}`,
|
|
238
|
+
onSelect: () => {
|
|
239
|
+
setMenuActionBusyId(a.id);
|
|
240
|
+
setMenuActionError(null);
|
|
241
|
+
void (async () => {
|
|
242
|
+
try {
|
|
243
|
+
await a.onTrigger();
|
|
244
|
+
}
|
|
245
|
+
catch (e) {
|
|
246
|
+
setMenuActionError(e instanceof Error ? e.message : String(e));
|
|
247
|
+
}
|
|
248
|
+
finally {
|
|
249
|
+
setMenuActionBusyId(null);
|
|
250
|
+
}
|
|
251
|
+
})();
|
|
252
|
+
},
|
|
253
|
+
}))
|
|
254
|
+
: [];
|
|
255
|
+
const headerActions = promotedToolbarActions.length > 0 && !promotedInOverflowMenu ? (_jsxs(_Fragment, { children: [promotedToolbarActions.map((a) => (_jsx(ToolbarActionView, { action: pageHeaderCompact ? { ...a, icon: a.icon ?? "plus" } : a, iconOnly: pageHeaderCompact, Button: Button, Dialog: Dialog, Banner: Banner }, a.id))), createInPageHeader && createButton] })) : createInPageHeader ? (createButton) : undefined;
|
|
256
|
+
const toolbarActionsInToolbar = toolbarActions?.filter((a) => !promotedToolbarActions.includes(a)) ?? [];
|
|
214
257
|
const hasToolbarActionsInToolbar = toolbarActionsInToolbar.length > 0;
|
|
215
258
|
const toolbarEnd = hasHeaderSlot ||
|
|
216
259
|
hasToolbarActionsInToolbar ||
|
|
@@ -229,8 +272,7 @@ export function RenderList(props) {
|
|
|
229
272
|
const titleKey = `screen:${screen.id}.title`;
|
|
230
273
|
const resolvedTitle = translate(titleKey);
|
|
231
274
|
const toolbarTitle = resolvedTitle === titleKey ? screen.id : resolvedTitle;
|
|
232
|
-
|
|
233
|
-
return (_jsxs(_Fragment, { children: [createInPageHeader && PageHeader !== undefined && _jsx(PageHeader, { actions: createButton }), promotedActionButton !== undefined && PageHeader !== undefined && (_jsx(PageHeader, { actions: promotedActionButton })), referenceColumns.map((rc) => (_jsx(ReferenceLookupBridge, { field: rc.field, refEntity: rc.refEntity, labelField: rc.labelField, featureName: rc.refFeature, onMap: handleLookupMap }, rc.field))), _jsx(DataTable, { columns: enrichedVm.columns, rows: enrichedVm.rows, toolbarTitle: toolbarTitle, ...(onRowClick !== undefined && { onRowClick }), ...(composedEmptyState !== undefined && { emptyState: composedEmptyState }), ...(toolbarStart !== undefined && { toolbarStart }), ...(description !== undefined && { toolbarDescription: description }), ...(toolbarEnd !== undefined && { toolbarEnd }), ...(sort !== undefined && { sort }), ...(onSortChange !== undefined && { onSortChange }), ...(pager !== undefined && { pager }), ...(onReachEnd !== undefined && { onReachEnd }), ...(loadingMore !== undefined && { loadingMore }), ...(hasMore !== undefined && { hasMore }), ...(itemNoun !== undefined && { itemNoun }), ...(rowActions !== undefined && { rowActions }), ...(rowActionMode !== undefined && { rowActionMode }), ...(rowGrouping !== undefined && { rowGrouping }), ...(rowTone !== undefined && { rowTone }), ...(filterFacets !== undefined && { filterFacets }), ...(filterValues !== undefined && { filterValues }), ...(onFilterChange !== undefined && { onFilterChange }), ...(onFilterReset !== undefined && { onFilterReset }), ...(dateRangeFacets !== undefined && { dateRangeFacets }), ...(onDateRangeChange !== undefined && { onDateRangeChange }), ...(chromeless !== undefined && { chromeless }), ...(scrollBody !== undefined && { scrollBody }), ...(screenPadding !== undefined && { screenPadding }), ...(expandedRowIds !== undefined && { expandedRowIds }), ...(onToggleRowExpanded !== undefined && { onToggleRowExpanded }), ...(renderExpandedRow !== undefined && { renderExpandedRow }), testId: "render-list-table" })] }));
|
|
275
|
+
return (_jsxs(_Fragment, { children: [headerActions !== undefined && PageHeader !== undefined && (_jsx(PageHeader, { actions: headerActions, ...(promotedInOverflowMenu && { overflowItems: promotedMenuItems }) })), menuActionError !== null && (_jsx(Banner, { variant: "error", testId: "render-list-toolbar-action-error", children: menuActionError })), referenceColumns.map((rc) => (_jsx(ReferenceLookupBridge, { field: rc.field, refEntity: rc.refEntity, labelField: rc.labelField, featureName: rc.refFeature, onMap: handleLookupMap }, rc.field))), _jsx(DataTable, { columns: enrichedVm.columns, rows: enrichedVm.rows, toolbarTitle: toolbarTitle, ...(onRowClick !== undefined && { onRowClick }), ...(composedEmptyState !== undefined && { emptyState: composedEmptyState }), ...(toolbarStart !== undefined && { toolbarStart }), ...(description !== undefined && { toolbarDescription: description }), ...(toolbarEnd !== undefined && { toolbarEnd }), ...(sort !== undefined && { sort }), ...(onSortChange !== undefined && { onSortChange }), ...(pager !== undefined && { pager }), ...(onReachEnd !== undefined && { onReachEnd }), ...(loadingMore !== undefined && { loadingMore }), ...(hasMore !== undefined && { hasMore }), ...(itemNoun !== undefined && { itemNoun }), ...(rowActions !== undefined && { rowActions }), ...(rowActionMode !== undefined && { rowActionMode }), ...(bulk !== undefined && { bulk }), ...(rowTone !== undefined && { rowTone }), ...(filterFacets !== undefined && { filterFacets }), ...(filterValues !== undefined && { filterValues }), ...(onFilterChange !== undefined && { onFilterChange }), ...(onFilterReset !== undefined && { onFilterReset }), ...(dateRangeFacets !== undefined && { dateRangeFacets }), ...(onDateRangeChange !== undefined && { onDateRangeChange }), ...(chromeless !== undefined && { chromeless }), ...(scrollBody !== undefined && { scrollBody }), ...(screenPadding !== undefined && { screenPadding }), ...(expandedRowIds !== undefined && { expandedRowIds }), ...(onToggleRowExpanded !== undefined && { onToggleRowExpanded }), ...(renderExpandedRow !== undefined && { renderExpandedRow }), ...tableBlockProps, testId: "render-list-table" })] }));
|
|
234
276
|
}
|
|
235
277
|
// Header slot in the list toolbar (toolbarEnd): `screen.slots.header` is
|
|
236
278
|
// a PlatformComponent (`{ react: { __component: "X" } }`) resolved through
|
|
@@ -273,47 +315,10 @@ function ReferenceLookupBridge({ field, refEntity, labelField, featureName, onMa
|
|
|
273
315
|
const resolved = !lookup.loading && !lookup.failed;
|
|
274
316
|
useEffect(() => {
|
|
275
317
|
if (resolved)
|
|
276
|
-
onMap(field, lookup.map);
|
|
277
|
-
}, [field, lookup.map, resolved, onMap]);
|
|
318
|
+
onMap(field, { map: lookup.map, complete: lookup.complete });
|
|
319
|
+
}, [field, lookup.map, lookup.complete, resolved, onMap]);
|
|
278
320
|
return null;
|
|
279
321
|
}
|
|
280
|
-
// ToolbarActionView renders one button per toolbar action, with a confirm
|
|
281
|
-
// dialog when required. It follows the inline RowAction pattern but has no
|
|
282
|
-
// row context. The busy state covers async onTrigger; the dialog opens before
|
|
283
|
-
// the trigger when confirmation is required for confirm/danger actions.
|
|
284
|
-
function ToolbarActionView({ action, iconOnly = false, Button, Dialog, Banner, }) {
|
|
285
|
-
const [busy, setBusy] = useState(false);
|
|
286
|
-
const [confirmOpen, setConfirmOpen] = useState(false);
|
|
287
|
-
// Toolbar actions do not have access to the toast primitive, unlike
|
|
288
|
-
// rowActions routed through the DataTable primitive, so an inline error
|
|
289
|
-
// banner is used to surface failures. Without it, a swallowed failure result
|
|
290
|
-
// would look to the user as though nothing happened.
|
|
291
|
-
const [errorText, setErrorText] = useState(null);
|
|
292
|
-
const trigger = async () => {
|
|
293
|
-
setBusy(true);
|
|
294
|
-
setErrorText(null);
|
|
295
|
-
try {
|
|
296
|
-
await action.onTrigger();
|
|
297
|
-
}
|
|
298
|
-
catch (e) {
|
|
299
|
-
setErrorText(e instanceof Error ? e.message : String(e));
|
|
300
|
-
}
|
|
301
|
-
finally {
|
|
302
|
-
setBusy(false);
|
|
303
|
-
}
|
|
304
|
-
};
|
|
305
|
-
const variant = action.style ?? "secondary";
|
|
306
|
-
const needsConfirm = needsActionConfirm(action);
|
|
307
|
-
const showIconOnly = iconOnly && action.icon !== undefined;
|
|
308
|
-
return (_jsxs(_Fragment, { children: [_jsx(Button, { variant: variant, loading: busy, ...(action.icon !== undefined && { icon: action.icon }), size: showIconOnly ? "icon" : "sm", ...(showIconOnly && { ariaLabel: action.label, title: action.label }), onClick: () => {
|
|
309
|
-
if (needsConfirm) {
|
|
310
|
-
setConfirmOpen(true);
|
|
311
|
-
}
|
|
312
|
-
else {
|
|
313
|
-
void trigger();
|
|
314
|
-
}
|
|
315
|
-
}, testId: `render-list-toolbar-action-${action.id}`, children: showIconOnly ? null : action.label }), _jsx(Dialog, { open: confirmOpen, onOpenChange: setConfirmOpen, title: action.label, ...(action.confirm !== undefined && { description: action.confirm }), confirmLabel: action.confirmLabel ?? action.label, ...(action.style === "danger" && { variant: "danger" }), onConfirm: trigger, testId: `render-list-toolbar-action-${action.id}-dialog` }), errorText !== null && (_jsx(Banner, { variant: "error", testId: `render-list-toolbar-action-${action.id}-error`, children: errorText }))] }));
|
|
316
|
-
}
|
|
317
322
|
function isPlainRecord(value) {
|
|
318
323
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
319
324
|
}
|
package/dist/current-user.d.ts
CHANGED
|
@@ -7,6 +7,8 @@ export type CurrentUser = {
|
|
|
7
7
|
readonly displayName: string;
|
|
8
8
|
/** Effective IANA zone of the user (override, else tenant). */
|
|
9
9
|
readonly timeZone?: string;
|
|
10
|
+
/** Active tenant; scopes per-user browser state such as remembered list views. */
|
|
11
|
+
readonly tenantId?: string;
|
|
10
12
|
};
|
|
11
13
|
export declare function CurrentUserProvider({ user, children, }: {
|
|
12
14
|
readonly user: CurrentUser | undefined;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { AggregateKey } from "@cosmicdrift/kumiko-types/aggregate-types";
|
|
2
|
+
import type { ListAggregateMeasureKind, ListAggregateValue } from "@cosmicdrift/kumiko-types/list-aggregate-types";
|
|
3
|
+
/** One server-computed measure; a money measure holds one minor-unit value per currency. */
|
|
4
|
+
export type DataTableMeasure = {
|
|
5
|
+
readonly field: string;
|
|
6
|
+
readonly kind: ListAggregateMeasureKind;
|
|
7
|
+
readonly value: ListAggregateValue;
|
|
8
|
+
};
|
|
9
|
+
export type DataTableServerGroup = {
|
|
10
|
+
/** `relatedListGroupKey` of the group value; stable across reloads. */
|
|
11
|
+
readonly key: string;
|
|
12
|
+
/** Raw group value, rendered through the group column when `label` is absent. */
|
|
13
|
+
readonly value: AggregateKey;
|
|
14
|
+
readonly label?: string;
|
|
15
|
+
/** Rows of the whole group on the server, not the loaded ones. */
|
|
16
|
+
readonly count: number;
|
|
17
|
+
readonly measures: readonly DataTableMeasure[];
|
|
18
|
+
readonly collapsed: boolean;
|
|
19
|
+
/** How many of the table's `rows` belong to this group; rows arrive in group order. */
|
|
20
|
+
readonly rowCount: number;
|
|
21
|
+
readonly hasMore: boolean;
|
|
22
|
+
readonly loading: boolean;
|
|
23
|
+
};
|
|
24
|
+
/** Server-side grouping: groups are the load unit, each loads its own rows. */
|
|
25
|
+
export type DataTableServerGroups = {
|
|
26
|
+
readonly field: string;
|
|
27
|
+
readonly groups: readonly DataTableServerGroup[];
|
|
28
|
+
readonly totalCount: number;
|
|
29
|
+
readonly onToggle: (key: string) => void;
|
|
30
|
+
readonly onLoadMore: (key: string) => void;
|
|
31
|
+
readonly onHeaderVisible: (key: string) => void;
|
|
32
|
+
};
|
|
33
|
+
export type DataTableTotals = {
|
|
34
|
+
readonly measures: readonly DataTableMeasure[];
|
|
35
|
+
};
|
|
36
|
+
/** `truncated`: more groups than the server answers. `searchTruncated`: the search hit its cap. */
|
|
37
|
+
export type DataTableListNotice = "truncated" | "searchTruncated";
|
|
38
|
+
export type DataTableRangeBound = "min" | "max";
|
|
39
|
+
/** "From/to" inputs on a number field. Values are the typed strings, "" = open bound. */
|
|
40
|
+
export type DataTableRangeFacet = {
|
|
41
|
+
readonly field: string;
|
|
42
|
+
readonly label: string;
|
|
43
|
+
readonly min: string;
|
|
44
|
+
readonly max: string;
|
|
45
|
+
/** Shown after the inputs, e.g. the money field's currency. */
|
|
46
|
+
readonly unit?: string;
|
|
47
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { IconKey } from "@cosmicdrift/kumiko-types/nav-icon";
|
|
2
|
+
import type { ListRowViewModel } from "./headless/index.ts";
|
|
3
|
+
export type BulkRowOutcome = {
|
|
4
|
+
readonly kind: "succeeded";
|
|
5
|
+
} | {
|
|
6
|
+
readonly kind: "failed";
|
|
7
|
+
readonly message: string;
|
|
8
|
+
};
|
|
9
|
+
export type DataTableBulkAction = {
|
|
10
|
+
readonly id: string;
|
|
11
|
+
/** Translated label of the underlying row action. */
|
|
12
|
+
readonly label: string;
|
|
13
|
+
readonly style?: "primary" | "secondary" | "danger";
|
|
14
|
+
readonly icon?: IconKey;
|
|
15
|
+
/** Present: one confirm dialog for the whole selection, with this text for `count` rows. */
|
|
16
|
+
readonly confirmText?: (count: number) => string;
|
|
17
|
+
readonly confirmLabel?: string;
|
|
18
|
+
/** False: the row action's `visible` hides it for this row; the run skips the row. */
|
|
19
|
+
readonly appliesTo: (row: ListRowViewModel) => boolean;
|
|
20
|
+
/** Exactly one server write for one row. Never throws for a rejected write. */
|
|
21
|
+
readonly writeRow: (row: ListRowViewModel) => Promise<BulkRowOutcome>;
|
|
22
|
+
};
|
|
23
|
+
export type DataTableBulk = {
|
|
24
|
+
readonly actions: readonly DataTableBulkAction[];
|
|
25
|
+
/** Reloads the list once after a run. */
|
|
26
|
+
readonly reload: () => Promise<void>;
|
|
27
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
export type DataTableFacet = {
|
|
2
|
+
/** Entity-Feldname (= URL-State-Key + payload.filters[].field). */
|
|
3
|
+
readonly field: string;
|
|
4
|
+
/** Translated Button-Label ("Status", "Active"). */
|
|
5
|
+
readonly label: string;
|
|
6
|
+
/** Selectable values with display labels. `value` is the raw filter value
|
|
7
|
+
* (a select option or `"true"`/`"false"`), transported as a string. */
|
|
8
|
+
readonly options: readonly {
|
|
9
|
+
readonly value: string;
|
|
10
|
+
readonly label: string;
|
|
11
|
+
}[];
|
|
12
|
+
/** Present: render the facet as a row of single-choice toggle chips instead
|
|
13
|
+
* of a dropdown. A chip is pressed while the selection equals its values. */
|
|
14
|
+
readonly chips?: readonly DataTableFacetChip[];
|
|
15
|
+
/** Chips only: show each chip's `count` next to its label. */
|
|
16
|
+
readonly showCounts?: boolean;
|
|
17
|
+
/** Chips only: hide chips whose `count` is 0 (unless pressed). */
|
|
18
|
+
readonly hideEmpty?: boolean;
|
|
19
|
+
};
|
|
20
|
+
export type DataTableFacetChip = {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
/** Translated chip label. */
|
|
23
|
+
readonly label: string;
|
|
24
|
+
/** Selection a click sets; `[]` clears the facet. */
|
|
25
|
+
readonly values: readonly string[];
|
|
26
|
+
/** Matching row count, undefined while unknown. */
|
|
27
|
+
readonly count?: number;
|
|
28
|
+
};
|
|
29
|
+
export type DataTableDateRangeFacet = {
|
|
30
|
+
/** The facet's namespace (its date field) — a key, not a filter value. */
|
|
31
|
+
readonly field: string;
|
|
32
|
+
/** Translated label ("When"). */
|
|
33
|
+
readonly label: string;
|
|
34
|
+
readonly from: string;
|
|
35
|
+
readonly to: string;
|
|
36
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { ListColumnPin, ListTableDensity } from "@cosmicdrift/kumiko-types/screen";
|
|
2
|
+
import type { DataTableListNotice, DataTableRangeBound, DataTableRangeFacet, DataTableServerGroups, DataTableTotals } from "./data-table-aggregate-types.ts";
|
|
3
|
+
export type DataTableDensity = ListTableDensity;
|
|
4
|
+
export type DataTableColumnPin = ListColumnPin;
|
|
5
|
+
export type DataTableColumnPins = Readonly<Record<string, DataTableColumnPin>>;
|
|
6
|
+
export type DataTableColumnWidths = Readonly<Record<string, number>>;
|
|
7
|
+
/** One column in the view menu. The label comes from the DataTable's own
|
|
8
|
+
* column of the same `field`. */
|
|
9
|
+
export type DataTableViewColumn = {
|
|
10
|
+
readonly field: string;
|
|
11
|
+
readonly visible: boolean;
|
|
12
|
+
};
|
|
13
|
+
export type DataTableViewOptions = {
|
|
14
|
+
/** Present: the view menu has a column section (visibility + order). The
|
|
15
|
+
* order lists every column; hidden ones stay in the list. */
|
|
16
|
+
readonly columns?: readonly DataTableViewColumn[];
|
|
17
|
+
readonly onColumnsChange?: (next: readonly DataTableViewColumn[]) => void;
|
|
18
|
+
readonly density: DataTableDensity;
|
|
19
|
+
/** Present: the view menu has a density section. */
|
|
20
|
+
readonly onDensityChange?: (next: DataTableDensity) => void;
|
|
21
|
+
/** Present: pinning is on and the view menu has a pin section. Unpinned columns have no entry. */
|
|
22
|
+
readonly pins?: DataTableColumnPins;
|
|
23
|
+
readonly onPinsChange?: (next: DataTableColumnPins) => void;
|
|
24
|
+
/** Present: resizing is on; the table switches to table-layout fixed. Every column has an entry (px). */
|
|
25
|
+
readonly widths?: DataTableColumnWidths;
|
|
26
|
+
/** Present together with `widths`; every column has an entry (px). */
|
|
27
|
+
readonly minWidths?: DataTableColumnWidths;
|
|
28
|
+
readonly onWidthChange?: (field: string, width: number) => void;
|
|
29
|
+
readonly keyboardNavigation?: boolean;
|
|
30
|
+
/** Present: the view menu has a "group by" section. */
|
|
31
|
+
readonly groupBy?: DataTableGroupByChoice;
|
|
32
|
+
/** False once columns, order or density differ from the screen default. */
|
|
33
|
+
readonly viewIsDefault: boolean;
|
|
34
|
+
readonly onViewReset: () => void;
|
|
35
|
+
};
|
|
36
|
+
export type DataTableGroupByChoice = {
|
|
37
|
+
/** Active group field, "" = not grouped. */
|
|
38
|
+
readonly value: string;
|
|
39
|
+
readonly fields: readonly string[];
|
|
40
|
+
readonly onChange: (next: string) => void;
|
|
41
|
+
};
|
|
42
|
+
export type DataTableResetAll = {
|
|
43
|
+
/** True while something deviates from the screen default. */
|
|
44
|
+
readonly active: boolean;
|
|
45
|
+
readonly onReset: () => void;
|
|
46
|
+
};
|
|
47
|
+
/** What the export button sends to POST /api/export/xlsx (xlsx is the only format). */
|
|
48
|
+
export type DataTableExport = {
|
|
49
|
+
/** Screen QN `<feature>:screen:<id>`. */
|
|
50
|
+
readonly screen: string;
|
|
51
|
+
readonly screenShortId: string;
|
|
52
|
+
/** The list payload without paging keys; the server reads the whole filtered list. */
|
|
53
|
+
readonly payload: Readonly<Record<string, unknown>>;
|
|
54
|
+
/** Visible column ids in display order. */
|
|
55
|
+
readonly columns: readonly string[];
|
|
56
|
+
};
|
|
57
|
+
/** The DataTable props a screen's `table` block feeds. */
|
|
58
|
+
export type DataTableTableBlock = {
|
|
59
|
+
/** View menu (column picker, density); absent = no menu. */
|
|
60
|
+
readonly view?: DataTableViewOptions;
|
|
61
|
+
/** Replaces the facet reset with one that also covers sort, search and page size. */
|
|
62
|
+
readonly resetAll?: DataTableResetAll;
|
|
63
|
+
/** Present: the toolbar has an export button (web only). */
|
|
64
|
+
readonly exportList?: DataTableExport;
|
|
65
|
+
/** Present: the rows are grouped on the server; replaces pager and infinite scroll. */
|
|
66
|
+
readonly serverGroups?: DataTableServerGroups;
|
|
67
|
+
/** Present: a footer row with the server-computed totals of the filtered list. */
|
|
68
|
+
readonly totals?: DataTableTotals;
|
|
69
|
+
/** Present: the toolbar says the answer is incomplete and asks to narrow the list. */
|
|
70
|
+
readonly notice?: DataTableListNotice;
|
|
71
|
+
readonly rangeFacets?: readonly DataTableRangeFacet[];
|
|
72
|
+
readonly onRangeChange?: (field: string, bound: DataTableRangeBound, value: string) => void;
|
|
73
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -23,7 +23,15 @@ function cardSize(node) {
|
|
|
23
23
|
return { width, height };
|
|
24
24
|
}
|
|
25
25
|
function assertEdgesReferenceKnownNodes(spec) {
|
|
26
|
-
const ids = new Set(
|
|
26
|
+
const ids = new Set();
|
|
27
|
+
for (const node of spec.nodes) {
|
|
28
|
+
if (ids.has(node.id))
|
|
29
|
+
throw new Error(`FlowSpec: duplicate node id "${node.id}"`);
|
|
30
|
+
ids.add(node.id);
|
|
31
|
+
if (node.icon !== undefined && node.icon !== "ai" && !Object.hasOwn(ICONS, node.icon)) {
|
|
32
|
+
throw new Error(`FlowSpec: node "${node.id}" has unknown icon "${node.icon}"`);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
27
35
|
for (const edge of spec.edges) {
|
|
28
36
|
if (!ids.has(edge.from))
|
|
29
37
|
throw new Error(`FlowSpec: edge references unknown node "${edge.from}"`);
|
|
@@ -217,6 +217,13 @@ export function createFormController(options) {
|
|
|
217
217
|
errors = Object.freeze({});
|
|
218
218
|
invalidate();
|
|
219
219
|
}
|
|
220
|
+
function runRebaseFields(fields) {
|
|
221
|
+
initial = {
|
|
222
|
+
...initial,
|
|
223
|
+
...Object.fromEntries(fields.map((field) => [field, values[field]])),
|
|
224
|
+
};
|
|
225
|
+
invalidate();
|
|
226
|
+
}
|
|
220
227
|
// Stale-submit-safe rebase. Difference to runRebase: the baseline is
|
|
221
228
|
// taken from the values that were actually SENT to the server, not
|
|
222
229
|
// from `values` at the moment the server replied. If the user typed
|
|
@@ -296,6 +303,7 @@ export function createFormController(options) {
|
|
|
296
303
|
invalidate();
|
|
297
304
|
},
|
|
298
305
|
rebase: runRebase,
|
|
306
|
+
rebaseFields: runRebaseFields,
|
|
299
307
|
setCtx(nextCtx) {
|
|
300
308
|
// Cast back to TCtx: setCtx's public signature takes unknown so
|
|
301
309
|
// callers don't need to plumb generics. If conditions depend on ctx
|
|
@@ -33,6 +33,7 @@ export type FormController<TValues extends FormValues> = {
|
|
|
33
33
|
validate(scope?: readonly FormScopeKey<TValues>[]): boolean;
|
|
34
34
|
reset(): void;
|
|
35
35
|
rebase(): void;
|
|
36
|
+
rebaseFields(fields: readonly string[]): void;
|
|
36
37
|
setCtx(ctx: unknown): void;
|
|
37
38
|
submit<TData = unknown>(): Promise<SubmitResult<TData>>;
|
|
38
39
|
};
|