@svadmin/ui 0.88.1 → 0.92.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 (172) hide show
  1. package/dist/app.css +4 -0
  2. package/dist/app.theme.css +4 -0
  3. package/dist/component-registry.svelte.d.ts +2 -2
  4. package/dist/components/AboutSettings.svelte +23 -4
  5. package/dist/components/AdminApp.svelte +6 -4
  6. package/dist/components/AdminApp.svelte.d.ts +3 -3
  7. package/dist/components/ApiSettings.svelte +43 -27
  8. package/dist/components/ApprovalActionCard.svelte +8 -9
  9. package/dist/components/ApprovalCenter.svelte +24 -5
  10. package/dist/components/AuditLogViewer.svelte +2 -2
  11. package/dist/components/AutoComplete.svelte +610 -0
  12. package/dist/components/AutoComplete.svelte.d.ts +25 -0
  13. package/dist/components/AutoForm.svelte +265 -18
  14. package/dist/components/AutoForm.svelte.d.ts +19 -2
  15. package/dist/components/AutoTable.svelte +141 -36
  16. package/dist/components/AutoTable.svelte.d.ts +8 -0
  17. package/dist/components/BatchActionBar.svelte +42 -14
  18. package/dist/components/BatchActionBar.svelte.d.ts +5 -0
  19. package/dist/components/BoundRecordDetailDrawer.svelte +5 -3
  20. package/dist/components/CancelTaskButton.svelte +14 -1
  21. package/dist/components/Cascader.svelte +412 -53
  22. package/dist/components/Cascader.svelte.d.ts +7 -1
  23. package/dist/components/ColorPicker.svelte +1 -1
  24. package/dist/components/ColorPicker.svelte.d.ts +1 -1
  25. package/dist/components/ComboboxField.svelte +270 -34
  26. package/dist/components/ComboboxField.svelte.d.ts +4 -0
  27. package/dist/components/CommandPalette.svelte +4 -4
  28. package/dist/components/DashboardView.svelte +73 -7
  29. package/dist/components/DashboardView.svelte.d.ts +3 -1
  30. package/dist/components/DateRangeInput.svelte +8 -1
  31. package/dist/components/DetailWorkspaceAsyncSection.svelte +107 -0
  32. package/dist/components/DetailWorkspaceAsyncSection.svelte.d.ts +36 -0
  33. package/dist/components/DetailWorkspaceSections.svelte +233 -0
  34. package/dist/components/DetailWorkspaceSections.svelte.d.ts +9 -0
  35. package/dist/components/DrawerForm.svelte +78 -9
  36. package/dist/components/DrawerForm.svelte.d.ts +3 -3
  37. package/dist/components/ErrorBoundary.svelte +1 -1
  38. package/dist/components/ErrorComponent.svelte +19 -6
  39. package/dist/components/ErrorComponent.svelte.d.ts +1 -0
  40. package/dist/components/ErrorSummary.svelte +9 -4
  41. package/dist/components/FieldRenderer.svelte +203 -90
  42. package/dist/components/FieldRenderer.svelte.d.ts +16 -1
  43. package/dist/components/FileUpload.svelte +116 -51
  44. package/dist/components/FileUpload.svelte.d.ts +7 -27
  45. package/dist/components/FileUploadField.svelte +170 -0
  46. package/dist/components/FileUploadField.svelte.d.ts +21 -0
  47. package/dist/components/FilterBuilder.svelte +13 -7
  48. package/dist/components/FilterCollectionInput.svelte +9 -7
  49. package/dist/components/FilterCollectionInput.svelte.d.ts +3 -0
  50. package/dist/components/ForgotPasswordPage.svelte +1 -1
  51. package/dist/components/Header.svelte +1 -1
  52. package/dist/components/ImportWizard.svelte +197 -28
  53. package/dist/components/ImportWizard.svelte.d.ts +5 -1
  54. package/dist/components/InferencerPanel.svelte +4 -2
  55. package/dist/components/InfiniteList.svelte +52 -9
  56. package/dist/components/JsonSchemaForm.svelte +237 -42
  57. package/dist/components/JsonSchemaForm.svelte.d.ts +10 -3
  58. package/dist/components/KanbanBoard.svelte +43 -7
  59. package/dist/components/KanbanBoard.svelte.d.ts +2 -2
  60. package/dist/components/Layout.svelte +1 -1
  61. package/dist/components/LoginPage.svelte +1 -1
  62. package/dist/components/MetricStrip.svelte +8 -3
  63. package/dist/components/MetricStrip.svelte.d.ts +1 -0
  64. package/dist/components/ModalForm.svelte +85 -9
  65. package/dist/components/ModalForm.svelte.d.ts +3 -3
  66. package/dist/components/MoneyInput.svelte +5 -3
  67. package/dist/components/MoneyInput.svelte.d.ts +4 -2
  68. package/dist/components/MultiTabKeepAlive.svelte +502 -43
  69. package/dist/components/MultiTabKeepAlive.svelte.d.ts +40 -6
  70. package/dist/components/NotificationsSettings.svelte +10 -10
  71. package/dist/components/NumberInput.svelte +5 -3
  72. package/dist/components/NumberInput.svelte.d.ts +4 -2
  73. package/dist/components/NumericInput.svelte +277 -25
  74. package/dist/components/NumericInput.svelte.d.ts +3 -1
  75. package/dist/components/PercentInput.svelte +5 -3
  76. package/dist/components/PercentInput.svelte.d.ts +4 -2
  77. package/dist/components/PermissionMatrix.svelte +44 -8
  78. package/dist/components/PermissionMatrix.svelte.d.ts +1 -0
  79. package/dist/components/ProfilePage.svelte +38 -13
  80. package/dist/components/RadioGroup.svelte +203 -4
  81. package/dist/components/RadioGroup.svelte.d.ts +1 -1
  82. package/dist/components/Rate.svelte +3 -3
  83. package/dist/components/Rate.svelte.d.ts +2 -2
  84. package/dist/components/RecordDetailDrawer.svelte +1 -1
  85. package/dist/components/RecordDetailDrawer.svelte.d.ts +1 -1
  86. package/dist/components/RegisterPage.svelte +1 -1
  87. package/dist/components/RelationPicker.svelte +39 -11
  88. package/dist/components/ResourceAccessGuard.svelte +7 -2
  89. package/dist/components/RetryTaskButton.svelte +14 -1
  90. package/dist/components/RolesSettings.svelte +74 -28
  91. package/dist/components/SecuritySettings.svelte +66 -51
  92. package/dist/components/SegmentedControl.svelte +4 -2
  93. package/dist/components/SegmentedControl.svelte.d.ts +1 -1
  94. package/dist/components/Sidebar.svelte +1 -1
  95. package/dist/components/StepsForm.svelte +62 -10
  96. package/dist/components/StepsForm.svelte.d.ts +4 -2
  97. package/dist/components/SvadminLogo.svelte +1 -0
  98. package/dist/components/SvarDataGrid.svelte +42 -24
  99. package/dist/components/SvarDataGrid.svelte.d.ts +10 -10
  100. package/dist/components/SvarResourceRead.svelte +2 -0
  101. package/dist/components/SvarResourceTableView.svelte +4 -2
  102. package/dist/components/TagsInput.svelte +3 -3
  103. package/dist/components/TagsInput.svelte.d.ts +2 -2
  104. package/dist/components/TaskDetails.svelte +22 -16
  105. package/dist/components/TaskDetails.svelte.d.ts +1 -1
  106. package/dist/components/TaskList.svelte +57 -15
  107. package/dist/components/TaskQueueDrawer.svelte +54 -34
  108. package/dist/components/Toast.svelte +13 -2
  109. package/dist/components/Transfer.svelte +67 -5
  110. package/dist/components/Transfer.svelte.d.ts +4 -0
  111. package/dist/components/TreeSelect.svelte +337 -77
  112. package/dist/components/TreeSelect.svelte.d.ts +4 -0
  113. package/dist/components/TreeTable.svelte +6 -1
  114. package/dist/components/TwoFactorAuthPage.svelte +8 -4
  115. package/dist/components/UpdatePasswordPage.svelte +1 -1
  116. package/dist/components/VersionDiffViewer.svelte +9 -7
  117. package/dist/components/account/CompanyProfilePage.svelte +13 -12
  118. package/dist/components/account/MemberDirectory.svelte +14 -10
  119. package/dist/components/account/SettingsEnterprisePage.svelte +18 -18
  120. package/dist/components/content/AuditTimeline.svelte +8 -6
  121. package/dist/components/content/DataState.svelte +1 -1
  122. package/dist/components/content/FileList.svelte +44 -13
  123. package/dist/components/content/FileList.svelte.d.ts +4 -2
  124. package/dist/components/content/FileListRow.svelte +87 -0
  125. package/dist/components/content/FileListRow.svelte.d.ts +11 -0
  126. package/dist/components/content/ImportDropzone.svelte +109 -12
  127. package/dist/components/content/ImportDropzone.svelte.d.ts +1 -0
  128. package/dist/components/content/IntegrationCard.svelte +3 -3
  129. package/dist/components/content/IntegrationCard.svelte.d.ts +2 -0
  130. package/dist/components/content/MediaThumbnail.svelte +6 -2
  131. package/dist/components/detail-workspace.d.ts +60 -0
  132. package/dist/components/detail-workspace.js +3 -0
  133. package/dist/components/enterprise/json-schema-form.d.ts +1 -1
  134. package/dist/components/enterprise/recursive-schema-form.d.ts +15 -1
  135. package/dist/components/enterprise/recursive-schema-form.js +288 -14
  136. package/dist/components/enterprise/schema-form-references.d.ts +14 -0
  137. package/dist/components/enterprise/schema-form-references.js +316 -0
  138. package/dist/components/fields/ArrayField.svelte +178 -14
  139. package/dist/components/fields/ArrayField.svelte.d.ts +17 -3
  140. package/dist/components/fields/array-field-metadata.d.ts +4 -0
  141. package/dist/components/fields/array-field-metadata.js +36 -0
  142. package/dist/components/file-upload-reference.d.ts +11 -0
  143. package/dist/components/file-upload-reference.js +50 -0
  144. package/dist/components/file-upload-types.d.ts +32 -0
  145. package/dist/components/file-upload-types.js +1 -0
  146. package/dist/components/form-contract.upload-type-fixture.svelte +108 -0
  147. package/dist/components/form-contract.upload-type-fixture.svelte.d.ts +18 -0
  148. package/dist/components/json-schema-form-state.d.ts +19 -0
  149. package/dist/components/list-deletion.svelte.js +6 -1
  150. package/dist/components/list-state.svelte.d.ts +12 -1
  151. package/dist/components/list-state.svelte.js +56 -16
  152. package/dist/components/numeric-format.d.ts +44 -0
  153. package/dist/components/numeric-format.js +198 -0
  154. package/dist/components/record-detail.svelte.js +2 -0
  155. package/dist/components/saved-list-views.d.ts +9 -2
  156. package/dist/components/saved-list-views.js +42 -11
  157. package/dist/components/saved-list-views.svelte.d.ts +3 -0
  158. package/dist/components/saved-list-views.svelte.js +56 -7
  159. package/dist/components/task-action.svelte.d.ts +1 -0
  160. package/dist/components/task-action.svelte.js +55 -3
  161. package/dist/components/task-utils.d.ts +1 -0
  162. package/dist/components/task-utils.js +18 -0
  163. package/dist/components/ui/input/input.svelte +1 -1
  164. package/dist/components/ui/input/input.svelte.d.ts +1 -1
  165. package/dist/components/ui/sheet/sheet.svelte +4 -1
  166. package/dist/components/version-history.d.ts +14 -0
  167. package/dist/components/version-history.js +32 -0
  168. package/dist/components/workspace-tab-state.d.ts +78 -0
  169. package/dist/components/workspace-tab-state.js +587 -0
  170. package/dist/index.d.ts +13 -1
  171. package/dist/index.js +4 -0
  172. package/package.json +5 -5
