@cosmicdrift/kumiko-renderer 0.385.0 → 0.387.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/LICENSE +21 -57
- package/dist/app/action-form-body.d.ts +2 -2
- package/dist/app/action-form-body.js +2 -2
- package/dist/app/action-form-shim.d.ts +8 -8
- package/dist/app/action-form-shim.js +20 -22
- package/dist/app/app-features-context.js +4 -4
- package/dist/app/column-renderers.d.ts +7 -9
- package/dist/app/column-renderers.js +15 -18
- package/dist/app/config-edit-body.js +1 -1
- package/dist/app/config-edit-shim.d.ts +7 -8
- package/dist/app/config-edit-shim.js +18 -20
- package/dist/app/custom-screens.d.ts +3 -3
- package/dist/app/custom-screens.js +8 -8
- package/dist/app/dashboard-body.d.ts +2 -2
- package/dist/app/dashboard-body.js +2 -2
- package/dist/app/entity-edit-prefill.js +9 -9
- package/dist/app/entity-edit-screen.js +4 -4
- package/dist/app/entity-list-screen.js +3 -3
- package/dist/app/extension-form-submit.d.ts +1 -1
- package/dist/app/extension-form-submit.js +14 -15
- package/dist/app/extension-sections.d.ts +24 -25
- package/dist/app/extension-sections.js +10 -10
- package/dist/app/feature-schema.js +11 -11
- package/dist/app/form-schema.js +1 -1
- package/dist/app/header-actions-bar.js +1 -1
- package/dist/app/kumiko-screen.js +8 -10
- package/dist/app/layout-fields.js +1 -1
- package/dist/app/list-facets.js +3 -3
- package/dist/app/nav.d.ts +10 -10
- package/dist/app/nav.js +13 -13
- package/dist/app/projection-detail-body.js +4 -4
- package/dist/app/projection-detail-shim.d.ts +6 -5
- package/dist/app/projection-detail-shim.js +23 -22
- package/dist/app/projection-list-body.js +10 -10
- package/dist/app/projection-list-shim.d.ts +5 -5
- package/dist/app/projection-list-shim.js +17 -18
- package/dist/app/qn.js +10 -12
- package/dist/app/qualify-screen-id.js +1 -1
- package/dist/app/screen-access.js +2 -2
- package/dist/app/screen-shared.js +1 -1
- package/dist/app/secret-mint-body.js +4 -4
- package/dist/app/write-failed-error.js +8 -7
- package/dist/components/embedded-list-field.js +4 -4
- package/dist/components/grid-cell-for-field.d.ts +1 -1
- package/dist/components/reference-create-dialog.js +3 -3
- package/dist/components/related-list-section.d.ts +1 -1
- package/dist/components/related-list-section.js +28 -5
- package/dist/components/render-edit-logic.js +1 -1
- package/dist/components/render-edit-types.d.ts +6 -6
- package/dist/components/render-edit.js +63 -65
- package/dist/components/render-field.d.ts +7 -7
- package/dist/components/render-field.js +57 -59
- package/dist/components/render-list.d.ts +43 -44
- package/dist/components/render-list.js +63 -67
- package/dist/components/use-wizard-form-draft.js +10 -10
- package/dist/context/dispatcher-context.js +5 -5
- package/dist/context/user-roles-context.js +2 -2
- package/dist/headless/apex/index.d.ts +1 -2
- package/dist/headless/apex/index.js +5 -6
- package/dist/headless/form/form-controller.js +6 -6
- package/dist/headless/form/zod-bridge.js +2 -2
- package/dist/headless/format/money.js +1 -1
- package/dist/headless/index.js +2 -2
- package/dist/headless/view-model/edit.js +2 -2
- package/dist/headless/view-model/list.js +3 -4
- package/dist/headless/view-model/types.d.ts +3 -3
- package/dist/hooks/reference-limits.js +11 -12
- package/dist/hooks/use-list-url-state.d.ts +13 -13
- package/dist/hooks/use-list-url-state.js +21 -22
- package/dist/hooks/use-query.js +3 -3
- package/dist/hooks/use-reference-lookup.js +16 -17
- package/dist/hooks/use-store.js +18 -22
- package/dist/hooks/use-stream-handler.js +4 -2
- package/dist/i18n-defaults.js +2 -8
- package/dist/i18n.d.ts +10 -10
- package/dist/i18n.js +25 -25
- package/dist/index.js +5 -4
- package/dist/live/dispatcher-live.js +2 -2
- package/dist/primitives.d.ts +225 -221
- package/dist/primitives.js +1 -1
- package/dist/sse/live-events.d.ts +5 -5
- package/dist/sse/live-events.js +17 -17
- package/dist/tokens.d.ts +8 -8
- package/dist/tokens.js +16 -17
- package/dist/web/app/browser-locale.d.ts +9 -9
- package/dist/web/app/browser-locale.js +19 -19
- package/dist/web/app/client-plugin.d.ts +44 -52
- package/dist/web/app/client-plugin.js +4 -5
- package/dist/web/app/create-app.d.ts +28 -31
- package/dist/web/app/create-app.js +21 -22
- package/dist/web/app/document-lang-sync.js +1 -1
- package/dist/web/app/draft-storage.js +1 -1
- package/dist/web/app/nav-providers-context.d.ts +3 -3
- package/dist/web/app/nav-providers-context.js +3 -3
- package/dist/web/app/nav.d.ts +18 -17
- package/dist/web/app/nav.js +66 -64
- package/dist/web/app/rich-content-editor.js +1 -1
- package/dist/web/components/config-cascade.js +17 -18
- package/dist/web/components/config-source-badge.js +4 -4
- package/dist/web/layout/app-layout.d.ts +9 -9
- package/dist/web/layout/app-layout.js +2 -2
- package/dist/web/layout/avatar.d.ts +4 -3
- package/dist/web/layout/avatar.js +15 -17
- package/dist/web/layout/default-app-shell.d.ts +22 -23
- package/dist/web/layout/editor-panel.js +3 -3
- package/dist/web/layout/nav-tree.js +86 -84
- package/dist/web/layout/profile-menu.d.ts +1 -1
- package/dist/web/layout/sidebar-brand.d.ts +7 -7
- package/dist/web/layout/sidebar-user.d.ts +1 -1
- package/dist/web/layout/sidebar-user.js +4 -4
- package/dist/web/layout/sidebar.d.ts +8 -8
- package/dist/web/layout/sidebar.js +7 -7
- package/dist/web/layout/target-resolver-stub.d.ts +5 -5
- package/dist/web/layout/target-resolver-stub.js +20 -20
- package/dist/web/layout/target-url.d.ts +7 -7
- package/dist/web/layout/target-url.js +22 -22
- package/dist/web/layout/theme-toggle.js +4 -5
- package/dist/web/layout/workspace-shell.d.ts +21 -21
- package/dist/web/layout/workspace-shell.js +29 -30
- package/dist/web/lib/cn.d.ts +4 -4
- package/dist/web/lib/cn.js +4 -4
- package/dist/web/primitives/action-menu.d.ts +8 -8
- package/dist/web/primitives/action-menu.js +14 -13
- package/dist/web/primitives/calendar-popover.d.ts +1 -1
- package/dist/web/primitives/calendar-popover.js +10 -10
- package/dist/web/primitives/card-styles.js +1 -1
- package/dist/web/primitives/card.js +2 -2
- package/dist/web/primitives/combobox.d.ts +12 -12
- package/dist/web/primitives/combobox.js +34 -35
- package/dist/web/primitives/data-table-cell.js +2 -2
- package/dist/web/primitives/data-table.js +2 -3
- package/dist/web/primitives/date-field.js +2 -2
- package/dist/web/primitives/date-input.js +4 -4
- package/dist/web/primitives/default-form.js +2 -2
- package/dist/web/primitives/dropdown-menu.js +14 -15
- package/dist/web/primitives/embedded-list-input.js +3 -3
- package/dist/web/primitives/facet-filter.js +1 -1
- package/dist/web/primitives/field.js +9 -8
- package/dist/web/primitives/form.d.ts +1 -1
- package/dist/web/primitives/form.js +6 -6
- package/dist/web/primitives/input.js +2 -2
- package/dist/web/primitives/layout-elements.js +1 -1
- package/dist/web/primitives/layout.js +2 -2
- package/dist/web/primitives/located-timestamp-input.js +4 -4
- package/dist/web/primitives/money-input.js +13 -17
- package/dist/web/primitives/pager.js +1 -1
- package/dist/web/primitives/row-actions.js +3 -4
- package/dist/web/primitives/secret-reveal.js +1 -1
- package/dist/web/primitives/timestamp-input.d.ts +9 -9
- package/dist/web/primitives/timestamp-input.js +21 -21
- package/dist/web/primitives/toast.js +12 -13
- package/dist/web/sse/live-events.d.ts +6 -6
- package/dist/web/sse/live-events.js +3 -3
- package/dist/web/tokens.d.ts +11 -12
- package/dist/web/tokens.js +35 -37
- package/dist/web/version/update-checker.js +14 -13
- package/dist/web/widgets/ai-text-field.js +1 -1
- package/dist/web/widgets/charts.d.ts +10 -10
- package/dist/web/widgets/charts.js +8 -8
- package/dist/web/widgets/collapsible-section.d.ts +3 -3
- package/dist/web/widgets/collapsible-section.js +6 -6
- package/dist/web/widgets/detail-list.d.ts +1 -3
- package/dist/web/widgets/detail-list.js +1 -3
- package/dist/web/widgets/feed-list.d.ts +2 -2
- package/dist/web/widgets/feed-list.js +2 -2
- package/dist/web/widgets/form-fields.d.ts +14 -14
- package/dist/web/widgets/form-fields.js +14 -13
- package/dist/web/widgets/index.js +4 -4
- package/dist/web/widgets/infinity-list.js +3 -3
- package/dist/web/widgets/mode-switch.d.ts +2 -2
- package/dist/web/widgets/mode-switch.js +2 -2
- package/dist/web/widgets/progress-list.d.ts +1 -2
- package/dist/web/widgets/progress-list.js +1 -2
- package/dist/web/widgets/query-table.d.ts +6 -6
- package/dist/web/widgets/result-panel.d.ts +16 -16
- package/dist/web/widgets/result-panel.js +10 -10
- package/dist/web/widgets/section-card.d.ts +3 -3
- package/dist/web/widgets/section-card.js +3 -3
- package/dist/web/widgets/stat.d.ts +4 -4
- package/dist/web/widgets/stat.js +6 -6
- package/dist/web/widgets/states.d.ts +4 -4
- package/dist/web/widgets/states.js +4 -4
- package/dist/web/widgets/use-draft.d.ts +7 -7
- package/dist/web/widgets/use-draft.js +7 -7
- package/package.json +6 -6
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
// @runtime client
|
|
2
|
-
// Extension-Form-Submit-Registry —
|
|
3
|
-
// Custom-Fields)
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
// mit der entityId.
|
|
2
|
+
// Extension-Form-Submit-Registry — lets an extension section (e.g.
|
|
3
|
+
// Custom-Fields) write during submission of the surrounding entityEdit form
|
|
4
|
+
// instead of maintaining its own Save button ("composed form, one save").
|
|
5
|
+
// The section reports (a) a submit handler and (b) its dirty state to the form;
|
|
6
|
+
// the form enables its Save button even when only one section is dirty and,
|
|
7
|
+
// after the entity write, calls all section handlers with the entityId.
|
|
9
8
|
//
|
|
10
|
-
//
|
|
11
|
-
// Save
|
|
9
|
+
// Without a surrounding form (Context === null), the section falls back to its
|
|
10
|
+
// own Save button (Standalone-Backward-Compat).
|
|
12
11
|
import { createContext, useContext, useEffect, useId, useMemo, useRef } from "react";
|
|
13
12
|
const ExtensionFormRegistryContext = createContext(null);
|
|
14
13
|
export const ExtensionFormRegistryProvider = ExtensionFormRegistryContext.Provider;
|
|
@@ -48,15 +47,15 @@ export function useExtensionFormHost(onStatusChange) {
|
|
|
48
47
|
}, []);
|
|
49
48
|
return { registry, runAll };
|
|
50
49
|
}
|
|
51
|
-
// Section
|
|
52
|
-
//
|
|
53
|
-
// Save
|
|
50
|
+
// Section side: reports the handler and dirty state to the surrounding form.
|
|
51
|
+
// Returns true when a composed form is present, so the section hides its own
|
|
52
|
+
// Save button; false means standalone, so its own Save button remains.
|
|
54
53
|
export function useExtensionFormSubmit(opts) {
|
|
55
54
|
const registry = useContext(ExtensionFormRegistryContext);
|
|
56
55
|
const key = useId();
|
|
57
|
-
//
|
|
58
|
-
//
|
|
59
|
-
//
|
|
56
|
+
// The ref prevents re-registration whenever the handler identity changes
|
|
57
|
+
// (only dirty triggers it); the handler always reads the latest closure state
|
|
58
|
+
// (pending) when invoked.
|
|
60
59
|
const handlerRef = useRef(opts.onSubmit);
|
|
61
60
|
handlerRef.current = opts.onSubmit;
|
|
62
61
|
useEffect(() => {
|
|
@@ -1,31 +1,30 @@
|
|
|
1
1
|
import type { PlatformComponent } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
2
|
import { type ComponentType, type ReactNode } from "react";
|
|
3
|
-
/**
|
|
4
|
-
* react
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
/** Extracts the `__component` name from a PlatformComponent. Reads the
|
|
4
|
+
* react and native branches (the app author registers both under the same
|
|
5
|
+
* name); returns the first string found. Pure narrowing via `in` — no cast
|
|
6
|
+
* or assertion. */
|
|
7
7
|
export declare function extensionSectionName(component: PlatformComponent): string | undefined;
|
|
8
8
|
export type ExtensionSectionProps = {
|
|
9
9
|
readonly entityName: string;
|
|
10
10
|
readonly entityId: string | null;
|
|
11
|
-
/**
|
|
12
|
-
* detail
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
/** Previously saved extension values for the entity (passed through from
|
|
12
|
+
* the loaded detail row). `undefined` in create mode or when the host
|
|
13
|
+
* screen provides no values. Allows the section to display existing
|
|
14
|
+
* values while editing instead of being write-only. */
|
|
15
15
|
readonly initialValues?: Readonly<Record<string, unknown>>;
|
|
16
|
-
/**
|
|
17
|
-
*
|
|
18
|
-
* (useListUrlState)
|
|
19
|
-
*
|
|
16
|
+
/** Set only in the list-header slot (`entityId` is null): the list's
|
|
17
|
+
* `screen.id`, so a header control can access the per-screen URL filter
|
|
18
|
+
* state (`useListUrlState`) — for example, a tag filter that narrows the
|
|
19
|
+
* list to a set of IDs. `undefined` in entityEdit sections. */
|
|
20
20
|
readonly screenId?: string;
|
|
21
|
-
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
21
|
+
/** Set only in a Dashboard-`custom` panel: the currently selected value
|
|
22
|
+
* of the screen filter (see DashboardFilterDefinition), merged as with any
|
|
23
|
+
* other panel query. `undefined` in all other mount locations. Dashboard
|
|
24
|
+
* panels have no entity — `entityName`/`entityId` contain the `screen.id`
|
|
25
|
+
* and `null`, respectively; see CustomPanelBody in dashboard-body.tsx. */
|
|
26
26
|
readonly filterParams?: Readonly<Record<string, unknown>>;
|
|
27
|
-
/** Current form values of the host RenderEdit (controlled mode
|
|
28
|
-
* issue #1887/#1888) — same snapshot reference as
|
|
27
|
+
/** Current form values of the host RenderEdit (controlled mode) — same snapshot reference as
|
|
29
28
|
* `RenderEditChangeState.values`. Lets the section read the host
|
|
30
29
|
* form's live state (e.g. for a review step). Only set in
|
|
31
30
|
* entityEdit sections; undefined in list-header and dashboard
|
|
@@ -73,10 +72,10 @@ export type ExtensionSectionsProviderProps = {
|
|
|
73
72
|
readonly value: ExtensionSectionsMap;
|
|
74
73
|
};
|
|
75
74
|
export declare function ExtensionSectionsProvider({ children, value, }: ExtensionSectionsProviderProps): ReactNode;
|
|
76
|
-
/**
|
|
77
|
-
* undefined
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
81
|
-
*
|
|
75
|
+
/** Looks up the component for an extension-section name. Returns
|
|
76
|
+
* `undefined` when no provider is mounted or the name is not registered in
|
|
77
|
+
* the map — the caller (RenderEdit) then displays its placeholder banner.
|
|
78
|
+
* `name` is optional so callers can invoke the hook unconditionally
|
|
79
|
+
* (Rules-of-Hooks) without supplying a stub key such as `""`, analogous to
|
|
80
|
+
* `useColumnRenderer`. */
|
|
82
81
|
export declare function useExtensionSectionComponent(name?: string): ExtensionSectionComponent | undefined;
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useContext } from "react";
|
|
3
|
-
/**
|
|
4
|
-
* react
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
/** Extracts the `__component` name from a PlatformComponent. Reads the
|
|
4
|
+
* react and native branches (the app author registers both under the same
|
|
5
|
+
* name); returns the first string found. Pure narrowing via `in` — no cast
|
|
6
|
+
* or assertion. */
|
|
7
7
|
export function extensionSectionName(component) {
|
|
8
8
|
for (const branch of [component.react, component.native]) {
|
|
9
9
|
if (branch !== null && typeof branch === "object" && "__component" in branch) {
|
|
@@ -18,12 +18,12 @@ const ExtensionSectionsContext = createContext(undefined);
|
|
|
18
18
|
export function ExtensionSectionsProvider({ children, value, }) {
|
|
19
19
|
return (_jsx(ExtensionSectionsContext.Provider, { value: value, children: children }));
|
|
20
20
|
}
|
|
21
|
-
/**
|
|
22
|
-
* undefined
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
21
|
+
/** Looks up the component for an extension-section name. Returns
|
|
22
|
+
* `undefined` when no provider is mounted or the name is not registered in
|
|
23
|
+
* the map — the caller (RenderEdit) then displays its placeholder banner.
|
|
24
|
+
* `name` is optional so callers can invoke the hook unconditionally
|
|
25
|
+
* (Rules-of-Hooks) without supplying a stub key such as `""`, analogous to
|
|
26
|
+
* `useColumnRenderer`. */
|
|
27
27
|
export function useExtensionSectionComponent(name) {
|
|
28
28
|
const map = useContext(ExtensionSectionsContext);
|
|
29
29
|
if (name === undefined || name === "")
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
// file that exports a schema can be imported from the browser bundle
|
|
5
5
|
// without dragging in Node-only framework internals.
|
|
6
6
|
//
|
|
7
|
-
// Types
|
|
8
|
-
// (buildAppSchema, dev-server)
|
|
9
|
-
//
|
|
10
|
-
// (toAppSchema, isAppSchema)
|
|
7
|
+
// Types live in `framework/ui-types/app-schema.ts` so the server
|
|
8
|
+
// (buildAppSchema, dev-server) can produce them without pulling in
|
|
9
|
+
// renderer as a dependency. Only re-exports and runtime helpers
|
|
10
|
+
// (toAppSchema, isAppSchema) needed by client code at runtime belong here.
|
|
11
11
|
//
|
|
12
12
|
// Typical layout on the feature author's side:
|
|
13
13
|
//
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
// r.screen(editScreen);
|
|
29
29
|
// ...
|
|
30
30
|
// });
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
31
|
+
// Normalizes FeatureSchema → AppSchema and is idempotent for AppSchema.
|
|
32
|
+
// Promotes a feature-local `workspaces` list (legacy) to the app level
|
|
33
|
+
// so all layouts can use the new form without requiring old clientSchemas
|
|
34
|
+
// to be migrated.
|
|
35
35
|
export function toAppSchema(input) {
|
|
36
36
|
if ("features" in input)
|
|
37
37
|
return input;
|
|
@@ -42,9 +42,9 @@ export function toAppSchema(input) {
|
|
|
42
42
|
...(workspaces !== undefined && { workspaces }),
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
-
//
|
|
46
|
-
// (
|
|
47
|
-
//
|
|
45
|
+
// Type guard for callers that need to distinguish the types at runtime
|
|
46
|
+
// (rare; toAppSchema is usually sufficient). Do not use `"features" in x`
|
|
47
|
+
// inline: TS then narrows to the join type instead of the actual distinction.
|
|
48
48
|
export function isAppSchema(input) {
|
|
49
49
|
return "features" in input;
|
|
50
50
|
}
|
package/dist/app/form-schema.js
CHANGED
|
@@ -26,7 +26,7 @@ function isRequiredValueMissing(spec, field, record, unchangedFrom) {
|
|
|
26
26
|
if (!isRequired)
|
|
27
27
|
return false;
|
|
28
28
|
// Embedded LIST fields get their own EmbeddedListField grid widget
|
|
29
|
-
//
|
|
29
|
+
// — they're fillable, so NO_WIDGET_FIELD_TYPES's "embedded"
|
|
30
30
|
// entry must not exempt them. A statically-`required: true` field
|
|
31
31
|
// with no bound widget is also caught at boot
|
|
32
32
|
// (validateNoWidgetRequiredField in boot-validator/screens.ts).
|
|
@@ -3,7 +3,7 @@ import { RenderEditActionButton } from "../components/render-edit-action-button.
|
|
|
3
3
|
import { useActionMenuItems } from "../components/use-action-menu-items.js";
|
|
4
4
|
import { useTranslation } from "../i18n.js";
|
|
5
5
|
import { compactHeaderPrimary, orderPlainHeaderActions, primaryHeaderAction, } from "./header-actions.js";
|
|
6
|
-
// Header actions bar
|
|
6
|
+
// Header actions bar: <=2 actions render as plain buttons; >2 with an
|
|
7
7
|
// `ActionOverflowMenu` primitive collapse to the primary button plus a menu
|
|
8
8
|
// for the rest — same rule as RowActionsCell for table rows. Owns confirm
|
|
9
9
|
// state for menu items: RenderEditActionButton already owns a per-button
|
|
@@ -130,9 +130,9 @@ function renderScreenBody({ schema, screen, translate, entityId, onRowClick, onC
|
|
|
130
130
|
return _jsx(CustomScreenBody, { screenId: screen.id, featureName: schema.featureName });
|
|
131
131
|
}
|
|
132
132
|
}
|
|
133
|
-
//
|
|
134
|
-
//
|
|
135
|
-
//
|
|
133
|
+
// The dashboard panel implementation (StatCard, charts) is platform-specific and
|
|
134
|
+
// comes from the DashboardBody context; renderer/web registers the web
|
|
135
|
+
// implementation in createKumikoApp.
|
|
136
136
|
function DashboardScreenBody({ featureName, screen, translate, }) {
|
|
137
137
|
const { Banner, Text } = usePrimitives();
|
|
138
138
|
const Body = useDashboardBody();
|
|
@@ -141,13 +141,11 @@ function DashboardScreenBody({ featureName, screen, translate, }) {
|
|
|
141
141
|
}
|
|
142
142
|
return (_jsx(Body, { featureName: featureName, screen: screen, ...(translate !== undefined && { translate }) }));
|
|
143
143
|
}
|
|
144
|
-
//
|
|
145
|
-
//
|
|
146
|
-
//
|
|
147
|
-
//
|
|
148
|
-
//
|
|
149
|
-
// gemountet ist, unabhängig davon ob ein Client-Plugin existiert
|
|
150
|
-
// (kumiko-framework#2025).
|
|
144
|
+
// Looks up the component from the CustomScreens context, populated by
|
|
145
|
+
// clientFeatures.components in createKumikoApp. If no provider is mounted or
|
|
146
|
+
// the screenId is unregistered, an error banner is rendered instead of an
|
|
147
|
+
// empty screen. The screen remains reachable by URL when the server feature is
|
|
148
|
+
// mounted, even without a client plugin.
|
|
151
149
|
function CustomScreenBody({ screenId, featureName, }) {
|
|
152
150
|
const { Banner, Text } = usePrimitives();
|
|
153
151
|
const Component = useCustomScreenComponent(screenId);
|
|
@@ -3,7 +3,7 @@ import { isFieldsEditSection, normalizeEditField, sectionFieldSpecs, } from "@co
|
|
|
3
3
|
// (and relatedList) sections skipped. Both this and `layoutFieldNames` key
|
|
4
4
|
// off "rendered by the layout" for the same reason: a field the user never
|
|
5
5
|
// sees gets no chance to review/correct a value nor to fix a presence error
|
|
6
|
-
// (search-param merge
|
|
6
|
+
// (search-param merge; presence schema in form-schema.ts).
|
|
7
7
|
export function layoutEditFields(screen) {
|
|
8
8
|
const specs = [];
|
|
9
9
|
for (const section of screen.layout.sections) {
|
package/dist/app/list-facets.js
CHANGED
|
@@ -94,7 +94,7 @@ export function normalizeDefaultFilters(defaults) {
|
|
|
94
94
|
// hand-crafted URL param for an undeclared field never reaches the
|
|
95
95
|
// server). Deliberately NOT gated on the field being a *facet* — entityList
|
|
96
96
|
// passes through any field present in entity.fields, matching its
|
|
97
|
-
//
|
|
97
|
+
// earlier behavior; only the boolean-coercion branch cares about type.
|
|
98
98
|
function isFacetI18nKey(label) {
|
|
99
99
|
return !/\s/.test(label) && (label.includes(".") || label.includes(":"));
|
|
100
100
|
}
|
|
@@ -119,8 +119,8 @@ export function buildFilterPayload(urlFilters, typeOf) {
|
|
|
119
119
|
return out;
|
|
120
120
|
}
|
|
121
121
|
// projectionList/relatedList adapter — neither has an entity/i18n convention
|
|
122
|
-
// to derive labels from, so ListFacetSpec carries every label explicitly
|
|
123
|
-
//
|
|
122
|
+
// to derive labels from, so ListFacetSpec carries every label explicitly.
|
|
123
|
+
// Labels may be raw display strings or i18n keys; run them
|
|
124
124
|
// through translate like entity facets (passthrough when the key is missing).
|
|
125
125
|
// `featureName` resolves a reference facet's `entity` target the same way
|
|
126
126
|
// ListColumnSpec.refEntity does (same-feature short name, or "feature:entity").
|
package/dist/app/nav.d.ts
CHANGED
|
@@ -40,16 +40,16 @@ export type NavApi = {
|
|
|
40
40
|
* platform-specific Link-Komponenten (Web: `<a href>`; Native
|
|
41
41
|
* typically doesn't need this). */
|
|
42
42
|
readonly hrefFor: (target: NavTarget, options?: NavigateOptions) => string;
|
|
43
|
-
/**
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
43
|
+
/** Snapshot of the current search params (browser: `?key=value` pairs
|
|
44
|
+
* after the path). Native implementations without a URL concept return
|
|
45
|
+
* an empty object. The value is a plain record, not a Map, so React
|
|
46
|
+
* subscribers can compare it shallowly. */
|
|
47
47
|
readonly searchParams: Readonly<Record<string, string>>;
|
|
48
|
-
/**
|
|
49
|
-
*
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
48
|
+
/** Merges updates into the current search params. A `null` value deletes
|
|
49
|
+
* the key. The path is unchanged. Platform implementations use
|
|
50
|
+
* `history.replaceState` rather than a push so sort/filter toggles do not
|
|
51
|
+
* flood back navigation. Native implementations may no-op when the
|
|
52
|
+
* concept does not exist. */
|
|
53
53
|
readonly setSearchParams: (updates: Readonly<Record<string, string | null>>) => void;
|
|
54
54
|
};
|
|
55
55
|
export declare function parsePath(pathname: string, hasWorkspaces?: boolean): NavRoute | undefined;
|
|
@@ -60,7 +60,7 @@ export declare function findDetailForScreen(features: readonly FeatureSchema[],
|
|
|
60
60
|
readonly screen: FeatureSchema["screens"][number];
|
|
61
61
|
} | undefined;
|
|
62
62
|
/** Non-throwing existence check for findDetailForScreen — lets a caller pick
|
|
63
|
-
* a different default (
|
|
63
|
+
* a different default (row-click fallback) instead of resolving. */
|
|
64
64
|
export declare function hasDetailScreen(features: readonly FeatureSchema[], entity: string): boolean;
|
|
65
65
|
export declare function resolveTarget(features: readonly FeatureSchema[], target: NavTarget): ScreenTarget;
|
|
66
66
|
export type NavProviderProps = {
|
package/dist/app/nav.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
2
|
import { createContext, useCallback, useContext, useEffect, useState } from "react";
|
|
3
3
|
import { lastSegment } from "./qn.js";
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
4
|
+
// Path format:
|
|
5
|
+
// without workspaces: `/task-list`, `/task-edit/abc-123`, `/` → undefined
|
|
6
|
+
// with workspaces: `/admin/task-list`, `/admin/task-edit/abc`,
|
|
7
|
+
// `/admin` → workspace-only (no screen yet),
|
|
8
|
+
// `/` → undefined.
|
|
9
|
+
// Everything after the first 2/3 segments is ignored because Kumiko's
|
|
10
|
+
// navigation grammar does not support further nesting. Nested routes would
|
|
11
|
+
// be a spec change, not an accidental URL shape.
|
|
12
12
|
function decodeSegments(segments) {
|
|
13
13
|
try {
|
|
14
14
|
return segments.map((segment) => decodeURIComponent(segment));
|
|
@@ -57,7 +57,7 @@ export function formatPath(target) {
|
|
|
57
57
|
return `/${segments.map((segment) => encodeURIComponent(segment)).join("/")}`;
|
|
58
58
|
}
|
|
59
59
|
// Shared entity→detailFor-screen lookup — resolveTarget's ObjectTarget
|
|
60
|
-
// branch and hasDetailScreen (create-app's row-click default
|
|
60
|
+
// branch and hasDetailScreen (create-app's row-click default) both
|
|
61
61
|
// need "is there a detail screen for this entity", one to resolve it, the
|
|
62
62
|
// other to just check before falling back to a different default.
|
|
63
63
|
export function findDetailForScreen(features, entity) {
|
|
@@ -70,7 +70,7 @@ export function findDetailForScreen(features, entity) {
|
|
|
70
70
|
return undefined;
|
|
71
71
|
}
|
|
72
72
|
/** Non-throwing existence check for findDetailForScreen — lets a caller pick
|
|
73
|
-
* a different default (
|
|
73
|
+
* a different default (row-click fallback) instead of resolving. */
|
|
74
74
|
export function hasDetailScreen(features, entity) {
|
|
75
75
|
return findDetailForScreen(features, entity) !== undefined;
|
|
76
76
|
}
|
|
@@ -93,9 +93,9 @@ export function resolveTarget(features, target) {
|
|
|
93
93
|
}
|
|
94
94
|
throw new Error(`resolveTarget: no detail screen for entity "${target.entity}". Add detailFor: "${target.entity}" to the screen that shows it.`);
|
|
95
95
|
}
|
|
96
|
-
// Context
|
|
97
|
-
//
|
|
98
|
-
//
|
|
96
|
+
// Context and hook. The default is `undefined` so a missing provider fails
|
|
97
|
+
// loudly instead of exposing a silent no-op NavApi with dead `navigate()`
|
|
98
|
+
// calls.
|
|
99
99
|
const NavContext = createContext(undefined);
|
|
100
100
|
const InitialValuesHandoffContext = createContext(undefined);
|
|
101
101
|
export function NavProvider({ children, value }) {
|
|
@@ -188,11 +188,11 @@ export function ProjectionDetailBody({ schema, screen, translate, entityId, }) {
|
|
|
188
188
|
await refetchAfterWrite(detailQuery.refetch);
|
|
189
189
|
setReloadNonce((n) => n + 1);
|
|
190
190
|
}, [detailQuery.refetch]);
|
|
191
|
-
// Header actions render directly via RenderEditActionButton
|
|
192
|
-
// headerActions moved out of RenderEdit's footer
|
|
191
|
+
// Header actions render directly via RenderEditActionButton
|
|
192
|
+
// headerActions moved out of RenderEdit's footer — this component owns
|
|
193
193
|
// the failure-surfacing state RenderEdit used to own for them.
|
|
194
194
|
const [actionError, setActionError] = useState(null);
|
|
195
|
-
// Default edit action
|
|
195
|
+
// Default edit action: resolved cross-feature over ALL mounted
|
|
196
196
|
// features, not just this feature's own schema — detailFor itself is
|
|
197
197
|
// resolved cross-feature by the boot-validator (detail-screens.ts), and
|
|
198
198
|
// the motivating case is a projectionDetail whose query belongs to one
|
|
@@ -383,7 +383,7 @@ export function ProjectionDetailBody({ schema, screen, translate, entityId, }) {
|
|
|
383
383
|
}),
|
|
384
384
|
}) }));
|
|
385
385
|
}
|
|
386
|
-
// Grouped into the head Card alongside title/status/metrics
|
|
386
|
+
// Grouped into the head Card alongside title/status/metrics:
|
|
387
387
|
// these are actions on the record the head shows, not on whichever tab is
|
|
388
388
|
// open, so they must stay in place across tab switches instead of
|
|
389
389
|
// trailing the active tab's content in the card footer.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import type { EditLayout, EntityDefinition, EntityEditScreenDefinition, ProjectionDetailScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
-
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
2
|
+
/** Minimal EntityDefinition built from the layout fields: every field is a text
|
|
3
|
+
* field — computeEditViewModel only reads `fields[<f>].type`, and text is
|
|
4
|
+
* sufficient for display-only rendering (no Select/Number-specific rendering
|
|
5
|
+
* is needed). */
|
|
5
6
|
export declare function synthesizeProjectionDetailEntity(layout: EditLayout): Pick<EntityDefinition, "fields">;
|
|
6
|
-
/**
|
|
7
|
-
*
|
|
7
|
+
/** Converts a ProjectionDetailScreenDefinition to the EntityEditScreen shape
|
|
8
|
+
* expected by RenderEdit, with every field forcibly set to readOnly:true. */
|
|
8
9
|
export declare function synthesizeProjectionDetailScreen(screen: ProjectionDetailScreenDefinition): EntityEditScreenDefinition;
|
|
@@ -1,29 +1,30 @@
|
|
|
1
|
-
// Shim
|
|
1
|
+
// Shim for the ProjectionDetail renderer (analogous to projection-list-shim.ts /
|
|
2
2
|
// config-edit-shim.ts).
|
|
3
3
|
//
|
|
4
|
-
// RenderEdit
|
|
5
|
-
// `screen: EntityEditScreenDefinition
|
|
6
|
-
// `entity.fields` (
|
|
7
|
-
// (
|
|
8
|
-
// query-
|
|
9
|
-
//
|
|
4
|
+
// RenderEdit requires an `entity: EntityDefinition` +
|
|
5
|
+
// `screen: EntityEditScreenDefinition` pair, but only uses
|
|
6
|
+
// `entity.fields` (field type for the input renderer) and `screen.layout`
|
|
7
|
+
// (which fields belong in which sections). A projectionDetail screen is
|
|
8
|
+
// query-driven and has NO Entity — these two helpers shape the input ad hoc
|
|
9
|
+
// so RenderEdit can be reused.
|
|
10
10
|
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
11
|
+
// The structural read-only guarantee is established in
|
|
12
|
+
// synthesizeProjectionDetailScreen: EVERY field is forced to readOnly:true
|
|
13
|
+
// (not just fields the author marked in the layout) — hasEditableSection()
|
|
14
|
+
// reads exactly this flag and hides the Save button when no field is
|
|
15
|
+
// editable. The author cannot accidentally bypass this guarantee.
|
|
16
16
|
//
|
|
17
|
-
//
|
|
18
|
-
//
|
|
19
|
-
// cross-
|
|
20
|
-
//
|
|
21
|
-
//
|
|
17
|
+
// Same debt reservation as in the other shims: if RenderEdit later accesses
|
|
18
|
+
// additional EntityDefinition fields (transitions, idType), or a boot
|
|
19
|
+
// validator cross-references the schema.entities map, these type lies will
|
|
20
|
+
// fail silently — then it is time for a real RenderProjectionDetail
|
|
21
|
+
// component.
|
|
22
22
|
import { isFieldsEditSection, normalizeEditField, PROJECTION_DETAIL_ENTITY as PROJECTION_DETAIL_PSEUDO_ENTITY, sectionFieldSpecs, } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
23
23
|
import { SYNTHESIZED_FIELD_PII_REASON } from "./projection-list-shim.js";
|
|
24
|
-
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
/** Minimal EntityDefinition built from the layout fields: every field is a text
|
|
25
|
+
* field — computeEditViewModel only reads `fields[<f>].type`, and text is
|
|
26
|
+
* sufficient for display-only rendering (no Select/Number-specific rendering
|
|
27
|
+
* is needed). */
|
|
27
28
|
export function synthesizeProjectionDetailEntity(layout) {
|
|
28
29
|
const fields = {};
|
|
29
30
|
for (const section of layout.sections) {
|
|
@@ -39,8 +40,8 @@ export function synthesizeProjectionDetailEntity(layout) {
|
|
|
39
40
|
}
|
|
40
41
|
return { fields };
|
|
41
42
|
}
|
|
42
|
-
/**
|
|
43
|
-
*
|
|
43
|
+
/** Converts a ProjectionDetailScreenDefinition to the EntityEditScreen shape
|
|
44
|
+
* expected by RenderEdit, with every field forcibly set to readOnly:true. */
|
|
44
45
|
export function synthesizeProjectionDetailScreen(screen) {
|
|
45
46
|
const forceReadOnly = (spec) => ({ ...normalizeEditField(spec), readOnly: true });
|
|
46
47
|
const sections = screen.layout.sections.map((section) => {
|
|
@@ -27,7 +27,7 @@ import { buildListQueryPayload, pageSizeOptionsFor } from "./screen-shared.js";
|
|
|
27
27
|
// (instead of being derived from an entity) — cross-feature capable.
|
|
28
28
|
// search/sort/pagination reuse the same URL-state + buildListQueryPayload
|
|
29
29
|
// wiring as EntityListBody; which of them are actually offered is derived
|
|
30
|
-
// from the query handler's Zod schema at buildAppSchema time
|
|
30
|
+
// from the query handler's Zod schema at buildAppSchema time, not
|
|
31
31
|
// declared here. Pages-mode pagination only — infinite-scroll accumulation
|
|
32
32
|
// isn't wired for projectionList.
|
|
33
33
|
export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
|
|
@@ -49,7 +49,7 @@ export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
|
|
|
49
49
|
return findEditScreenFor(detailFor, appFeatures, userRoles);
|
|
50
50
|
}, [appFeatures, screen.detailFor, userRoles]);
|
|
51
51
|
// searchable/sortable/paginated are derived at buildAppSchema time from the
|
|
52
|
-
// query handler's Zod schema
|
|
52
|
+
// query handler's Zod schema — not authored on the screen.
|
|
53
53
|
const searchable = screen.searchable ?? false;
|
|
54
54
|
const sortable = screen.sortable ?? false;
|
|
55
55
|
const paginated = screen.paginated ?? false;
|
|
@@ -72,7 +72,7 @@ export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
|
|
|
72
72
|
// Pages-mode only (see header comment) — an author-set pagination:
|
|
73
73
|
// "infinite" on a paginated projectionList is silently a no-op today.
|
|
74
74
|
const usePager = paginated && (screen.pagination ?? "pages") === "pages";
|
|
75
|
-
// Facets
|
|
75
|
+
// Facets — a projectionList has no entity, so screen.facets is
|
|
76
76
|
// the only field inventory; resolveProjectionFacetSpecs reshapes +
|
|
77
77
|
// translates its explicit labels into the same ResolvedFacetSpec the
|
|
78
78
|
// entityList adapter produces, so both feed the same
|
|
@@ -82,7 +82,7 @@ export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
|
|
|
82
82
|
const handleProjectionFacetOptions = useCallback((field, options) => setProjectionReferenceFacetOptions((prev) => prev[field] === options ? prev : { ...prev, [field]: options }), []);
|
|
83
83
|
const resolvedFacetSpecs = useMemo(() => mergeReferenceFacetOptions(facetSpecs, projectionReferenceFacetOptions), [facetSpecs, projectionReferenceFacetOptions]);
|
|
84
84
|
const filterPayload = useMemo(() => buildFilterPayload(urlState.filters, (field) => facetSpecs.find((spec) => spec.field === field)?.type), [urlState.filters, facetSpecs]);
|
|
85
|
-
// dateRange facets
|
|
85
|
+
// dateRange facets bypass `filters` entirely — their two bounds
|
|
86
86
|
// become the top-level query params the facet declares, resolved from a
|
|
87
87
|
// calendar date to an instant in the viewer's zone.
|
|
88
88
|
const timeZone = useOptionalTimeZone();
|
|
@@ -101,7 +101,7 @@ export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
|
|
|
101
101
|
// Gated on screen.filter/screen.facets being declared (not just on
|
|
102
102
|
// urlState/filterPayload happening to carry a value) — same rule as
|
|
103
103
|
// activeSearch/activeSort above: a stale URL must not smuggle a param
|
|
104
|
-
// the bound query's Zod schema doesn't accept
|
|
104
|
+
// the bound query's Zod schema doesn't accept.
|
|
105
105
|
if (screen.filter !== undefined) {
|
|
106
106
|
payload["filter"] = screen.filter;
|
|
107
107
|
}
|
|
@@ -131,8 +131,8 @@ export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
|
|
|
131
131
|
const handleDateRangeChange = useCallback((field, bound, value) => {
|
|
132
132
|
urlState.setDateRange(field, clampDateRange(readDateRange(urlState.filters, field), bound, value));
|
|
133
133
|
}, [urlState.setDateRange, urlState.filters]);
|
|
134
|
-
// Entity
|
|
135
|
-
//
|
|
134
|
+
// Entity targets: see EntityListBody.runNavigate for why the resolution happens
|
|
135
|
+
// in the NavApi impl. Unlike there: NO row["id"]
|
|
136
136
|
// fallback — projectionList rows come from an arbitrary query projection
|
|
137
137
|
// with no guaranteed "id" field. The boot validator therefore enforces an
|
|
138
138
|
// explicit entityId for projectionList entity targets. Same helper
|
|
@@ -198,8 +198,8 @@ export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
|
|
|
198
198
|
// A failed refetch keeps the last good data on screen.
|
|
199
199
|
const refetchErrorBanner = rowsQuery.error ? (_jsx(QueryErrorBanner, { error: rowsQuery.error, translate: effectiveTranslate, onRetry: rowsQuery.refetch })) : null;
|
|
200
200
|
// rowsQuery.data is typed as PagedRows but the query handler is free to
|
|
201
|
-
// return anything at runtime (
|
|
202
|
-
//
|
|
201
|
+
// return anything at runtime (a bare array silently rendered an empty
|
|
202
|
+
// table instead of surfacing the mismatch), so the shape is checked.
|
|
203
203
|
if (rowsQuery.data !== null && !Array.isArray(rowsQuery.data.rows)) {
|
|
204
204
|
return (_jsxs(Banner, { padded: true, variant: "error", testId: "kumiko-screen-projection-list-bad-shape", children: ["Screen ", _jsx(Text, { variant: "code", children: screen.id }), " query", " ", _jsx(Text, { variant: "code", children: screen.query }), " did not return a PagedRows envelope; a projectionList query must return ", _jsx(Text, { variant: "code", children: "{ rows, nextCursor }" }), "."] }));
|
|
205
205
|
}
|
|
@@ -213,7 +213,7 @@ export function ProjectionListBody({ schema, screen, translate, onRowClick, }) {
|
|
|
213
213
|
: undefined;
|
|
214
214
|
// Same pager-construction as EntityListBody: no Pager UI until the
|
|
215
215
|
// server-provided `total` arrives (guards pagination="pages" without
|
|
216
|
-
// totalCount support
|
|
216
|
+
// totalCount support).
|
|
217
217
|
const total = rowsQuery.data?.total;
|
|
218
218
|
const pager = usePager && total !== undefined
|
|
219
219
|
? {
|
|
@@ -2,14 +2,14 @@ import type { EntityDefinition, EntityListScreenDefinition, ListColumnSpec, Proj
|
|
|
2
2
|
export declare const SYNTHESIZED_FIELD_PII_REASON = "synthesized display field, not stored entity data";
|
|
3
3
|
/** Minimal EntityDefinition from the column list: every field is a text
|
|
4
4
|
* field. `sortable` applies to all columns — buildAppSchema derives it from
|
|
5
|
-
* the query's Zod schema (`screen.sortable
|
|
5
|
+
* the query's Zod schema (`screen.sortable`); the query itself has
|
|
6
6
|
* no per-field server-sort guarantee, so a column opts out with
|
|
7
7
|
* `sortable: false` when the handler cannot order by it.
|
|
8
8
|
* computeListViewModel only reads `fields[<col>].type` — text is enough,
|
|
9
9
|
* presentation comes from the column renderer + explicit label. */
|
|
10
10
|
export declare function synthesizeProjectionEntity(columns: readonly ListColumnSpec[], sortable: boolean): Pick<EntityDefinition, "fields">;
|
|
11
|
-
/**
|
|
12
|
-
* RenderList
|
|
13
|
-
*
|
|
14
|
-
*
|
|
11
|
+
/** Converts a ProjectionListScreenDefinition to the EntityListScreen shape
|
|
12
|
+
* expected by RenderList. `type` = "entityList" plus a pseudo-entity satisfy
|
|
13
|
+
* the type constraint; RenderList does not branch on `type` and reads `entity`
|
|
14
|
+
* only for error messages. The list fields are passed through unchanged. */
|
|
15
15
|
export declare function synthesizeProjectionScreen(screen: ProjectionListScreenDefinition): EntityListScreenDefinition;
|