@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.
Files changed (210) hide show
  1. package/dist/app/action-form-body.js +10 -2
  2. package/dist/app/action-form-support.d.ts +1 -0
  3. package/dist/app/action-form-support.js +1 -1
  4. package/dist/app/base-path-context.d.ts +9 -0
  5. package/dist/app/base-path-context.js +13 -0
  6. package/dist/app/base-path.d.ts +3 -0
  7. package/dist/app/base-path.js +27 -0
  8. package/dist/app/config-edit-body.d.ts +1 -1
  9. package/dist/app/config-edit-body.js +19 -6
  10. package/dist/app/config-edit-without-secret-fields.d.ts +2 -0
  11. package/dist/app/config-edit-without-secret-fields.js +28 -0
  12. package/dist/app/entity-edit-prefill.d.ts +11 -0
  13. package/dist/app/entity-edit-prefill.js +9 -4
  14. package/dist/app/entity-list-facet-specs.d.ts +3 -0
  15. package/dist/app/entity-list-facet-specs.js +65 -0
  16. package/dist/app/entity-list-screen.js +74 -206
  17. package/dist/app/extension-sections.d.ts +5 -3
  18. package/dist/app/form-schema.js +1 -1
  19. package/dist/app/header-actions-bar.js +1 -1
  20. package/dist/app/kumiko-screen.js +6 -14
  21. package/dist/app/list-bulk-actions.d.ts +18 -0
  22. package/dist/app/list-bulk-actions.js +54 -0
  23. package/dist/app/locked-screen-header.d.ts +9 -0
  24. package/dist/app/locked-screen-header.js +33 -0
  25. package/dist/app/projection-list-body.js +51 -46
  26. package/dist/app/row-actions.d.ts +3 -0
  27. package/dist/app/row-actions.js +10 -5
  28. package/dist/app/screen-shared.js +9 -2
  29. package/dist/app/use-entity-range-facets.d.ts +24 -0
  30. package/dist/app/use-entity-range-facets.js +64 -0
  31. package/dist/app/use-projection-date-range.d.ts +12 -0
  32. package/dist/app/use-projection-date-range.js +21 -0
  33. package/dist/app/use-screen-list-rows.d.ts +37 -0
  34. package/dist/app/use-screen-list-rows.js +104 -0
  35. package/dist/app/use-screen-list-table.d.ts +24 -0
  36. package/dist/app/use-screen-list-table.js +46 -0
  37. package/dist/components/edit-mounts.d.ts +2 -2
  38. package/dist/components/embedded-list-field.js +1 -1
  39. package/dist/components/list-group-page-bridges.d.ts +3 -0
  40. package/dist/components/list-group-page-bridges.js +31 -0
  41. package/dist/components/list-state-store.d.ts +16 -0
  42. package/dist/components/list-state-store.js +31 -0
  43. package/dist/components/related-list-section.js +51 -90
  44. package/dist/components/render-edit-action-button.d.ts +1 -3
  45. package/dist/components/render-edit-action-button.js +2 -2
  46. package/dist/components/render-edit.js +11 -8
  47. package/dist/components/render-field.js +11 -7
  48. package/dist/components/render-list-toolbar-action.d.ts +27 -0
  49. package/dist/components/render-list-toolbar-action.js +40 -0
  50. package/dist/components/render-list.d.ts +13 -29
  51. package/dist/components/render-list.js +72 -67
  52. package/dist/current-user.d.ts +2 -0
  53. package/dist/data-table-aggregate-types.d.ts +47 -0
  54. package/dist/data-table-aggregate-types.js +1 -0
  55. package/dist/data-table-bulk-types.d.ts +27 -0
  56. package/dist/data-table-bulk-types.js +1 -0
  57. package/dist/data-table-facet-types.d.ts +36 -0
  58. package/dist/data-table-facet-types.js +1 -0
  59. package/dist/data-table-view-types.d.ts +73 -0
  60. package/dist/data-table-view-types.js +1 -0
  61. package/dist/flow-diagram/render.js +9 -1
  62. package/dist/headless/form/form-controller.js +8 -0
  63. package/dist/headless/form/types.d.ts +1 -0
  64. package/dist/headless/format/index.js +89 -64
  65. package/dist/headless/view-model/edit.js +5 -1
  66. package/dist/headless/view-model/list.js +184 -149
  67. package/dist/headless/view-model/types.d.ts +7 -2
  68. package/dist/hooks/list-column-layout.d.ts +19 -0
  69. package/dist/hooks/list-column-layout.js +36 -0
  70. package/dist/hooks/list-group-pages.d.ts +26 -0
  71. package/dist/hooks/list-group-pages.js +37 -0
  72. package/dist/hooks/list-reset.d.ts +8 -0
  73. package/dist/hooks/list-reset.js +15 -0
  74. package/dist/hooks/list-server-groups.d.ts +39 -0
  75. package/dist/hooks/list-server-groups.js +84 -0
  76. package/dist/hooks/list-state-schema.d.ts +28 -0
  77. package/dist/hooks/list-state-schema.js +76 -0
  78. package/dist/hooks/live-refetch-scheduler.js +4 -0
  79. package/dist/hooks/use-bulk-run.d.ts +41 -0
  80. package/dist/hooks/use-bulk-run.js +65 -0
  81. package/dist/hooks/use-group-load-state.d.ts +11 -0
  82. package/dist/hooks/use-group-load-state.js +31 -0
  83. package/dist/hooks/use-list-aggregates.d.ts +38 -0
  84. package/dist/hooks/use-list-aggregates.js +107 -0
  85. package/dist/hooks/use-list-table.d.ts +38 -0
  86. package/dist/hooks/use-list-table.js +91 -0
  87. package/dist/hooks/use-list-view-state.d.ts +43 -0
  88. package/dist/hooks/use-list-view-state.js +161 -0
  89. package/dist/hooks/use-reference-lookup.d.ts +9 -1
  90. package/dist/hooks/use-reference-lookup.js +17 -3
  91. package/dist/hooks/use-related-list-rows.d.ts +27 -0
  92. package/dist/hooks/use-related-list-rows.js +81 -0
  93. package/dist/hooks/use-related-list-state.d.ts +15 -0
  94. package/dist/hooks/use-related-list-state.js +31 -0
  95. package/dist/hooks/use-remembered-list-params.d.ts +13 -0
  96. package/dist/hooks/use-remembered-list-params.js +53 -0
  97. package/dist/hooks/use-remembered-local-list-state.d.ts +26 -0
  98. package/dist/hooks/use-remembered-local-list-state.js +63 -0
  99. package/dist/i18n-defaults.js +59 -2
  100. package/dist/index.d.ts +4 -1
  101. package/dist/index.js +3 -0
  102. package/dist/primitives.d.ts +30 -47
  103. package/dist/web/app/create-app.d.ts +5 -0
  104. package/dist/web/app/create-app.js +15 -7
  105. package/dist/web/app/dashboard-body.js +17 -1
  106. package/dist/web/app/dashboard-panels.js +12 -2
  107. package/dist/web/app/history-leave-guard.d.ts +6 -0
  108. package/dist/web/app/history-leave-guard.js +115 -0
  109. package/dist/web/app/list-state-store-boot.d.ts +6 -0
  110. package/dist/web/app/list-state-store-boot.js +15 -0
  111. package/dist/web/app/nav.js +7 -54
  112. package/dist/web/app/session-list-state-store.d.ts +15 -0
  113. package/dist/web/app/session-list-state-store.js +117 -0
  114. package/dist/web/index.d.ts +2 -1
  115. package/dist/web/index.js +2 -1
  116. package/dist/web/layout/shell-header.js +2 -1
  117. package/dist/web/layout/uniform-bar-controls.d.ts +1 -0
  118. package/dist/web/layout/uniform-bar-controls.js +4 -0
  119. package/dist/web/lib/export-xlsx-request.d.ts +11 -0
  120. package/dist/web/lib/export-xlsx-request.js +46 -0
  121. package/dist/web/lib/file-ref-download.js +4 -1
  122. package/dist/web/primitives/action-menu.js +1 -1
  123. package/dist/web/primitives/code-input.d.ts +6 -0
  124. package/dist/web/primitives/code-input.js +31 -0
  125. package/dist/web/primitives/data-table/actions-column.d.ts +16 -0
  126. package/dist/web/primitives/data-table/actions-column.js +34 -0
  127. package/dist/web/primitives/data-table/bulk-selection-bar.d.ts +20 -0
  128. package/dist/web/primitives/data-table/bulk-selection-bar.js +37 -0
  129. package/dist/web/primitives/data-table/card-sort-select.d.ts +14 -0
  130. package/dist/web/primitives/data-table/card-sort-select.js +40 -0
  131. package/dist/web/primitives/data-table/column-layout-mode.d.ts +5 -0
  132. package/dist/web/primitives/data-table/column-layout-mode.js +10 -0
  133. package/dist/web/primitives/data-table/column-picker-section.d.ts +7 -0
  134. package/dist/web/primitives/data-table/column-picker-section.js +35 -0
  135. package/dist/web/primitives/data-table/column-resize-handle.d.ts +14 -0
  136. package/dist/web/primitives/data-table/column-resize-handle.js +46 -0
  137. package/dist/web/primitives/data-table/density.d.ts +14 -0
  138. package/dist/web/primitives/data-table/density.js +20 -0
  139. package/dist/web/primitives/data-table/export-button.d.ts +5 -0
  140. package/dist/web/primitives/data-table/export-button.js +37 -0
  141. package/dist/web/primitives/data-table/facet-cluster.d.ts +8 -0
  142. package/dist/web/primitives/data-table/facet-cluster.js +37 -0
  143. package/dist/web/primitives/data-table/group-band.d.ts +28 -0
  144. package/dist/web/primitives/data-table/group-band.js +53 -0
  145. package/dist/web/primitives/data-table/measure-value.d.ts +10 -0
  146. package/dist/web/primitives/data-table/measure-value.js +28 -0
  147. package/dist/web/primitives/data-table/pin-section.d.ts +8 -0
  148. package/dist/web/primitives/data-table/pin-section.js +20 -0
  149. package/dist/web/primitives/data-table/reset-all-button.d.ts +4 -0
  150. package/dist/web/primitives/data-table/reset-all-button.js +8 -0
  151. package/dist/web/primitives/data-table/server-group-header.d.ts +11 -0
  152. package/dist/web/primitives/data-table/server-group-header.js +51 -0
  153. package/dist/web/primitives/data-table/sortable-header.d.ts +17 -0
  154. package/dist/web/primitives/data-table/sortable-header.js +41 -0
  155. package/dist/web/primitives/data-table/table-chrome.d.ts +13 -0
  156. package/dist/web/primitives/data-table/table-chrome.js +30 -0
  157. package/dist/web/primitives/data-table/table-footer.d.ts +7 -0
  158. package/dist/web/primitives/data-table/table-footer.js +20 -0
  159. package/dist/web/primitives/data-table/toolbar-end-cluster.d.ts +10 -0
  160. package/dist/web/primitives/data-table/toolbar-end-cluster.js +13 -0
  161. package/dist/web/primitives/data-table/totals-footer.d.ts +24 -0
  162. package/dist/web/primitives/data-table/totals-footer.js +48 -0
  163. package/dist/web/primitives/data-table/use-bulk-selection.d.ts +21 -0
  164. package/dist/web/primitives/data-table/use-bulk-selection.js +66 -0
  165. package/dist/web/primitives/data-table/use-column-pins.d.ts +32 -0
  166. package/dist/web/primitives/data-table/use-column-pins.js +117 -0
  167. package/dist/web/primitives/data-table/use-column-sizing.d.ts +22 -0
  168. package/dist/web/primitives/data-table/use-column-sizing.js +44 -0
  169. package/dist/web/primitives/data-table/use-measured-column-widths.d.ts +15 -0
  170. package/dist/web/primitives/data-table/use-measured-column-widths.js +83 -0
  171. package/dist/web/primitives/data-table/use-row-keyboard-navigation.d.ts +22 -0
  172. package/dist/web/primitives/data-table/use-row-keyboard-navigation.js +65 -0
  173. package/dist/web/primitives/data-table/use-row-selection.d.ts +14 -0
  174. package/dist/web/primitives/data-table/use-row-selection.js +48 -0
  175. package/dist/web/primitives/data-table/use-view-columns.d.ts +6 -0
  176. package/dist/web/primitives/data-table/use-view-columns.js +17 -0
  177. package/dist/web/primitives/data-table/view-menu.d.ts +8 -0
  178. package/dist/web/primitives/data-table/view-menu.js +45 -0
  179. package/dist/web/primitives/data-table-cell.d.ts +0 -22
  180. package/dist/web/primitives/data-table-cell.js +1 -58
  181. package/dist/web/primitives/data-table.d.ts +1 -1
  182. package/dist/web/primitives/data-table.js +125 -146
  183. package/dist/web/primitives/facet-filter.js +1 -1
  184. package/dist/web/primitives/file-thumbnail.d.ts +3 -0
  185. package/dist/web/primitives/file-thumbnail.js +5 -0
  186. package/dist/web/primitives/file-value-list.d.ts +1 -1
  187. package/dist/web/primitives/file-value-list.js +5 -5
  188. package/dist/web/primitives/input.js +6 -1
  189. package/dist/web/primitives/layout-elements.js +6 -4
  190. package/dist/web/primitives/number-range-facet.d.ts +7 -0
  191. package/dist/web/primitives/number-range-facet.js +33 -0
  192. package/dist/web/primitives/page-header.js +3 -1
  193. package/dist/web/primitives/row-actions.js +3 -3
  194. package/dist/web/primitives/use-focus-on-mount.d.ts +2 -0
  195. package/dist/web/primitives/use-focus-on-mount.js +9 -0
  196. package/dist/web/widgets/bounded-load-cache.d.ts +5 -1
  197. package/dist/web/widgets/bounded-load-cache.js +49 -21
  198. package/dist/web/widgets/choice-cards.d.ts +17 -0
  199. package/dist/web/widgets/choice-cards.js +12 -0
  200. package/dist/web/widgets/collapsible-section.js +1 -1
  201. package/dist/web/widgets/detail-list.d.ts +5 -1
  202. package/dist/web/widgets/detail-list.js +5 -1
  203. package/dist/web/widgets/index.d.ts +1 -0
  204. package/dist/web/widgets/index.js +1 -0
  205. package/dist/web/widgets/pdf-preview.js +10 -5
  206. package/dist/web/widgets/plan-card.d.ts +2 -0
  207. package/dist/web/widgets/plan-card.js +1 -1
  208. package/dist/web/widgets/upload-zone.js +13 -5
  209. package/package.json +5 -5
  210. 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
- setFormEpoch((epoch) => epoch + 1);
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,3 @@
1
+ export declare function normalizeBasePath(raw: string | undefined): string;
2
+ export declare function stripBasePath(path: string, basePath: string): string | undefined;
3
+ export declare function prependBasePath(path: string, basePath: string): string;
@@ -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
- const cascadeQuery = useQuery("config:query:cascade", {});
102
- const secretKeys = screen.secretKeys;
103
- const secretsQuery = useQuery("secrets:query:list", {}, { enabled: secretKeys !== undefined });
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 ? valuesQuery : secretsQuery.error ? secretsQuery : null;
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,2 @@
1
+ import type { ConfigEditScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
2
+ export declare function configEditWithoutSecretFields(screen: ConfigEditScreenDefinition): ConfigEditScreenDefinition;
@@ -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(fieldNames, query, fallback) {
75
- if (fieldNames.length === 0)
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 query = useQuery("config:query:values", { keys: [TENANT_CURRENCY_CONFIG_KEY] }, { enabled: tenantFieldNames.length > 0 });
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
+ }