@@ -2,8 +2,14 @@ import { readSchemaForm, validateSchemaForm } from './json-schema-form.js';
2
2
  import { SCHEMA_FORM_LIMITS, schemaFormSnapshot } from '../json-schema-form-state.js';
3
3
  const annotationKeys = ['$schema', '$id', '$comment', 'title', 'description', 'type', 'default', 'readOnly', 'writeOnly', 'deprecated', 'examples'];
4
4
  const objectKeys = new Set([...annotationKeys, 'properties', 'required', 'additionalProperties']);
5
+ const rootKeys = new Set([...objectKeys, '$defs']);
5
6
  const arrayKeys = new Set([...annotationKeys, 'items', 'minItems', 'maxItems']);
7
+ const oneOfKeys = new Set([...annotationKeys, 'oneOf', 'discriminator']);
8
+ const rootOneOfKeys = new Set([...rootKeys, 'oneOf', 'discriminator']);
9
+ const MAX_ONEOF_BRANCHES = 256;
6
10
  const unsafe = new Set(['__proto__', 'prototype', 'constructor']);
11
+ const internalRef = /^#\/\$defs\/([^/]+)$/u;
12
+ const sameScalar = (left, right) => Object.is(left, right);
7
13
  export const schemaFormPointer = (path) => path.map(key => '/' + key.replaceAll('~', '~0').replaceAll('/', '~1')).join('');
