@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.
Files changed (52) hide show
  1. package/dist/app/config-edit-shim.d.ts +7 -0
  2. package/dist/app/config-edit-shim.js +39 -0
  3. package/dist/app/content-preview.js +7 -9
  4. package/dist/app/extension-sections.d.ts +6 -0
  5. package/dist/app/form-schema.d.ts +2 -1
  6. package/dist/app/form-schema.js +11 -2
  7. package/dist/app/kumiko-screen.d.ts +5 -0
  8. package/dist/app/kumiko-screen.js +259 -101
  9. package/dist/app/nav.d.ts +5 -0
  10. package/dist/app/nav.js +17 -4
  11. package/dist/app/query-state-banners.d.ts +8 -0
  12. package/dist/app/query-state-banners.js +14 -0
  13. package/dist/app/reference-facet-bridge.js +8 -1
  14. package/dist/app/row-actions.d.ts +12 -0
  15. package/dist/app/row-actions.js +24 -7
  16. package/dist/app/screen-access.js +13 -9
  17. package/dist/app/secret-mint-body.js +41 -7
  18. package/dist/app/secrets-edit-body.js +89 -33
  19. package/dist/components/embedded-list-field.js +2 -4
  20. package/dist/components/grid-cell-for-field.d.ts +7 -2
  21. package/dist/components/grid-cell-for-field.js +2 -2
  22. package/dist/components/multi-select-checkboxes.js +4 -1
  23. package/dist/components/query-options-select.d.ts +12 -3
  24. package/dist/components/query-options-select.js +45 -2
  25. package/dist/components/reference-create-dialog.js +9 -4
  26. package/dist/components/related-list-section.js +8 -10
  27. package/dist/components/render-edit-action-button.d.ts +1 -1
  28. package/dist/components/render-edit-action-button.js +5 -1
  29. package/dist/components/render-edit-logic.d.ts +2 -1
  30. package/dist/components/render-edit-logic.js +8 -5
  31. package/dist/components/render-edit-types.d.ts +30 -1
  32. package/dist/components/render-edit.js +248 -76
  33. package/dist/components/render-field.d.ts +7 -1
  34. package/dist/components/render-field.js +40 -15
  35. package/dist/components/render-list.js +6 -6
  36. package/dist/components/write-form-section.d.ts +5 -1
  37. package/dist/components/write-form-section.js +29 -25
  38. package/dist/context/embedded-screen-context.d.ts +5 -0
  39. package/dist/context/embedded-screen-context.js +12 -0
  40. package/dist/hooks/use-reference-lookup.d.ts +7 -0
  41. package/dist/hooks/use-reference-lookup.js +10 -0
  42. package/dist/i18n-defaults.js +30 -5
  43. package/dist/i18n.js +8 -4
  44. package/dist/index.d.ts +5 -1
  45. package/dist/index.js +3 -0
  46. package/dist/primitives.d.ts +70 -19
  47. package/dist/session/session-ended.d.ts +16 -0
  48. package/dist/session/session-ended.js +73 -0
  49. package/dist/sort-by-accessor.d.ts +6 -2
  50. package/dist/sort-by-accessor.js +13 -3
  51. package/package.json +5 -5
  52. 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, "&amp;")
14
- .replace(/</g, "&lt;")
15
- .replace(/>/g, "&gt;")
16
- .replace(/"/g, "&quot;")
17
- .replace(/'/g, "&#39;");
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, escapeHtml(value)]))
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;
@@ -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
- export function buildFormSchema(entity, screen) {
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;