@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
|
@@ -129,6 +129,7 @@ export function ActionFormBody({ schema, screen, translate, entityId, initialOve
|
|
|
129
129
|
})
|
|
130
130
|
: undefined, loadableFieldNames);
|
|
131
131
|
const loadedValues = loadState?.status === "ready" ? loadState.values : undefined;
|
|
132
|
+
const refetchLoadedValues = loadState?.status === "ready" ? loadState.refetch : undefined;
|
|
132
133
|
const initial = useMemo(() => {
|
|
133
134
|
// With `load` the loaded record is the only source of values: a crafted link
|
|
134
135
|
// or a row's params must not seed fields the record would otherwise own.
|
|
@@ -204,8 +205,14 @@ export function ActionFormBody({ schema, screen, translate, entityId, initialOve
|
|
|
204
205
|
else if (hasSuccessLinks || screen.resetOnSuccess === true) {
|
|
205
206
|
publish(effectiveTranslate("kumiko.actionForm.success"));
|
|
206
207
|
}
|
|
207
|
-
if (screen.resetOnSuccess === true)
|
|
208
|
-
|
|
208
|
+
if (screen.resetOnSuccess === true) {
|
|
209
|
+
// A loaded record is stale after the write: reseed the form from the fresh one.
|
|
210
|
+
const remountForm = () => setFormEpoch((epoch) => epoch + 1);
|
|
211
|
+
if (refetchLoadedValues === undefined)
|
|
212
|
+
remountForm();
|
|
213
|
+
else
|
|
214
|
+
void refetchLoadedValues().finally(remountForm);
|
|
215
|
+
}
|
|
209
216
|
}
|
|
210
217
|
// Without a redirect the form stays put; returnTo only replaces an existing
|
|
211
218
|
// navigation, and never one to a record screen (see redirectTargetsRecord).
|
|
@@ -227,6 +234,7 @@ export function ActionFormBody({ schema, screen, translate, entityId, initialOve
|
|
|
227
234
|
dispatcher,
|
|
228
235
|
locale,
|
|
229
236
|
effectiveTranslate,
|
|
237
|
+
refetchLoadedValues,
|
|
230
238
|
]);
|
|
231
239
|
// The success box disappears with the next edit; a submit clears it itself.
|
|
232
240
|
const handleDirtyChange = useCallback((dirty) => {
|
|
@@ -21,6 +21,7 @@ export type ActionFormLoadState = {
|
|
|
21
21
|
} | {
|
|
22
22
|
readonly status: "ready";
|
|
23
23
|
readonly values: Readonly<Record<string, unknown>>;
|
|
24
|
+
readonly refetch: () => Promise<void>;
|
|
24
25
|
};
|
|
25
26
|
/** A sensitive, writeOnly or password field never takes a value from a loaded
|
|
26
27
|
* record: the form must not echo a secret back into an input. */
|
|
@@ -53,7 +53,7 @@ export function useActionFormLoad(load, recordId, fieldNames) {
|
|
|
53
53
|
return { status: "loading" };
|
|
54
54
|
if (query.data === null)
|
|
55
55
|
return { status: "missing-record" };
|
|
56
|
-
return { status: "ready", values };
|
|
56
|
+
return { status: "ready", values, refetch: query.refetch };
|
|
57
57
|
}
|
|
58
58
|
/** Banner for every `load` state that is not "ready". */
|
|
59
59
|
export function ActionFormLoadStatus({ state, screenId, translate, }) {
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ReactNode } from "react";
|
|
2
|
+
/** Provides the URL prefix the app is mounted under to components outside the NavProvider
|
|
3
|
+
* (auth gates and pages). `createKumikoApp({ basePath })` mounts it. */
|
|
4
|
+
export declare function BasePathProvider({ basePath, children, }: {
|
|
5
|
+
readonly basePath: string | undefined;
|
|
6
|
+
readonly children: ReactNode;
|
|
7
|
+
}): ReactNode;
|
|
8
|
+
/** Normalized base path ("" when the app is served from the root). */
|
|
9
|
+
export declare function useBasePath(): string;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
import { normalizeBasePath } from "./base-path.js";
|
|
4
|
+
const BasePathContext = createContext("");
|
|
5
|
+
/** Provides the URL prefix the app is mounted under to components outside the NavProvider
|
|
6
|
+
* (auth gates and pages). `createKumikoApp({ basePath })` mounts it. */
|
|
7
|
+
export function BasePathProvider({ basePath, children, }) {
|
|
8
|
+
return (_jsx(BasePathContext.Provider, { value: normalizeBasePath(basePath), children: children }));
|
|
9
|
+
}
|
|
10
|
+
/** Normalized base path ("" when the app is served from the root). */
|
|
11
|
+
export function useBasePath() {
|
|
12
|
+
return useContext(BasePathContext);
|
|
13
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
// basePath mounts an app under a URL prefix (for example, `/admin`). Pure helpers shared by the
|
|
2
|
+
// browser nav adapter and the auth pages, which sit outside the NavProvider.
|
|
3
|
+
export function normalizeBasePath(raw) {
|
|
4
|
+
if (raw === undefined || raw === "" || raw === "/")
|
|
5
|
+
return "";
|
|
6
|
+
const withLeading = raw.startsWith("/") ? raw : `/${raw}`;
|
|
7
|
+
return withLeading.endsWith("/") ? withLeading.slice(0, -1) : withLeading;
|
|
8
|
+
}
|
|
9
|
+
// Returns the in-app (basePath-relative) path when `path` is under basePath; otherwise
|
|
10
|
+
// `undefined`. The segment boundary is strict: "/administrator" does not match "/admin".
|
|
11
|
+
export function stripBasePath(path, basePath) {
|
|
12
|
+
if (basePath === "")
|
|
13
|
+
return path;
|
|
14
|
+
if (path === basePath)
|
|
15
|
+
return "/";
|
|
16
|
+
if (path.startsWith(`${basePath}/`))
|
|
17
|
+
return path.slice(basePath.length);
|
|
18
|
+
return undefined;
|
|
19
|
+
}
|
|
20
|
+
export function prependBasePath(path, basePath) {
|
|
21
|
+
if (basePath === "")
|
|
22
|
+
return path;
|
|
23
|
+
// An in-app path is absolute: "/" or "/screen-id" or "/screen-id/entity-id". "/" becomes basePath.
|
|
24
|
+
if (path === "/" || path === "")
|
|
25
|
+
return basePath;
|
|
26
|
+
return `${basePath}${path}`;
|
|
27
|
+
}
|
|
@@ -2,7 +2,7 @@ import type { ConfigEditScreenDefinition } from "@cosmicdrift/kumiko-framework/u
|
|
|
2
2
|
import { type ReactNode } from "react";
|
|
3
3
|
import type { Translate } from "../headless/index.ts";
|
|
4
4
|
import type { FeatureSchema } from "./feature-schema.ts";
|
|
5
|
-
export declare function ConfigEditBody({ schema, screen, translate, }: {
|
|
5
|
+
export declare function ConfigEditBody({ schema, screen: declaredScreen, translate, }: {
|
|
6
6
|
readonly schema: FeatureSchema;
|
|
7
7
|
readonly screen: ConfigEditScreenDefinition;
|
|
8
8
|
readonly translate?: Translate;
|
|
@@ -11,6 +11,7 @@ import { useTranslation } from "../i18n.js";
|
|
|
11
11
|
import { usePrimitives } from "../primitives.js";
|
|
12
12
|
import { screenFillsHeight } from "../screen-fills-height.js";
|
|
13
13
|
import { buildConfigEditSchema, synthesizeConfigEditEntity, synthesizeConfigEditScreen, } from "./config-edit-shim.js";
|
|
14
|
+
import { configEditWithoutSecretFields } from "./config-edit-without-secret-fields.js";
|
|
14
15
|
import { buildInitialValues } from "./entity-edit-prefill.js";
|
|
15
16
|
import { layoutEditFields } from "./layout-fields.js";
|
|
16
17
|
import { QueryErrorBanner, QueryLoadingBanner } from "./query-state-banners.js";
|
|
@@ -89,7 +90,7 @@ function isEmptyNumericFormValue(value) {
|
|
|
89
90
|
return true;
|
|
90
91
|
return typeof value === "number" && !Number.isFinite(value);
|
|
91
92
|
}
|
|
92
|
-
export function ConfigEditBody({ schema, screen, translate, }) {
|
|
93
|
+
export function ConfigEditBody({ schema, screen: declaredScreen, translate, }) {
|
|
93
94
|
const { ConfigCascadeView } = usePrimitives();
|
|
94
95
|
const t = useTranslation();
|
|
95
96
|
const effectiveTranslate = translate ?? t;
|
|
@@ -97,10 +98,18 @@ export function ConfigEditBody({ schema, screen, translate, }) {
|
|
|
97
98
|
// Detail load: config:query:values returns ALL keys of the tenant.
|
|
98
99
|
// We map them via screen.configKeys from short to qualified name onto our
|
|
99
100
|
// form field values.
|
|
100
|
-
const valuesQuery = useQuery("config:query:values", {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
const
|
|
101
|
+
const valuesQuery = useQuery("config:query:values", {
|
|
102
|
+
scope: declaredScreen.scope,
|
|
103
|
+
});
|
|
104
|
+
const cascadeQuery = useQuery("config:query:cascade", {
|
|
105
|
+
scope: declaredScreen.scope,
|
|
106
|
+
});
|
|
107
|
+
const secretsQuery = useQuery("secrets:query:list", {}, { enabled: declaredScreen.secretKeys !== undefined });
|
|
108
|
+
// The form screen's access is the union of config and secrets access, so a viewer may write
|
|
109
|
+
// the config keys without being allowed to list secrets: that must not take the form down.
|
|
110
|
+
const secretsDenied = secretsQuery.error?.code === "access_denied";
|
|
111
|
+
const screen = useMemo(() => (secretsDenied ? configEditWithoutSecretFields(declaredScreen) : declaredScreen), [secretsDenied, declaredScreen]);
|
|
112
|
+
const secretKeys = secretsDenied ? undefined : declaredScreen.secretKeys;
|
|
104
113
|
const synthEntity = useMemo(() => synthesizeConfigEditEntity(screen.fields), [screen.fields]);
|
|
105
114
|
const synthScreen = useMemo(() => synthesizeConfigEditScreen(screen), [screen]);
|
|
106
115
|
const boundsSchema = useMemo(() => buildConfigEditSchema(screen.fields), [screen.fields]);
|
|
@@ -300,7 +309,11 @@ export function ConfigEditBody({ schema, screen, translate, }) {
|
|
|
300
309
|
]);
|
|
301
310
|
if (valuesQuery.loading && valuesQuery.data === null)
|
|
302
311
|
return _jsx(QueryLoadingBanner, {});
|
|
303
|
-
const failedQuery = valuesQuery.error
|
|
312
|
+
const failedQuery = valuesQuery.error
|
|
313
|
+
? valuesQuery
|
|
314
|
+
: secretsQuery.error && !secretsDenied
|
|
315
|
+
? secretsQuery
|
|
316
|
+
: null;
|
|
304
317
|
if (failedQuery?.error) {
|
|
305
318
|
return (_jsx(QueryErrorBanner, { error: failedQuery.error, translate: effectiveTranslate, onRetry: failedQuery.refetch }));
|
|
306
319
|
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { isFieldsEditSection, normalizeEditField } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
// A merged provider form carries the secret-backed fields of a secrets feature the viewer may
|
|
3
|
+
// not read. Without the secrets list those fields cannot show or save a state, so the form
|
|
4
|
+
// drops them and stays usable for the config keys.
|
|
5
|
+
export function configEditWithoutSecretFields(screen) {
|
|
6
|
+
const secretFieldNames = new Set(Object.keys(screen.secretKeys ?? {}));
|
|
7
|
+
if (secretFieldNames.size === 0)
|
|
8
|
+
return screen;
|
|
9
|
+
const keepSpec = (spec) => !secretFieldNames.has(normalizeEditField(spec).field);
|
|
10
|
+
const fields = Object.fromEntries(Object.entries(screen.fields).filter(([name]) => !secretFieldNames.has(name)));
|
|
11
|
+
const sections = screen.layout.sections.flatMap((section) => {
|
|
12
|
+
if (!isFieldsEditSection(section))
|
|
13
|
+
return [section];
|
|
14
|
+
if ("groups" in section) {
|
|
15
|
+
const groups = section.groups
|
|
16
|
+
.map((group) => ({ ...group, fields: group.fields.filter(keepSpec) }))
|
|
17
|
+
.filter((group) => group.fields.length > 0);
|
|
18
|
+
return groups.length === 0 ? [] : [{ ...section, groups }];
|
|
19
|
+
}
|
|
20
|
+
const kept = section.fields.filter(keepSpec);
|
|
21
|
+
return kept.length === 0 ? [] : [{ ...section, fields: kept }];
|
|
22
|
+
});
|
|
23
|
+
return {
|
|
24
|
+
...screen,
|
|
25
|
+
fields,
|
|
26
|
+
layout: { ...screen.layout, sections },
|
|
27
|
+
};
|
|
28
|
+
}
|
|
@@ -2,6 +2,17 @@ import type { FormValues } from "../headless/index.ts";
|
|
|
2
2
|
export declare function versionOf(record: Readonly<Record<string, unknown>>): number;
|
|
3
3
|
export declare function buildInitialValues(fields: Readonly<Record<string, unknown>>, defaultCurrency?: string, moneyCurrencyOverrides?: Readonly<Record<string, string>>): FormValues;
|
|
4
4
|
export declare function tenantCurrencyMoneyFieldNames(fields: Readonly<Record<string, unknown>>): readonly string[];
|
|
5
|
+
export type TenantCurrencyResolution = {
|
|
6
|
+
readonly status: "not-needed";
|
|
7
|
+
} | {
|
|
8
|
+
readonly status: "loading";
|
|
9
|
+
} | {
|
|
10
|
+
readonly status: "unresolved";
|
|
11
|
+
} | {
|
|
12
|
+
readonly status: "ready";
|
|
13
|
+
readonly currency: string;
|
|
14
|
+
};
|
|
15
|
+
export declare function useTenantCurrency(needed: boolean, fallback: string | undefined): TenantCurrencyResolution;
|
|
5
16
|
export declare function literalCurrencyOverrides(fields: Readonly<Record<string, unknown>>): Readonly<Record<string, string>>;
|
|
6
17
|
export declare function useMoneyCurrencyOverrides(fields: Readonly<Record<string, unknown>>, tenantFieldNames: readonly string[], fallback: string | undefined): {
|
|
7
18
|
readonly overrides: Readonly<Record<string, string>> | undefined;
|
|
@@ -71,8 +71,8 @@ export function tenantCurrencyMoneyFieldNames(fields) {
|
|
|
71
71
|
// back to "EUR" while the query is genuinely still in flight — a
|
|
72
72
|
// screen with no tenant-declared money field, or a query that errored or
|
|
73
73
|
// returned no value for the key, resolves immediately instead of blocking.
|
|
74
|
-
function resolveTenantCurrency(
|
|
75
|
-
if (
|
|
74
|
+
function resolveTenantCurrency(needed, query, fallback) {
|
|
75
|
+
if (!needed)
|
|
76
76
|
return { status: "not-needed" };
|
|
77
77
|
if (query.error) {
|
|
78
78
|
return fallback === undefined
|
|
@@ -88,6 +88,12 @@ function resolveTenantCurrency(fieldNames, query, fallback) {
|
|
|
88
88
|
? { status: "unresolved" }
|
|
89
89
|
: { status: "ready", currency: fallback };
|
|
90
90
|
}
|
|
91
|
+
// `needed = false` skips the config query entirely; an errored query or a missing value
|
|
92
|
+
// resolves to `fallback` (or `unresolved` without one).
|
|
93
|
+
export function useTenantCurrency(needed, fallback) {
|
|
94
|
+
const query = useQuery("config:query:values", { keys: [TENANT_CURRENCY_CONFIG_KEY] }, { enabled: needed });
|
|
95
|
+
return resolveTenantCurrency(needed, query, fallback);
|
|
96
|
+
}
|
|
91
97
|
// A money field naming a fixed ISO code (`currency: { kind: "literal", code }`)
|
|
92
98
|
// resolves with no query at all. Read structurally, same idiom as
|
|
93
99
|
// tenantCurrencyMoneyFieldNames above.
|
|
@@ -115,8 +121,7 @@ export function useMoneyCurrencyOverrides(fields, tenantFieldNames,
|
|
|
115
121
|
// `undefined` = no safe last resort (entity-less forms): an unresolvable
|
|
116
122
|
// tenant currency is reported via `unresolved` instead of guessing a code.
|
|
117
123
|
fallback) {
|
|
118
|
-
const
|
|
119
|
-
const resolution = resolveTenantCurrency(tenantFieldNames, query, fallback);
|
|
124
|
+
const resolution = useTenantCurrency(tenantFieldNames.length > 0, fallback);
|
|
120
125
|
const resolvedTenantCurrency = resolution.status === "ready" ? resolution.currency : undefined;
|
|
121
126
|
const overrides = useMemo(() => {
|
|
122
127
|
const out = { ...literalCurrencyOverrides(fields) };
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import type { Translate } from "../headless/index.ts";
|
|
2
|
+
import type { ResolvedFacetSpec } from "./list-facets.ts";
|
|
3
|
+
export declare function resolveEntityFacetSpecs(fields: Readonly<Record<string, unknown>>, featureName: string, entityName: string, translate: Translate): ResolvedFacetSpec[];
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { parseRefTarget } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import { propertyOf } from "@cosmicdrift/kumiko-types/type-guards";
|
|
3
|
+
import { fieldLabelKey, fieldOptionLabelKey } from "../headless/index.js";
|
|
4
|
+
// entityList adapter — one DataTableFacet per filterable select/boolean
|
|
5
|
+
// entity field, labels via the standard field/option i18n convention.
|
|
6
|
+
export function resolveEntityFacetSpecs(fields, featureName, entityName, translate) {
|
|
7
|
+
const out = [];
|
|
8
|
+
for (const [field, rawDef] of Object.entries(fields)) {
|
|
9
|
+
// entity.fields is weakly typed at the renderer layer (deserialized from the schema).
|
|
10
|
+
const def = {
|
|
11
|
+
type: propertyOf(rawDef, "type"),
|
|
12
|
+
filterable: propertyOf(rawDef, "filterable"),
|
|
13
|
+
options: propertyOf(rawDef, "options"),
|
|
14
|
+
entity: propertyOf(rawDef, "entity"),
|
|
15
|
+
labelField: propertyOf(rawDef, "labelField"),
|
|
16
|
+
};
|
|
17
|
+
if (def.filterable !== true)
|
|
18
|
+
continue;
|
|
19
|
+
const label = translate(fieldLabelKey(featureName, entityName, field));
|
|
20
|
+
if (def.type === "select" && Array.isArray(def.options)) {
|
|
21
|
+
const optionValues = def.options.filter((value) => typeof value === "string");
|
|
22
|
+
out.push({
|
|
23
|
+
field,
|
|
24
|
+
type: "select",
|
|
25
|
+
label,
|
|
26
|
+
options: optionValues.map((value) => ({
|
|
27
|
+
value,
|
|
28
|
+
label: translate(fieldOptionLabelKey(featureName, entityName, field, value)),
|
|
29
|
+
})),
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
else if (def.type === "boolean") {
|
|
33
|
+
out.push({
|
|
34
|
+
field,
|
|
35
|
+
type: "boolean",
|
|
36
|
+
label,
|
|
37
|
+
options: [
|
|
38
|
+
{
|
|
39
|
+
value: "true",
|
|
40
|
+
label: translate(fieldOptionLabelKey(featureName, entityName, field, "true")),
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
value: "false",
|
|
44
|
+
label: translate(fieldOptionLabelKey(featureName, entityName, field, "false")),
|
|
45
|
+
},
|
|
46
|
+
],
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
else if (def.type === "reference" && typeof def.entity === "string") {
|
|
50
|
+
const target = parseRefTarget(def.entity, featureName);
|
|
51
|
+
out.push({
|
|
52
|
+
field,
|
|
53
|
+
type: "reference",
|
|
54
|
+
label,
|
|
55
|
+
options: [],
|
|
56
|
+
reference: {
|
|
57
|
+
refEntity: target.entityName,
|
|
58
|
+
refFeature: target.featureName,
|
|
59
|
+
labelField: typeof def.labelField === "string" ? def.labelField : "id",
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return out;
|
|
65
|
+
}
|