@riducms/admin 0.1.5 → 0.2.1

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 (66) hide show
  1. package/package.json +11 -9
  2. package/src/app.css +63 -4
  3. package/src/app.svelte +12 -14
  4. package/src/components/ui/date-value-control/date-value-control-loader.svelte +15 -4
  5. package/src/components/ui/date-value-control/date-value-control.svelte +17 -17
  6. package/src/components/ui/select/select-trigger.svelte +1 -1
  7. package/src/core/forms/embedded-fields.ts +132 -0
  8. package/src/core/forms/embedded-schema-draft.svelte.ts +335 -0
  9. package/src/core/forms/field-editor-authoring.ts +47 -0
  10. package/src/core/forms/field-editor-binding.ts +228 -0
  11. package/src/core/forms/field-issue-focus.ts +42 -0
  12. package/src/core/forms/field-occurrence.ts +122 -0
  13. package/src/core/forms/form-controller.svelte.ts +475 -29
  14. package/src/core/forms/form-issue-correlation.ts +159 -0
  15. package/src/core/forms/form-schema.ts +211 -17
  16. package/src/core/forms/form-validation.ts +183 -17
  17. package/src/core/forms/live-validation.svelte.ts +317 -0
  18. package/src/core/forms/plugin-field-authoring.ts +160 -0
  19. package/src/core/forms/plugin-field-binding.ts +258 -0
  20. package/src/core/forms/plugin-field-write.ts +57 -0
  21. package/src/core/i18n/admin-i18n.svelte.ts +8 -1
  22. package/src/core/i18n/localized-schema.ts +33 -10
  23. package/src/core/plugins/field-registry.ts +29 -49
  24. package/src/core/plugins/row-label-registry.ts +25 -5
  25. package/src/core/runtime/admin-runtime.svelte.ts +25 -13
  26. package/src/features/auth/account-route.svelte +10 -5
  27. package/src/features/auth/account-security-route.svelte +1 -1
  28. package/src/features/collections/collection-list-bulk-editor.svelte +6 -6
  29. package/src/features/collections/collection-list-controller.svelte.ts +5 -5
  30. package/src/features/collections/collection-list-filter-popover.svelte +6 -6
  31. package/src/features/collections/collection-list-route.svelte +13 -14
  32. package/src/features/collections/list-workspace.ts +22 -8
  33. package/src/features/documents/document-controller.svelte.ts +8 -2
  34. package/src/features/documents/document-route.svelte +36 -43
  35. package/src/features/reference-browser/reference-browser-workflow.svelte.ts +30 -15
  36. package/src/features/reference-browser/reference-browser.svelte +215 -189
  37. package/src/features/uploads/upload-metadata-fields.svelte +6 -6
  38. package/src/features/versions/version-history-route.svelte +1 -1
  39. package/src/fields/embedded-schema-draft-editor.svelte +74 -0
  40. package/src/fields/embedded-schema-fields.svelte +44 -0
  41. package/src/fields/field-clipboard.ts +76 -16
  42. package/src/fields/field-collapsible.svelte +49 -0
  43. package/src/fields/field-layout.svelte +2 -18
  44. package/src/fields/field-messages.svelte +13 -18
  45. package/src/fields/field-renderer.svelte +53 -14
  46. package/src/fields/join/join-field.svelte +8 -1
  47. package/src/fields/live-validation-feedback.svelte +21 -0
  48. package/src/fields/local-field-editor.svelte +52 -0
  49. package/src/fields/nested/nested-field.svelte +159 -91
  50. package/src/fields/nested/scoped-field.ts +7 -9
  51. package/src/fields/plugin-field.svelte +156 -0
  52. package/src/fields/point/point-field.svelte +12 -2
  53. package/src/fields/primitive-list/primitive-list-field.svelte +170 -0
  54. package/src/fields/primitive-list/primitive-number-input.ts +6 -0
  55. package/src/fields/radio/radio-field.svelte +13 -9
  56. package/src/fields/relationship/relationship-field.svelte +36 -12
  57. package/src/fields/relationship/relationship-query.ts +4 -2
  58. package/src/fields/relationship/relationship-quick-picker.svelte +38 -10
  59. package/src/fields/scalar/date-control-value.ts +5 -5
  60. package/src/fields/scalar/date-value.ts +5 -5
  61. package/src/fields/scalar/scalar-field.svelte +47 -41
  62. package/src/fields/select/select-field.svelte +24 -24
  63. package/src/fields/select/select-value.ts +3 -3
  64. package/src/fields/text/slug-field.svelte +24 -8
  65. package/src/fields/text/text-field.svelte +8 -6
  66. package/src/index.ts +4 -12
