@cosmicdrift/kumiko-renderer 0.333.0 → 0.335.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/config-edit-shim.d.ts +7 -0
- package/dist/app/config-edit-shim.js +39 -0
- package/dist/app/content-preview.js +7 -9
- package/dist/app/extension-sections.d.ts +6 -0
- package/dist/app/form-schema.d.ts +2 -1
- package/dist/app/form-schema.js +11 -2
- package/dist/app/kumiko-screen.d.ts +5 -0
- package/dist/app/kumiko-screen.js +259 -101
- package/dist/app/nav.d.ts +5 -0
- package/dist/app/nav.js +17 -4
- package/dist/app/query-state-banners.d.ts +8 -0
- package/dist/app/query-state-banners.js +14 -0
- package/dist/app/reference-facet-bridge.js +8 -1
- package/dist/app/row-actions.d.ts +12 -0
- package/dist/app/row-actions.js +24 -7
- package/dist/app/screen-access.js +13 -9
- package/dist/app/secret-mint-body.js +41 -7
- package/dist/app/secrets-edit-body.js +89 -33
- package/dist/components/embedded-list-field.js +2 -4
- package/dist/components/grid-cell-for-field.d.ts +7 -2
- package/dist/components/grid-cell-for-field.js +2 -2
- package/dist/components/multi-select-checkboxes.js +4 -1
- package/dist/components/query-options-select.d.ts +12 -3
- package/dist/components/query-options-select.js +45 -2
- package/dist/components/reference-create-dialog.js +9 -4
- package/dist/components/related-list-section.js +8 -10
- package/dist/components/render-edit-action-button.d.ts +1 -1
- package/dist/components/render-edit-action-button.js +5 -1
- package/dist/components/render-edit-logic.d.ts +2 -1
- package/dist/components/render-edit-logic.js +8 -5
- package/dist/components/render-edit-types.d.ts +30 -1
- package/dist/components/render-edit.js +248 -76
- package/dist/components/render-field.d.ts +7 -1
- package/dist/components/render-field.js +40 -15
- package/dist/components/render-list.js +6 -6
- package/dist/components/write-form-section.d.ts +5 -1
- package/dist/components/write-form-section.js +29 -25
- package/dist/context/embedded-screen-context.d.ts +5 -0
- package/dist/context/embedded-screen-context.js +12 -0
- package/dist/hooks/use-reference-lookup.d.ts +7 -0
- package/dist/hooks/use-reference-lookup.js +10 -0
- package/dist/i18n-defaults.js +30 -5
- package/dist/i18n.js +8 -4
- package/dist/index.d.ts +5 -1
- package/dist/index.js +3 -0
- package/dist/primitives.d.ts +70 -19
- package/dist/session/session-ended.d.ts +16 -0
- package/dist/session/session-ended.js +73 -0
- package/dist/sort-by-accessor.d.ts +6 -2
- package/dist/sort-by-accessor.js +13 -3
- package/package.json +5 -5
- package/src/changes.json +40 -0
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { ConfigEditScreenDefinition, EntityDefinition, EntityEditScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
|
+
import * as z from "zod";
|
|
2
3
|
/** Baut eine minimale EntityDefinition aus den Inline-Fields des
|
|
3
4
|
* ConfigEdit-Screens. RenderEdit + computeEditViewModel iterieren
|
|
4
5
|
* über entity.fields zur Render-Zeit; alle weiteren EntityDefinition-
|
|
@@ -9,3 +10,9 @@ export declare function synthesizeConfigEditEntity(fields: ConfigEditScreenDefin
|
|
|
9
10
|
* damit der Type-Constraint hält; entity wird auf den Pseudo-Namen
|
|
10
11
|
* gepinnt — RenderEdit liest das Feld nicht. */
|
|
11
12
|
export declare function synthesizeConfigEditScreen(screen: ConfigEditScreenDefinition): EntityEditScreenDefinition;
|
|
13
|
+
/** Client-side range check for number keys with declared bounds, so an
|
|
14
|
+
* out-of-range value surfaces on its field instead of as the server's
|
|
15
|
+
* form-level "Validation failed." banner. Only bounds: required/format stay
|
|
16
|
+
* server-authoritative like in buildFormSchema. `.passthrough()` keeps every
|
|
17
|
+
* form key visible to the superRefine. */
|
|
18
|
+
export declare function buildConfigEditSchema(fields: ConfigEditScreenDefinition["fields"]): z.ZodType;
|
|
@@ -12,6 +12,8 @@
|
|
|
12
12
|
// zukünftiger Boot-Validator die schema.entities-Map cross-referenziert,
|
|
13
13
|
// brechen die Type-Lies hier silent. Dann ist Zeit für eine echte
|
|
14
14
|
// RenderConfigEdit-Komponente die direkt fields nimmt.
|
|
15
|
+
import { I18N_KEY_PARAM } from "@cosmicdrift/kumiko-headless";
|
|
16
|
+
import * as z from "zod";
|
|
15
17
|
const CONFIG_EDIT_PSEUDO_ENTITY = "__config-edit__";
|
|
16
18
|
/** Baut eine minimale EntityDefinition aus den Inline-Fields des
|
|
17
19
|
* ConfigEdit-Screens. RenderEdit + computeEditViewModel iterieren
|
|
@@ -35,3 +37,40 @@ export function synthesizeConfigEditScreen(screen) {
|
|
|
35
37
|
...(screen.access !== undefined && { access: screen.access }),
|
|
36
38
|
};
|
|
37
39
|
}
|
|
40
|
+
/** Client-side range check for number keys with declared bounds, so an
|
|
41
|
+
* out-of-range value surfaces on its field instead of as the server's
|
|
42
|
+
* form-level "Validation failed." banner. Only bounds: required/format stay
|
|
43
|
+
* server-authoritative like in buildFormSchema. `.passthrough()` keeps every
|
|
44
|
+
* form key visible to the superRefine. */
|
|
45
|
+
export function buildConfigEditSchema(fields) {
|
|
46
|
+
return z
|
|
47
|
+
.object({})
|
|
48
|
+
.passthrough()
|
|
49
|
+
.superRefine((values, ctx) => {
|
|
50
|
+
// @cast-boundary form-values
|
|
51
|
+
const record = values;
|
|
52
|
+
for (const [name, field] of Object.entries(fields)) {
|
|
53
|
+
if (field.type !== "number")
|
|
54
|
+
continue;
|
|
55
|
+
const value = record[name];
|
|
56
|
+
if (typeof value !== "number" || !Number.isFinite(value))
|
|
57
|
+
continue;
|
|
58
|
+
if (field.min !== undefined && value < field.min) {
|
|
59
|
+
ctx.addIssue({
|
|
60
|
+
code: "custom",
|
|
61
|
+
minimum: field.min,
|
|
62
|
+
params: { [I18N_KEY_PARAM]: "kumiko.validation.number.min" },
|
|
63
|
+
path: [name],
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
if (field.max !== undefined && value > field.max) {
|
|
67
|
+
ctx.addIssue({
|
|
68
|
+
code: "custom",
|
|
69
|
+
maximum: field.max,
|
|
70
|
+
params: { [I18N_KEY_PARAM]: "kumiko.validation.number.max" },
|
|
71
|
+
path: [name],
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
}
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
// Read-only render of a content editor's format with the collection's
|
|
3
|
+
// example variableSchema values substituted in for `{{name}}` — reuses the
|
|
4
|
+
// same registered editor the collection edits with (readOnly), so "rich"
|
|
5
|
+
// renders formatted HTML and "plain"/"markdown" render as text through the
|
|
6
|
+
// exact same component, no separate render path per format.
|
|
7
|
+
import { escapeHtmlAttr } from "@cosmicdrift/kumiko-headless";
|
|
2
8
|
import { useId } from "react";
|
|
3
9
|
import { useContentEditor } from "./content-editors.js";
|
|
4
10
|
const VARIABLE_PATTERN = /\{\{\s*(\w+)\s*\}\}/g;
|
|
@@ -8,14 +14,6 @@ const noop = () => { };
|
|
|
8
14
|
export function substituteVariables(content, variables) {
|
|
9
15
|
return content.replace(VARIABLE_PATTERN, (match, name) => Object.hasOwn(variables, name) ? (variables[name] ?? match) : match);
|
|
10
16
|
}
|
|
11
|
-
function escapeHtml(value) {
|
|
12
|
-
return value
|
|
13
|
-
.replace(/&/g, "&")
|
|
14
|
-
.replace(/</g, "<")
|
|
15
|
-
.replace(/>/g, ">")
|
|
16
|
-
.replace(/"/g, """)
|
|
17
|
-
.replace(/'/g, "'");
|
|
18
|
-
}
|
|
19
17
|
export function ContentPreview({ content, variables, contentFormat, }) {
|
|
20
18
|
const Editor = useContentEditor(contentFormat);
|
|
21
19
|
const id = useId();
|
|
@@ -24,7 +22,7 @@ export function ContentPreview({ content, variables, contentFormat, }) {
|
|
|
24
22
|
// render as an unescaped entity (`&`). "plain"/"markdown" content is text,
|
|
25
23
|
// no escaping wanted there.
|
|
26
24
|
const safeVariables = contentFormat === "rich"
|
|
27
|
-
? Object.fromEntries(Object.entries(variables).map(([name, value]) => [name,
|
|
25
|
+
? Object.fromEntries(Object.entries(variables).map(([name, value]) => [name, escapeHtmlAttr(value)]))
|
|
28
26
|
: variables;
|
|
29
27
|
return (_jsx(Editor, { id: id, value: substituteVariables(content, safeVariables), onChange: noop, variables: [], readOnly: true }));
|
|
30
28
|
}
|
|
@@ -55,6 +55,12 @@ export type ExtensionSectionProps = {
|
|
|
55
55
|
readonly index: number;
|
|
56
56
|
readonly isLast: boolean;
|
|
57
57
|
};
|
|
58
|
+
/** Only set for extension steps of a `layout.mode: "wizard"` entityEdit
|
|
59
|
+
* editing an existing record (update mode); undefined everywhere else.
|
|
60
|
+
* Call it (typically from an effect) with whether the step already holds
|
|
61
|
+
* its data, so the step bar shows the step as done. Repeated calls with
|
|
62
|
+
* the same value are cheap. */
|
|
63
|
+
readonly reportStepComplete?: (complete: boolean) => void;
|
|
58
64
|
};
|
|
59
65
|
export type ExtensionSectionComponent = ComponentType<ExtensionSectionProps>;
|
|
60
66
|
export type ExtensionSectionsMap = Readonly<Record<string, ExtensionSectionComponent>>;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { EntityDefinition, EntityEditScreenDefinition } from "@cosmicdrift/kumiko-framework/ui-types";
|
|
2
2
|
import * as z from "zod";
|
|
3
|
+
export declare function isPresent(value: unknown): boolean;
|
|
3
4
|
export declare const REQUIRED_FIELD_I18N_KEY = "kumiko.validation.required";
|
|
4
|
-
export declare function buildFormSchema(entity: EntityDefinition, screen: EntityEditScreenDefinition): z.ZodType;
|
|
5
|
+
export declare function buildFormSchema(entity: EntityDefinition, screen: EntityEditScreenDefinition, unchangedFrom?: Readonly<Record<string, unknown>>): z.ZodType;
|
package/dist/app/form-schema.js
CHANGED
|
@@ -4,7 +4,7 @@ import * as z from "zod";
|
|
|
4
4
|
import { layoutEditFields } from "./layout-fields.js";
|
|
5
5
|
// `required` means "has a value", not "is truthy" — `false` and `0` count
|
|
6
6
|
// as present, only the actually-empty representations don't.
|
|
7
|
-
function isPresent(value) {
|
|
7
|
+
export function isPresent(value) {
|
|
8
8
|
if (value === undefined || value === null || value === "")
|
|
9
9
|
return false;
|
|
10
10
|
if (Array.isArray(value) && value.length === 0)
|
|
@@ -39,7 +39,12 @@ export const REQUIRED_FIELD_I18N_KEY = "kumiko.validation.required";
|
|
|
39
39
|
// checked here: `visible`. `runValidate` already filters issues on hidden
|
|
40
40
|
// fields via `computeFieldStates(options.fields, …)` (form-controller.ts:163,181),
|
|
41
41
|
// fed by `deriveFormFields(screen)` in render-edit.tsx.
|
|
42
|
-
|
|
42
|
+
//
|
|
43
|
+
// `unchangedFrom` is for payloadMode "changes" (update): a field still equal to
|
|
44
|
+
// its loaded value never reaches the payload, so the server never presence-
|
|
45
|
+
// checks it — a legacy row with a now-required empty field must stay saveable
|
|
46
|
+
// when the user edits something else.
|
|
47
|
+
export function buildFormSchema(entity, screen, unchangedFrom) {
|
|
43
48
|
const fields = layoutEditFields(screen);
|
|
44
49
|
return z
|
|
45
50
|
.object({})
|
|
@@ -73,6 +78,10 @@ export function buildFormSchema(entity, screen) {
|
|
|
73
78
|
continue;
|
|
74
79
|
if (isPresent(record[spec.field]))
|
|
75
80
|
continue;
|
|
81
|
+
if (unchangedFrom !== undefined &&
|
|
82
|
+
JSON.stringify(record[spec.field]) === JSON.stringify(unchangedFrom[spec.field])) {
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
76
85
|
ctx.addIssue({
|
|
77
86
|
code: "custom",
|
|
78
87
|
path: [spec.field],
|
|
@@ -20,7 +20,12 @@ export declare function qualifyNavId(featureName: string, navId: string): string
|
|
|
20
20
|
export { screenAccessAllows };
|
|
21
21
|
export declare function KumikoScreen({ schema, qn, translate, entityId, onRowClick, onCopyLink, }: KumikoScreenProps): ReactNode;
|
|
22
22
|
export declare function buildInitialValues(fields: Readonly<Record<string, unknown>>, defaultCurrency?: string, moneyCurrencyOverrides?: Readonly<Record<string, string>>): Readonly<Record<string, unknown>>;
|
|
23
|
+
export declare function tenantCurrencyMoneyFieldNames(fields: Readonly<Record<string, unknown>>): readonly string[];
|
|
23
24
|
export declare function literalCurrencyOverrides(fields: Readonly<Record<string, unknown>>): Readonly<Record<string, string>>;
|
|
25
|
+
export declare function useMoneyCurrencyOverrides(fields: Readonly<Record<string, unknown>>, tenantFieldNames: readonly string[], fallback: string): {
|
|
26
|
+
readonly overrides: Readonly<Record<string, string>> | undefined;
|
|
27
|
+
readonly loading: boolean;
|
|
28
|
+
};
|
|
24
29
|
export type InitialValueSources = {
|
|
25
30
|
readonly searchParams: Readonly<Record<string, string>>;
|
|
26
31
|
readonly urlPrefillFields: readonly string[] | undefined;
|