8
14
  function record(value) {
9
15
  return value !== null && typeof value === 'object' && !Array.isArray(value)
@@ -15,6 +21,27 @@ function dataRecord(value) {
15
21
  return descriptor !== undefined && 'value' in descriptor;
16
22
  });
17
23
  }
24
+ /** Decode one RFC6901 reference token; a bare or malformed `~` escape is rejected. */
25
+ function decodePointerSegment(segment) {
26
+ if (!segment)
27
+ return undefined;
28
+ let result = '';
29
+ for (let index = 0; index < segment.length; index++) {
30
+ const character = segment[index];
31
+ if (character !== '~') {
32
+ result += character;
33
+ continue;
34
+ }
35
+ const escape = segment[++index];
36
+ if (escape === '0')
37
+ result += '~';
38
+ else if (escape === '1')
39
+ result += '/';
40
+ else
41
+ return undefined;
42
+ }
43
+ return result;
44
+ }
18
45
  /** Compile bounded objects/arrays, delegating scalar assertions to the reviewed leaf validator.
19
46
  * Unsupported assertions fail closed; this is deliberately not a complete JSON Schema engine.
20
47
  * TypeBox's symbol annotations are not JSON assertion keywords and are not executed.
@@ -22,34 +49,136 @@ function dataRecord(value) {
22
49
  export function readRecursiveSchemaForm(schema) {
23
50
  const issues = [];
24
51
  const active = new Set();
52
+ let definitions;
25
53
  let visited = 0;
54
+ let references = 0;
26
55
  const issue = (path, code) => {
27
56
  if (issues.length < 128)
28
57
  issues.push({ path, code });
29
58
  };
30
- function read(raw, path, depth, root = false) {
59
+ /** `oneOf` 只接受显式 discriminator;分支 selector 值必须唯一,绝不尝试首个匹配分支。 */
60
+ function readOneOf(raw, path, depth, root) {
61
+ // 根级 oneOf 与字段级共享同一显式 discriminator 契约,由表单根部渲染选择器。
62
+ const allowed = root ? rootOneOfKeys : oneOfKeys;
63
+ for (const key of Object.keys(raw))
64
+ if (!allowed.has(key))
65
+ issue(path + schemaFormPointer([key]), 'unsupported-schema');
66
+ if (raw['type'] !== undefined && raw['type'] !== 'object') {
67
+ issue(path + '/type', 'oneOf-invalid');
68
+ return;
69
+ }
70
+ const branchesRaw = raw['oneOf'];
71
+ if (!Array.isArray(branchesRaw) || branchesRaw.length === 0 || branchesRaw.length > MAX_ONEOF_BRANCHES
72
+ || Reflect.ownKeys(branchesRaw).some(key => typeof key === 'symbol')
73
+ || Object.keys(branchesRaw).some(key => !/^(0|[1-9]\d*)$/u.test(key) || Number(key) >= branchesRaw.length)) {
74
+ issue(path + '/oneOf', 'oneOf-invalid');
75
+ return;
76
+ }
77
+ const discriminator = raw['discriminator'];
78
+ if (discriminator === undefined) {
79
+ issue(path + '/oneOf', 'oneOf-unsupported');
80
+ return;
81
+ }
82
+ // discriminator 与根 Schema 一样按数据属性读取;accessor 在编译期拒绝且不执行。
83
+ if (!dataRecord(discriminator)) {
84
+ issue(path + '/discriminator', 'oneOf-invalid');
85
+ return;
86
+ }
87
+ const discriminatorKeys = Object.keys(discriminator);
88
+ if (discriminatorKeys.length !== 1 || discriminatorKeys[0] !== 'propertyName') {
89
+ issue(path + '/discriminator', 'oneOf-unsupported');
90
+ return;
91
+ }
92
+ const propertyNameDescriptor = Object.getOwnPropertyDescriptor(discriminator, 'propertyName');
93
+ const propertyName = propertyNameDescriptor && 'value' in propertyNameDescriptor ? propertyNameDescriptor.value : undefined;
94
+ if (typeof propertyName !== 'string' || !propertyName || unsafe.has(propertyName)) {
95
+ issue(path + '/discriminator' + schemaFormPointer(['propertyName']), 'oneOf-invalid');
96
+ return;
97
+ }
98
+ const branches = [];
99
+ const values = [];
100
+ const seen = new Map();
101
+ let ambiguous = false;
102
+ for (let index = 0; index < branchesRaw.length; index++) {
103
+ const branchPath = path + '/oneOf/' + index;
104
+ const descriptor = Object.getOwnPropertyDescriptor(branchesRaw, String(index));
105
+ if (!descriptor || !('value' in descriptor)) {
106
+ issue(branchPath, 'oneOf-invalid');
107
+ continue;
108
+ }
109
+ const branch = read(descriptor.value, branchPath, depth + 1);
110
+ if (!branch)
111
+ continue;
112
+ if (branch.kind !== 'object') {
113
+ issue(branchPath, 'oneOf-invalid');
114
+ continue;
115
+ }
116
+ const selector = branch.properties[propertyName];
117
+ const field = selector?.kind === 'scalar' ? selector.model.fields[0] : undefined;
118
+ const choices = field?.choices;
119
+ if (selector?.kind !== 'scalar' || !field || !choices?.length) {
120
+ issue(branchPath + schemaFormPointer([propertyName]), 'oneOf-invalid');
121
+ continue;
122
+ }
123
+ for (const value of choices) {
124
+ const key = value === null ? 'null' : `${typeof value}:${String(value)}`;
125
+ const existing = seen.get(key);
126
+ if (existing !== undefined && existing !== index)
127
+ ambiguous = true;
128
+ else
129
+ seen.set(key, index);
130
+ }
131
+ branches.push(branch);
132
+ values.push([...choices]);
133
+ }
134
+ if (ambiguous)
135
+ issue(path + '/oneOf', 'oneOf-ambiguous');
136
+ if (!branches.length || branches.length !== branchesRaw.length)
137
+ return;
138
+ return { kind: 'oneOf', branches, propertyName, values, nullable: false };
139
+ }
140
+ function read(raw, path, depth, root = false, refDepth = 0) {
31
141
  if (++visited > SCHEMA_FORM_LIMITS.nodes || depth > SCHEMA_FORM_LIMITS.depth || !dataRecord(raw) || active.has(raw)) {
32
142
  issue(path, 'invalid-schema');
33
143
  return;
34
144
  }
35
145
  active.add(raw);
36
146
  try {
37
- let type = raw['type'];
38
- let nullable = false;
39
- if (Array.isArray(type)) {
40
- const concrete = type.filter(value => value !== 'null');
41
- if (type.length !== 2 || concrete.length !== 1 || !type.includes('null')) {
147
+ if (Object.hasOwn(raw, '$ref')) {
148
+ if (Reflect.ownKeys(raw).length !== 1) {
42
149
  issue(path, 'unsupported-schema');
43
150
  return;
44
151
  }
45
- nullable = true;
46
- type = concrete[0];
152
+ const reference = raw['$ref'];
153
+ if (typeof reference !== 'string') {
154
+ issue(path, 'ref-invalid');
155
+ return;
156
+ }
157
+ const targetMatch = internalRef.exec(reference);
158
+ const targetName = targetMatch?.[1];
159
+ const name = typeof targetName === 'string' ? decodePointerSegment(targetName) : undefined;
160
+ if (name === undefined || unsafe.has(name) || !definitions || !Object.hasOwn(definitions, name)) {
161
+ issue(path, 'ref-invalid');
162
+ return;
163
+ }
164
+ if (++references > 256 || refDepth + 1 > SCHEMA_FORM_LIMITS.depth) {
165
+ issue(path, 'ref-limit');
166
+ return;
167
+ }
168
+ const target = definitions[name];
169
+ if (target !== null && typeof target === 'object' && active.has(target)) {
170
+ issue(path, 'ref-cycle');
171
+ return;
172
+ }
173
+ // A reference is transparent: the target occupies the referencing node's depth.
174
+ return read(target, path, depth, false, refDepth + 1);
47
175
  }
48
- if (type === undefined && (root || Object.hasOwn(raw, 'properties')))
49
- type = 'object';
50
- if (root && type !== 'object') {
51
- issue(path, 'unsupported-schema');
52
- return;
176
+ if (root && Object.hasOwn(raw, '$defs')) {
177
+ const candidates = raw['$defs'];
178
+ if (!dataRecord(candidates) || Object.keys(candidates).length > 256)
179
+ issue(path + '/$defs', 'invalid-schema');
180
+ else
181
+ definitions = candidates;
53
182
  }
54
183
  for (const key of ['readOnly', 'writeOnly', 'deprecated']) {
55
184
  if (raw[key] !== undefined && typeof raw[key] !== 'boolean')
@@ -63,13 +192,32 @@ export function readRecursiveSchemaForm(schema) {
63
192
  issue(path + '/default', 'invalid-schema');
64
193
  }
65
194
  }
195
+ if (Object.hasOwn(raw, 'oneOf'))
196
+ return readOneOf(raw, path, depth, root);
197
+ let type = raw['type'];
198
+ let nullable = false;
199
+ if (Array.isArray(type)) {
200
+ const concrete = type.filter(value => value !== 'null');
201
+ if (type.length !== 2 || concrete.length !== 1 || !type.includes('null')) {
202
+ issue(path, 'unsupported-schema');
203
+ return;
204
+ }
205
+ nullable = true;
206
+ type = concrete[0];
207
+ }
208
+ if (type === undefined && (root || Object.hasOwn(raw, 'properties')))
209
+ type = 'object';
210
+ if (root && type !== 'object') {
211
+ issue(path, 'unsupported-schema');
212
+ return;
213
+ }
66
214
  if (type !== 'object' && type !== 'array') {
67
215
  const model = readSchemaForm({ type: 'object', properties: { value: raw } });
68
216
  for (const failure of model.issues)
69
217
  issue(path + failure.path.replace(/^\/value(?=\/|$)/u, ''), failure.code);
70
218
  return { kind: 'scalar', model };
71
219
  }
72
- const allowed = type === 'object' ? objectKeys : arrayKeys;
220
+ const allowed = type === 'object' ? (root ? rootKeys : objectKeys) : arrayKeys;
73
221
  for (const key of Object.keys(raw))
74
222
  if (!allowed.has(key))
75
223
  issue(path + '/' + key, 'unsupported-schema');
@@ -131,6 +279,117 @@ export function readRecursiveSchemaForm(schema) {
131
279
  }
132
280
  return { node, issues };
133
281
  }
282
+ function dataValue(current, key) {
283
+ if (!record(current))
284
+ return undefined;
285
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
286
+ return descriptor !== undefined && 'value' in descriptor ? descriptor.value : undefined;
287
+ }
288
+ /** Return the unique branch selected by data; missing or overlapping values never fall back to first match. */
289
+ export function selectRecursiveOneOfBranch(node, current) {
290
+ if (node.kind !== 'oneOf')
291
+ return 'oneOf-unselected';
292
+ const value = dataValue(current, node.propertyName);
293
+ let found = -1;
294
+ for (const [index, choices] of node.values.entries()) {
295
+ if (!choices.some(choice => sameScalar(choice, value)))
296
+ continue;
297
+ if (found >= 0)
298
+ return 'oneOf-ambiguous';
299
+ found = index;
300
+ }
301
+ return found >= 0 ? found : 'oneOf-unselected';
302
+ }
303
+ /** Switch the explicit discriminator and drop keys that exist only on unselected branches. */
304
+ export function switchRecursiveOneOfBranch(node, current, index, preferred) {
305
+ if (node.kind !== 'oneOf' || !Number.isSafeInteger(index) || index < 0 || index >= node.branches.length)
306
+ return undefined;
307
+ if (!record(current))
308
+ return undefined;
309
+ const choices = node.values[index];
310
+ const branch = node.branches[index];
311
+ if (!choices?.length || !branch || branch.kind !== 'object')
312
+ return undefined;
313
+ const selectedValue = preferred !== undefined && choices.some(choice => sameScalar(choice, preferred)) ? preferred : choices[0];
314
+ if (selectedValue === undefined)
315
+ return undefined;
316
+ const selectedKeys = new Set(Object.keys(branch.properties));
317
+ const stale = new Set();
318
+ for (const [otherIndex, other] of node.branches.entries()) {
319
+ if (otherIndex === index || other.kind !== 'object')
320
+ continue;
321
+ for (const key of Object.keys(other.properties))
322
+ if (!selectedKeys.has(key))
323
+ stale.add(key);
324
+ }
325
+ const result = {};
326
+ for (const key of Object.keys(current)) {
327
+ if (stale.has(key))
328
+ continue;
329
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
330
+ if (descriptor !== undefined && 'value' in descriptor)
331
+ result[key] = descriptor.value;
332
+ }
333
+ result[node.propertyName] = selectedValue;
334
+ return result;
335
+ }
336
+ function pruneNodeValue(node, payload) {
337
+ let nodes = 0;
338
+ function prune(currentNode, current, depth) {
339
+ if (++nodes > SCHEMA_FORM_LIMITS.nodes || depth > SCHEMA_FORM_LIMITS.depth)
340
+ throw new Error('Form value exceeds limits');
341
+ if (currentNode.kind === 'scalar')
342
+ return current;
343
+ if (currentNode.kind === 'array') {
344
+ return Array.isArray(current) ? current.map(item => prune(currentNode.item, item, depth + 1)) : current;
345
+ }
346
+ if (currentNode.kind === 'object') {
347
+ if (!record(current))
348
+ return current;
349
+ const result = {};
350
+ for (const key of Object.keys(current)) {
351
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
352
+ if (!descriptor || !('value' in descriptor))
353
+ continue;
354
+ const child = currentNode.properties[key];
355
+ result[key] = child ? prune(child, descriptor.value, depth + 1) : descriptor.value;
356
+ }
357
+ return result;
358
+ }
359
+ const selected = selectRecursiveOneOfBranch(currentNode, current);
360
+ if (typeof selected !== 'number')
361
+ return current;
362
+ const branch = currentNode.branches[selected];
363
+ if (!branch || branch.kind !== 'object' || !record(current))
364
+ return current;
365
+ const selectedKeys = new Set(Object.keys(branch.properties));
366
+ const stale = new Set();
367
+ for (const [index, other] of currentNode.branches.entries()) {
368
+ if (index === selected || other.kind !== 'object')
369
+ continue;
370
+ for (const key of Object.keys(other.properties))
371
+ if (!selectedKeys.has(key))
372
+ stale.add(key);
373
+ }
374
+ const result = {};
375
+ for (const key of Object.keys(current)) {
376
+ if (stale.has(key))
377
+ continue;
378
+ const descriptor = Object.getOwnPropertyDescriptor(current, key);
379
+ if (!descriptor || !('value' in descriptor))
380
+ continue;
381
+ const child = branch.properties[key];
382
+ result[key] = child ? prune(child, descriptor.value, depth + 1) : descriptor.value;
383
+ }
384
+ return result;
385
+ }
386
+ return prune(node, payload, 0);
387
+ }
388
+ /** Return a detached payload with stale unselected `oneOf` branch keys removed. */
389
+ export function pruneRecursiveSchemaFormValue(model, value) {
390
+ const payload = schemaFormSnapshot(value);
391
+ return model.node ? pruneNodeValue(model.node, payload) : payload;
392
+ }
134
393
  /** Validate a detached JSON payload without defaults, coercion, cleaning or partial acceptance. */
135
394
  export function validateRecursiveSchemaForm(model, value) {
136
395
  if (model.issues.length || !model.node)
@@ -143,6 +402,8 @@ export function validateRecursiveSchemaForm(model, value) {
143
402
  let payload;
144
403
  try {
145
404
  payload = schemaFormSnapshot(value);
405
+ if (model.node)
406
+ payload = pruneNodeValue(model.node, payload);
146
407
  }
147
408
  catch {
148
409
  return [{ path: '', code: 'invalid-value' }];
@@ -158,6 +419,19 @@ export function validateRecursiveSchemaForm(model, value) {
158
419
  issue(path, 'required');
159
420
  return;
160
421
  }
422
+ if (node.kind === 'oneOf') {
423
+ const selected = selectRecursiveOneOfBranch(node, current);
424
+ if (selected === 'oneOf-ambiguous') {
425
+ issue(path + '/oneOf', 'oneOf-ambiguous');
426
+ return;
427
+ }
428
+ if (selected === 'oneOf-unselected') {
429
+ issue(path + schemaFormPointer([node.propertyName]), 'oneOf-unselected');
430
+ return;
431
+ }
432
+ visit(node.branches[selected], current, path, true, depth + 1);
433
+ return;
434
+ }
161
435
  if (node.kind === 'scalar') {
162
436
  for (const failure of validateSchemaForm(node.model, { value: current }))
163
437
  issue(path + failure.path.replace(/^\/value(?=\/|$)/u, ''), failure.code);
@@ -0,0 +1,14 @@
1
+ import type { SchemaFormIssue } from './json-schema-form.js';
2
+ /** 宿主已经取得的普通 Schema 文档;解析器不会为其执行任何加载动作。 */
3
+ export type SchemaFormReferenceDocuments = Record<string, unknown>;
4
+ /** 展开后的 Schema。渲染、默认值和校验都必须使用同一份结果。 */
5
+ export interface ResolvedSchemaForm<T> {
6
+ schema: T | undefined;
7
+ issues: SchemaFormIssue[];
8
+ }
9
+ /**
10
+ * 解析受控的本地外部引用子集。
11
+ * 外部引用必须命中宿主传入的精确 URI 映射,只允许根文档或 `/$defs/<name>`;
12
+ * 不执行网络、fetch/XHR、动态 import、storage 或环境读取。
13
+ */
14
+ export declare function resolveSchemaFormReferences<T>(schema: T, referenceDocuments?: SchemaFormReferenceDocuments): ResolvedSchemaForm<T>;