@@ -0,0 +1,122 @@
1
+ import { resolveBlockTypes } from "@riducms/protocol";
2
+ import type { SchemaField } from "@riducms/protocol";
3
+ import type { FormController } from "@admin/core/forms/form-controller.svelte";
4
+ import { embeddedOccurrences } from "@admin/core/forms/embedded-fields";
5
+ import { scopeRepeatedRowField } from "@admin/fields/nested/scoped-field";
6
+
7
+ type ResolvePath = () => string | undefined;
8
+ export interface FieldOccurrence {
9
+ schema: SchemaField;
10
+ resolve: ResolvePath;
11
+ ancestors: readonly { schema: SchemaField; resolve: ResolvePath }[];
12
+ }
13
+
14
+ /** Capture schema-owned identities once. Resolution never trusts a retained numeric index. */
15
+ export function captureFieldOccurrence(form: FormController, target: string): FieldOccurrence {
16
+ const find = (
17
+ fields: readonly SchemaField[],
18
+ parent: ResolvePath,
19
+ instance: string,
20
+ ancestors: FieldOccurrence["ancestors"]
21
+ ): FieldOccurrence | undefined => {
22
+ for (const field of fields) {
23
+ const path = () => {
24
+ const prefix = parent();
25
+ return prefix === undefined ? undefined : prefix ? `${prefix}.${field.name}` : field.name;
26
+ };
27
+ const current = path();
28
+ if (current === undefined || (target !== current && !target.startsWith(`${current}.`)))
29
+ continue;
30
+ const parents = [...ancestors, { schema: field, resolve: path }];
31
+ const schema = scopeRepeatedRowField(field, parent() ?? "", instance);
32
+ // Root fields do not have a leading dot.
33
+ if (target === current)
34
+ return { schema: { ...schema, path: current }, resolve: path, ancestors };
35
+ if (field.type === "group") {
36
+ // An absent optional object may initialize through its visible inputs.
37
+ // Once present, removing it revokes the child occurrence immediately.
38
+ let initialized = record(form.get(current));
39
+ const objectPath = () => {
40
+ const current = path();
41
+ if (current === undefined) return;
42
+ if (record(form.get(current))) initialized = true;
43
+ else if (initialized) return;
44
+ return current;
45
+ };
46
+ const result = find(field.nested?.fields ?? [], objectPath, instance, parents);
47
+ if (result) return result;
48
+ }
49
+ if (field.type === "array" || field.type === "blocks") {
50
+ const rows = form.get(current);
51
+ const index = Number(target.slice(current.length + 1).split(".")[0]);
52
+ if (!Array.isArray(rows) || !Number.isSafeInteger(index)) continue;
53
+ const row: unknown = rows[index];
54
+ if (!record(row) || typeof row._key !== "string" || !row._key)
55
+ throw new Error(`Cannot bind ${target}: rows require stable _key values.`);
56
+ const key = row._key,
57
+ variant = row.blockType;
58
+ const rowPath = () => {
59
+ const base = path();
60
+ if (base === undefined) return;
61
+ const values = form.get(base);
62
+ if (!Array.isArray(values)) return;
63
+ const matches = values
64
+ .map((value: unknown, index) => ({ value, index }))
65
+ .filter(({ value }) => record(value) && value._key === key);
66
+ const match = matches[0];
67
+ if (
68
+ matches.length !== 1 ||
69
+ !match ||
70
+ !record(match.value) ||
71
+ match.value.blockType !== variant
72
+ )
73
+ return;
74
+ return `${base}.${match.index}`;
75
+ };
76
+ const children =
77
+ field.type === "array"
78
+ ? field.nested?.fields
79
+ : resolveBlockTypes(field.blocks).find((block) => block.slug === variant)?.fields;
80
+ const result = find(children ?? [], rowPath, `${instance}-${key}`, parents);
81
+ if (result) return result;
82
+ }
83
+ if (field.plugin?.embeddedTrees !== undefined) {
84
+ const initial = embeddedOccurrences(field, form.get(current), current).occurrences.find(
85
+ (item) => target.startsWith(`${item.path}.`)
86
+ );
87
+ if (!initial || initial.identity === undefined)
88
+ throw new Error(`Cannot bind ${target}: embedded payloads require stable identities.`);
89
+ const { identity, tree, block, case: branch } = initial;
90
+ const payloadPath = () => {
91
+ const base = path();
92
+ if (base === undefined) return;
93
+ const matches = embeddedOccurrences(field, form.get(base), base).occurrences.filter(
94
+ (item) => item.tree.key === tree.key && item.identity === identity
95
+ );
96
+ const match = matches[0];
97
+ if (
98
+ matches.length !== 1 ||
99
+ match?.block.slug !== block.slug ||
100
+ match.case.tagValue !== branch.tagValue
101
+ )
102
+ return;
103
+ return match.path;
104
+ };
105
+ const result = find(
106
+ block.fields,
107
+ payloadPath,
108
+ `${instance}-${tree.key}-${identity}`,
109
+ parents
110
+ );
111
+ if (result) return result;
112
+ }
113
+ }
114
+ };
115
+ const found = find(form.schemaFields, () => "", "binding", []);
116
+ if (!found || found.resolve() === undefined)
117
+ throw new Error(`Cannot bind ${target}: no unique live schema field occurrence exists.`);
118
+ return found;
119
+ }
120
+ function record(value: unknown): value is Record<string, unknown> {
121
+ return typeof value === "object" && value !== null && !Array.isArray(value);
122
+ }