@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.
- package/package.json +11 -9
- package/src/app.css +63 -4
- package/src/app.svelte +12 -14
- package/src/components/ui/date-value-control/date-value-control-loader.svelte +15 -4
- package/src/components/ui/date-value-control/date-value-control.svelte +17 -17
- package/src/components/ui/select/select-trigger.svelte +1 -1
- package/src/core/forms/embedded-fields.ts +132 -0
- package/src/core/forms/embedded-schema-draft.svelte.ts +335 -0
- package/src/core/forms/field-editor-authoring.ts +47 -0
- package/src/core/forms/field-editor-binding.ts +228 -0
- package/src/core/forms/field-issue-focus.ts +42 -0
- package/src/core/forms/field-occurrence.ts +122 -0
- package/src/core/forms/form-controller.svelte.ts +475 -29
- package/src/core/forms/form-issue-correlation.ts +159 -0
- package/src/core/forms/form-schema.ts +211 -17
- package/src/core/forms/form-validation.ts +183 -17
- package/src/core/forms/live-validation.svelte.ts +317 -0
- package/src/core/forms/plugin-field-authoring.ts +160 -0
- package/src/core/forms/plugin-field-binding.ts +258 -0
- package/src/core/forms/plugin-field-write.ts +57 -0
- package/src/core/i18n/admin-i18n.svelte.ts +8 -1
- package/src/core/i18n/localized-schema.ts +33 -10
- package/src/core/plugins/field-registry.ts +29 -49
- package/src/core/plugins/row-label-registry.ts +25 -5
- package/src/core/runtime/admin-runtime.svelte.ts +25 -13
- package/src/features/auth/account-route.svelte +10 -5
- package/src/features/auth/account-security-route.svelte +1 -1
- package/src/features/collections/collection-list-bulk-editor.svelte +6 -6
- package/src/features/collections/collection-list-controller.svelte.ts +5 -5
- package/src/features/collections/collection-list-filter-popover.svelte +6 -6
- package/src/features/collections/collection-list-route.svelte +13 -14
- package/src/features/collections/list-workspace.ts +22 -8
- package/src/features/documents/document-controller.svelte.ts +8 -2
- package/src/features/documents/document-route.svelte +36 -43
- package/src/features/reference-browser/reference-browser-workflow.svelte.ts +30 -15
- package/src/features/reference-browser/reference-browser.svelte +215 -189
- package/src/features/uploads/upload-metadata-fields.svelte +6 -6
- package/src/features/versions/version-history-route.svelte +1 -1
- package/src/fields/embedded-schema-draft-editor.svelte +74 -0
- package/src/fields/embedded-schema-fields.svelte +44 -0
- package/src/fields/field-clipboard.ts +76 -16
- package/src/fields/field-collapsible.svelte +49 -0
- package/src/fields/field-layout.svelte +2 -18
- package/src/fields/field-messages.svelte +13 -18
- package/src/fields/field-renderer.svelte +53 -14
- package/src/fields/join/join-field.svelte +8 -1
- package/src/fields/live-validation-feedback.svelte +21 -0
- package/src/fields/local-field-editor.svelte +52 -0
- package/src/fields/nested/nested-field.svelte +159 -91
- package/src/fields/nested/scoped-field.ts +7 -9
- package/src/fields/plugin-field.svelte +156 -0
- package/src/fields/point/point-field.svelte +12 -2
- package/src/fields/primitive-list/primitive-list-field.svelte +170 -0
- package/src/fields/primitive-list/primitive-number-input.ts +6 -0
- package/src/fields/radio/radio-field.svelte +13 -9
- package/src/fields/relationship/relationship-field.svelte +36 -12
- package/src/fields/relationship/relationship-query.ts +4 -2
- package/src/fields/relationship/relationship-quick-picker.svelte +38 -10
- package/src/fields/scalar/date-control-value.ts +5 -5
- package/src/fields/scalar/date-value.ts +5 -5
- package/src/fields/scalar/scalar-field.svelte +47 -41
- package/src/fields/select/select-field.svelte +24 -24
- package/src/fields/select/select-value.ts +3 -3
- package/src/fields/text/slug-field.svelte +24 -8
- package/src/fields/text/text-field.svelte +8 -6
- 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
|
+
}
|