@svadmin/ui 0.73.0 → 0.75.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 (303) hide show
  1. package/README.md +53 -28
  2. package/dist/app.css +3733 -4119
  3. package/dist/app.theme.css +3733 -4119
  4. package/dist/component-registry.d.ts +14977 -0
  5. package/dist/component-registry.js +216 -0
  6. package/dist/components/AboutSettings.svelte +7 -1
  7. package/dist/components/AboutSettings.svelte.d.ts +4 -1
  8. package/dist/components/ActivityFeed.svelte +9 -3
  9. package/dist/components/ActivityFeed.svelte.d.ts +3 -0
  10. package/dist/components/AdminApp.svelte +24 -15
  11. package/dist/components/AdminApp.svelte.d.ts +6 -0
  12. package/dist/components/ApiSettings.svelte +41 -5
  13. package/dist/components/ApprovalCenter.svelte +283 -0
  14. package/dist/components/ApprovalCenter.svelte.d.ts +10 -0
  15. package/dist/components/AuditLogViewer.svelte +3 -1
  16. package/dist/components/AutoForm.svelte +33 -3
  17. package/dist/components/AutoForm.svelte.d.ts +1 -0
  18. package/dist/components/AutoTable.svelte +610 -47
  19. package/dist/components/AutoTable.svelte.d.ts +17 -1
  20. package/dist/components/BoundRecordDetailDrawer.svelte +9 -0
  21. package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +2 -0
  22. package/dist/components/Breadcrumbs.svelte +11 -1
  23. package/dist/components/CancelTaskButton.svelte +12 -42
  24. package/dist/components/ColorPicker.svelte +73 -0
  25. package/dist/components/ColorPicker.svelte.d.ts +15 -0
  26. package/dist/components/ComboboxField.svelte +114 -27
  27. package/dist/components/ComboboxField.svelte.d.ts +1 -0
  28. package/dist/components/DashboardView.svelte +99 -0
  29. package/dist/components/DashboardView.svelte.d.ts +14 -0
  30. package/dist/components/DatePicker.svelte +10 -0
  31. package/dist/components/DatePicker.svelte.d.ts +6 -0
  32. package/dist/components/DateRangeInput.svelte +85 -5
  33. package/dist/components/DateRangeInput.svelte.d.ts +10 -0
  34. package/dist/components/DateRangePicker.svelte +10 -0
  35. package/dist/components/DateRangePicker.svelte.d.ts +6 -0
  36. package/dist/components/DateTimeInput.svelte +85 -16
  37. package/dist/components/DateTimeInput.svelte.d.ts +19 -11
  38. package/dist/components/DateTimePicker.svelte +10 -0
  39. package/dist/components/DateTimePicker.svelte.d.ts +6 -0
  40. package/dist/components/DevTools.svelte +34 -26
  41. package/dist/components/DevframePanel.svelte +123 -0
  42. package/dist/components/DevframePanel.svelte.d.ts +3 -0
  43. package/dist/components/ErrorSummary.svelte +52 -0
  44. package/dist/components/ErrorSummary.svelte.d.ts +14 -0
  45. package/dist/components/FieldRenderer.svelte +82 -81
  46. package/dist/components/FileUpload.svelte +214 -52
  47. package/dist/components/FileUpload.svelte.d.ts +11 -0
  48. package/dist/components/FilterBuilder.svelte +88 -66
  49. package/dist/components/FilterCollectionInput.svelte +80 -0
  50. package/dist/components/FilterCollectionInput.svelte.d.ts +10 -0
  51. package/dist/components/GanttChart.svelte +66 -14
  52. package/dist/components/GanttChart.svelte.d.ts +6 -0
  53. package/dist/components/Header.svelte +18 -2
  54. package/dist/components/ImportWizard.svelte +339 -17
  55. package/dist/components/ImportWizard.svelte.d.ts +11 -0
  56. package/dist/components/JsonSchemaForm.svelte +120 -52
  57. package/dist/components/JsonSchemaForm.svelte.d.ts +7 -1
  58. package/dist/components/KanbanBoard.svelte +195 -40
  59. package/dist/components/KanbanBoard.svelte.d.ts +9 -2
  60. package/dist/components/Layout.svelte +4 -2
  61. package/dist/components/LazyAutoForm.svelte +28 -0
  62. package/dist/components/LazyAutoForm.svelte.d.ts +8 -0
  63. package/dist/components/LazyAutoTable.svelte +28 -0
  64. package/dist/components/LazyAutoTable.svelte.d.ts +8 -0
  65. package/dist/components/LazyPage.svelte +3 -3
  66. package/dist/components/LazyPage.svelte.d.ts +2 -2
  67. package/dist/components/LazySettingsPage.svelte +28 -0
  68. package/dist/components/LazySettingsPage.svelte.d.ts +8 -0
  69. package/dist/components/LazyShowPage.svelte +28 -0
  70. package/dist/components/LazyShowPage.svelte.d.ts +8 -0
  71. package/dist/components/ListPage.svelte +5 -1
  72. package/dist/components/ListPage.svelte.d.ts +3 -0
  73. package/dist/components/LoginPage.svelte +1 -1
  74. package/dist/components/MultiTabKeepAlive.svelte +11 -6
  75. package/dist/components/NotificationsSettings.svelte +69 -2
  76. package/dist/components/NotificationsSettings.svelte.d.ts +25 -1
  77. package/dist/components/PasswordInput.svelte +3 -3
  78. package/dist/components/PasswordInput.svelte.d.ts +2 -1
  79. package/dist/components/PermissionMatrix.svelte +20 -1
  80. package/dist/components/PivotExportButton.svelte +157 -0
  81. package/dist/components/PivotExportButton.svelte.d.ts +16 -0
  82. package/dist/components/PivotTable.svelte +229 -74
  83. package/dist/components/PivotTable.svelte.d.ts +19 -2
  84. package/dist/components/RadioGroup.svelte +121 -0
  85. package/dist/components/RadioGroup.svelte.d.ts +43 -0
  86. package/dist/components/RangeSlider.svelte +127 -0
  87. package/dist/components/RangeSlider.svelte.d.ts +22 -0
  88. package/dist/components/Rate.svelte +121 -0
  89. package/dist/components/Rate.svelte.d.ts +16 -0
  90. package/dist/components/RecordDetailDrawer.svelte +4 -2
  91. package/dist/components/RecordDetailDrawer.svelte.d.ts +2 -0
  92. package/dist/components/RelationPicker.svelte +341 -0
  93. package/dist/components/RelationPicker.svelte.d.ts +26 -0
  94. package/dist/components/ResourceOperationsPage.svelte +23 -19
  95. package/dist/components/ResourceOperationsPage.svelte.d.ts +2 -2
  96. package/dist/components/RetryTaskButton.svelte +12 -42
  97. package/dist/components/SecuritySettings.svelte +29 -5
  98. package/dist/components/SegmentedControl.svelte +145 -0
  99. package/dist/components/SegmentedControl.svelte.d.ts +38 -0
  100. package/dist/components/SettingsPage.svelte +3 -1
  101. package/dist/components/SettingsPage.svelte.d.ts +1 -0
  102. package/dist/components/ShowPage.svelte +18 -3
  103. package/dist/components/ShowPage.svelte.d.ts +3 -0
  104. package/dist/components/Sidebar.svelte +2 -2
  105. package/dist/components/Slider.svelte +100 -0
  106. package/dist/components/Slider.svelte.d.ts +19 -0
  107. package/dist/components/SpreadsheetView.svelte +1213 -95
  108. package/dist/components/SpreadsheetView.svelte.d.ts +19 -0
  109. package/dist/components/TagsInput.svelte +178 -0
  110. package/dist/components/TagsInput.svelte.d.ts +17 -0
  111. package/dist/components/TimePicker.svelte +10 -0
  112. package/dist/components/TimePicker.svelte.d.ts +6 -0
  113. package/dist/components/TooltipButton.svelte +3 -4
  114. package/dist/components/TreeSelect.svelte +267 -53
  115. package/dist/components/TreeSelect.svelte.d.ts +3 -0
  116. package/dist/components/TreeTable.svelte +484 -82
  117. package/dist/components/TreeTable.svelte.d.ts +24 -1
  118. package/dist/components/TwoFactorAuthPage.svelte +85 -48
  119. package/dist/components/TwoFactorAuthPage.svelte.d.ts +12 -1
  120. package/dist/components/VirtualTable.svelte +116 -20
  121. package/dist/components/VirtualTable.svelte.d.ts +5 -0
  122. package/dist/components/account/CompanyProfilePage.svelte +74 -38
  123. package/dist/components/account/GetStartedPage.svelte +2 -1
  124. package/dist/components/account/ImportMembersPage.svelte +22 -167
  125. package/dist/components/account/ImportMembersPage.svelte.d.ts +4 -1
  126. package/dist/components/account/MemberDirectory.svelte +156 -0
  127. package/dist/components/account/MemberDirectory.svelte.d.ts +19 -0
  128. package/dist/components/account/MembersStarterPage.svelte +9 -55
  129. package/dist/components/account/MembersStarterPage.svelte.d.ts +5 -1
  130. package/dist/components/account/SecurityLogPage.svelte +4 -1
  131. package/dist/components/account/SettingsPlainPage.svelte +7 -38
  132. package/dist/components/account/SettingsPlainPage.svelte.d.ts +4 -1
  133. package/dist/components/account/SettingsSidebarPage.svelte +7 -20
  134. package/dist/components/account/TeamMembersPage.svelte +4 -20
  135. package/dist/components/account/TeamMembersPage.svelte.d.ts +5 -1
  136. package/dist/components/account/UserProfilePage.svelte +3 -25
  137. package/dist/components/account/UserProfilePage.svelte.d.ts +17 -2
  138. package/dist/components/buttons/ExportButton.svelte +56 -12
  139. package/dist/components/buttons/ExportButton.svelte.d.ts +11 -0
  140. package/dist/components/content/ContentPageHeader.svelte +1 -1
  141. package/dist/components/content/ContentPageShell.svelte +1 -1
  142. package/dist/components/content/MetricBlock.svelte +1 -1
  143. package/dist/components/content/PageToolbar.svelte +1 -1
  144. package/dist/components/content/ReferenceNotice.svelte +5 -0
  145. package/dist/components/content/ReferenceNotice.svelte.d.ts +18 -0
  146. package/dist/components/content/SectionHeader.svelte +1 -1
  147. package/dist/components/content/SettingsFieldRow.svelte +1 -1
  148. package/dist/components/content/SettingsGroup.svelte +1 -1
  149. package/dist/components/content/StatusBadge.svelte +1 -1
  150. package/dist/components/content/WorkspaceLayout.svelte +1 -1
  151. package/dist/components/export-reference.d.ts +2 -0
  152. package/dist/components/export-reference.js +9 -0
  153. package/dist/components/fieldComponentMap.js +2 -5
  154. package/dist/components/fields/DateField.svelte +12 -13
  155. package/dist/components/fields/MarkdownField.svelte +14 -5
  156. package/dist/components/fields/date-display.d.ts +6 -0
  157. package/dist/components/fields/date-display.js +24 -0
  158. package/dist/components/network/TeamCrewTablePage.svelte +6 -1
  159. package/dist/components/network/UserCardsNFTPage.svelte +2 -0
  160. package/dist/components/profile/PublicProfilePage.svelte +2 -0
  161. package/dist/components/saved-list-views.d.ts +74 -1
  162. package/dist/components/saved-list-views.js +191 -3
  163. package/dist/components/table-contract.d.ts +12 -1
  164. package/dist/components/task-action.svelte.d.ts +13 -0
  165. package/dist/components/task-action.svelte.js +73 -0
  166. package/dist/components/ui/DesignSystemShowcase.svelte +164 -0
  167. package/dist/components/ui/DesignSystemShowcase.svelte.d.ts +3 -0
  168. package/dist/components/ui/badge/badge-variants.d.ts +3 -2
  169. package/dist/components/ui/badge/badge-variants.js +2 -3
  170. package/dist/components/ui/badge/badge.svelte +3 -0
  171. package/dist/components/ui/badge/badge.svelte.d.ts +1 -0
  172. package/dist/components/ui/button/button-variants.d.ts +5 -4
  173. package/dist/components/ui/button/button-variants.js +7 -5
  174. package/dist/components/ui/checkbox/checkbox.svelte +5 -1
  175. package/dist/components/ui/checkbox/checkbox.svelte.d.ts +5 -1
  176. package/dist/components/ui/dropdown-menu/dropdown-menu-trigger.svelte +1 -2
  177. package/dist/components/ui/input/input.svelte +7 -2
  178. package/dist/components/ui/input/input.svelte.d.ts +4 -1
  179. package/dist/components/ui/select/select.svelte +4 -1
  180. package/dist/components/ui/select/select.svelte.d.ts +2 -1
  181. package/dist/components/ui/textarea/textarea.svelte +2 -2
  182. package/dist/components.css +33 -2
  183. package/dist/date-input-policy.d.ts +4 -0
  184. package/dist/date-input-policy.js +35 -0
  185. package/dist/design-system-components.d.ts +100 -0
  186. package/dist/design-system-components.js +101 -0
  187. package/dist/design-system.js +1 -1
  188. package/dist/devtools-bridge.d.ts +80 -0
  189. package/dist/devtools-bridge.js +92 -0
  190. package/dist/editor-config.svelte.d.ts +4 -2
  191. package/dist/index.d.ts +37 -2
  192. package/dist/index.js +23 -2
  193. package/dist/recipes.d.ts +960 -4
  194. package/dist/recipes.js +264 -4
  195. package/dist/tree-table-model.d.ts +24 -0
  196. package/dist/tree-table-model.js +111 -0
  197. package/dist/types.d.ts +4 -4
  198. package/package.json +22 -10
  199. package/dist/styled-system/css/conditions.js +0 -36
  200. package/dist/styled-system/css/css.d.ts +0 -22
  201. package/dist/styled-system/css/css.js +0 -45
  202. package/dist/styled-system/css/cva.d.ts +0 -6
  203. package/dist/styled-system/css/cva.js +0 -87
  204. package/dist/styled-system/css/cx.d.ts +0 -5
  205. package/dist/styled-system/css/cx.js +0 -15
  206. package/dist/styled-system/css/index.d.ts +0 -5
  207. package/dist/styled-system/css/index.js +0 -4
  208. package/dist/styled-system/css/sva.d.ts +0 -4
  209. package/dist/styled-system/css/sva.js +0 -46
  210. package/dist/styled-system/helpers.js +0 -316
  211. package/dist/styled-system/patterns/aspect-ratio.d.ts +0 -20
  212. package/dist/styled-system/patterns/aspect-ratio.js +0 -38
  213. package/dist/styled-system/patterns/bleed.d.ts +0 -21
  214. package/dist/styled-system/patterns/bleed.js +0 -24
  215. package/dist/styled-system/patterns/box.d.ts +0 -20
  216. package/dist/styled-system/patterns/box.js +0 -15
  217. package/dist/styled-system/patterns/center.d.ts +0 -20
  218. package/dist/styled-system/patterns/center.js +0 -21
  219. package/dist/styled-system/patterns/circle.d.ts +0 -20
  220. package/dist/styled-system/patterns/circle.js +0 -25
  221. package/dist/styled-system/patterns/container.d.ts +0 -20
  222. package/dist/styled-system/patterns/container.js +0 -21
  223. package/dist/styled-system/patterns/cq.d.ts +0 -21
  224. package/dist/styled-system/patterns/cq.js +0 -21
  225. package/dist/styled-system/patterns/divider.d.ts +0 -22
  226. package/dist/styled-system/patterns/divider.js +0 -25
  227. package/dist/styled-system/patterns/flex.d.ts +0 -26
  228. package/dist/styled-system/patterns/flex.js +0 -26
  229. package/dist/styled-system/patterns/float.d.ts +0 -23
  230. package/dist/styled-system/patterns/float.js +0 -52
  231. package/dist/styled-system/patterns/grid-item.d.ts +0 -25
  232. package/dist/styled-system/patterns/grid-item.js +0 -25
  233. package/dist/styled-system/patterns/grid.d.ts +0 -24
  234. package/dist/styled-system/patterns/grid.js +0 -27
  235. package/dist/styled-system/patterns/hstack.d.ts +0 -21
  236. package/dist/styled-system/patterns/hstack.js +0 -24
  237. package/dist/styled-system/patterns/index.d.ts +0 -21
  238. package/dist/styled-system/patterns/index.js +0 -20
  239. package/dist/styled-system/patterns/link-overlay.d.ts +0 -20
  240. package/dist/styled-system/patterns/link-overlay.js +0 -24
  241. package/dist/styled-system/patterns/spacer.d.ts +0 -20
  242. package/dist/styled-system/patterns/spacer.js +0 -25
  243. package/dist/styled-system/patterns/square.d.ts +0 -20
  244. package/dist/styled-system/patterns/square.js +0 -24
  245. package/dist/styled-system/patterns/stack.d.ts +0 -23
  246. package/dist/styled-system/patterns/stack.js +0 -24
  247. package/dist/styled-system/patterns/visually-hidden.d.ts +0 -20
  248. package/dist/styled-system/patterns/visually-hidden.js +0 -18
  249. package/dist/styled-system/patterns/vstack.d.ts +0 -21
  250. package/dist/styled-system/patterns/vstack.js +0 -24
  251. package/dist/styled-system/patterns/wrap.d.ts +0 -24
  252. package/dist/styled-system/patterns/wrap.js +0 -25
  253. package/dist/styled-system/recipes/content-header.d.ts +0 -31
  254. package/dist/styled-system/recipes/content-header.js +0 -65
  255. package/dist/styled-system/recipes/content-page.d.ts +0 -34
  256. package/dist/styled-system/recipes/content-page.js +0 -43
  257. package/dist/styled-system/recipes/create-recipe.js +0 -82
  258. package/dist/styled-system/recipes/index.d.ts +0 -17
  259. package/dist/styled-system/recipes/index.js +0 -16
  260. package/dist/styled-system/recipes/metric-block.d.ts +0 -34
  261. package/dist/styled-system/recipes/metric-block.js +0 -76
  262. package/dist/styled-system/recipes/product-list.d.ts +0 -31
  263. package/dist/styled-system/recipes/product-list.js +0 -73
  264. package/dist/styled-system/recipes/product-section.d.ts +0 -31
  265. package/dist/styled-system/recipes/product-section.js +0 -49
  266. package/dist/styled-system/recipes/product-settings-row.d.ts +0 -34
  267. package/dist/styled-system/recipes/product-settings-row.js +0 -58
  268. package/dist/styled-system/recipes/product-settings.d.ts +0 -31
  269. package/dist/styled-system/recipes/product-settings.js +0 -57
  270. package/dist/styled-system/recipes/product-status.d.ts +0 -34
  271. package/dist/styled-system/recipes/product-status.js +0 -45
  272. package/dist/styled-system/recipes/product-toolbar.d.ts +0 -31
  273. package/dist/styled-system/recipes/product-toolbar.js +0 -41
  274. package/dist/styled-system/recipes/product-workspace.d.ts +0 -34
  275. package/dist/styled-system/recipes/product-workspace.js +0 -58
  276. package/dist/styled-system/recipes/surface-metric.d.ts +0 -38
  277. package/dist/styled-system/recipes/surface-metric.js +0 -63
  278. package/dist/styled-system/recipes/surface-table.d.ts +0 -34
  279. package/dist/styled-system/recipes/surface-table.js +0 -62
  280. package/dist/styled-system/recipes/ui-badge.d.ts +0 -31
  281. package/dist/styled-system/recipes/ui-badge.js +0 -38
  282. package/dist/styled-system/recipes/ui-button.d.ts +0 -32
  283. package/dist/styled-system/recipes/ui-button.js +0 -43
  284. package/dist/styled-system/recipes/ui-input.d.ts +0 -31
  285. package/dist/styled-system/recipes/ui-input.js +0 -49
  286. package/dist/styled-system/recipes/ui-textarea.d.ts +0 -31
  287. package/dist/styled-system/recipes/ui-textarea.js +0 -24
  288. package/dist/styled-system/styles.css +0 -1257
  289. package/dist/styled-system/tokens/index.d.ts +0 -9
  290. package/dist/styled-system/tokens/index.js +0 -268
  291. package/dist/styled-system/tokens/tokens.d.ts +0 -36
  292. package/dist/styled-system/types/composition.d.ts +0 -227
  293. package/dist/styled-system/types/conditions.d.ts +0 -236
  294. package/dist/styled-system/types/csstype.d.ts +0 -22570
  295. package/dist/styled-system/types/index.d.ts +0 -6
  296. package/dist/styled-system/types/parts.d.ts +0 -8
  297. package/dist/styled-system/types/pattern.d.ts +0 -78
  298. package/dist/styled-system/types/prop-type.d.ts +0 -223
  299. package/dist/styled-system/types/recipe.d.ts +0 -181
  300. package/dist/styled-system/types/selectors.d.ts +0 -59
  301. package/dist/styled-system/types/static-css.d.ts +0 -56
  302. package/dist/styled-system/types/style-props.d.ts +0 -8088
  303. package/dist/styled-system/types/system-types.d.ts +0 -151
@@ -1,18 +1,24 @@
1
1
  <script lang="ts">
2
+ import { assertSchemaFormSchema, isSchemaFormNodeVisible, prepareSchemaFormValue, type SchemaFormSchema } from '@svadmin/core/schema-form';
3
+ import { tick } from 'svelte';
2
4
  import { useTranslation } from '@svadmin/core/i18n';
3
5
  import { Button } from './ui/button/index.js';
4
6
  import { Loader2, Plus, Trash2 } from '@lucide/svelte';
5
7
  import { cn } from '../utils.js';
6
8
  import { onDestroy, untrack } from 'svelte';
7
9
  import {
8
- initializeSchemaForm, schemaFormArrayItem, schemaFormEnumIndex,
9
- schemaFormEnumValue, schemaFormSnapshot, writeSchemaFormPath,
10
+ initializeSchemaForm, schemaFormArrayItem,
11
+ schemaFormSnapshot, writeSchemaFormPath,
10
12
  type JsonSchemaFormSchema,
11
13
  } from './json-schema-form-state.js';
12
-
13
14
  import { readRecursiveSchemaForm, validateRecursiveSchemaForm, schemaFormPointer } from './enterprise/recursive-schema-form.js';
14
15
  import { parseSchemaNumber, type SchemaFormIssue } from './enterprise/json-schema-form.js';
15
- type JsonSchema = JsonSchemaFormSchema;
16
+ interface JsonSchema extends JsonSchemaFormSchema {
17
+ properties?: Record<string, JsonSchema>;
18
+ items?: JsonSchema;
19
+ visibleWhen?: SchemaFormSchema['visibleWhen'];
20
+ pattern?: string;
21
+ }
16
22
 
17
23
  interface Props {
18
24
  schema: JsonSchema;
@@ -48,35 +54,70 @@
48
54
  const instanceId = $props.id();
49
55
  const prefix = $derived(idPrefix ?? `json-form-${instanceId}`);
50
56
  const isZh = $derived((locale ?? i18n.locale).startsWith('zh'));
51
- const labels = $derived(isZh ? {
52
- add: '添加项目', remove: '删除', empty: '暂无项目',
53
- failed: '提交未完成,请检查状态后重试。', invalid: '请检查表单输入。',
54
- } : {
55
- add: 'Add item', remove: 'Remove item', empty: 'No items',
56
- failed: 'Submission did not complete. Check its status before retrying.', invalid: 'Check the form input.',
57
- });
57
+ const labels = $derived(isZh ? { add: '添加项目', remove: '删除', empty: '暂无项目', failed: '提交未完成,请检查状态后重试。', invalid: '请检查表单输入。' }
58
+ : { add: 'Add item', remove: 'Remove item', empty: 'No items', failed: 'Submission did not complete. Check its status before retrying.', invalid: 'Check the form input.' });
59
+ let isSubmitting = $state(false);
58
60
  const model = $derived(readRecursiveSchemaForm(schema));
61
+ // 两个已发布的 schema 子集分别验证;任何一个都不接受时关闭表单。
62
+ const coreSchema = $derived.by(() => {
63
+ try { assertSchemaFormSchema(schema as SchemaFormSchema); return schema as SchemaFormSchema; }
64
+ catch { return undefined; }
65
+ });
59
66
  let validationIssues = $state<SchemaFormIssue[]>([]);
60
67
  let parseIssues = $state<SchemaFormIssue[]>([]);
61
- let internalValue: Record<string, unknown> | undefined;
62
- let isSubmitting = $state(false);
63
68
  let failure = $state<'invalid' | 'failed' | null>(null);
64
69
  let formElement: HTMLFormElement | undefined;
65
70
  let destroyed = false;
66
71
  let authorityRevision = 0;
67
72
  let identity: readonly unknown[] = [];
73
+ let internalValue: Record<string, unknown> | undefined;
74
+
75
+ function cloneDefault(node: JsonSchema): unknown {
76
+ if (node.default !== undefined) return structuredClone($state.snapshot(node.default));
77
+ if (node.type === 'null') return null;
78
+ if (node.type === 'object' || node.properties) {
79
+ return Object.fromEntries(
80
+ Object.entries(node.properties ?? {})
81
+ .map(([key, child]) => [key, cloneDefault(child)])
82
+ .filter(([, child]) => child !== undefined),
83
+ );
84
+ }
85
+ if (node.type === 'array') return [];
86
+ return undefined;
87
+ }
88
+
89
+ function mergeDefaults(node: JsonSchema, current: unknown, present: boolean, budget = { nodes: 0 }): unknown {
90
+ if (++budget.nodes > 1000) throw new Error('Schema form data limit exceeded.');
91
+ const candidate = present ? current : cloneDefault(node);
92
+ if (candidate === null || (present && candidate === undefined)) return candidate;
93
+ if (node.type === 'array') {
94
+ if (Array.isArray(candidate) && candidate.length > 1000 - budget.nodes) throw new Error('Schema form data limit exceeded.');
95
+ return Array.isArray(candidate)
96
+ ? candidate.map(item => mergeDefaults(node.items ?? { type: 'string' }, item, true, budget))
97
+ : candidate;
98
+ }
99
+ if (node.type !== 'object' && !node.properties) return candidate;
100
+ if (candidate !== undefined && (typeof candidate !== 'object' || Array.isArray(candidate))) return candidate;
101
+ const source = (candidate ?? {}) as Record<string, unknown>;
102
+ const result: Record<string, unknown> = { ...source };
103
+ for (const [key, child] of Object.entries(node.properties ?? {})) {
104
+ const own = Object.hasOwn(source, key);
105
+ const next = mergeDefaults(child, own ? source[key] : undefined, own, budget);
106
+ if (next !== undefined || own) Object.defineProperty(result, key, { value: next, enumerable: true, configurable: true, writable: true });
107
+ }
108
+ return result;
109
+ }
110
+
68
111
  const prepared = $derived.by(() => {
69
112
  try {
70
- if (model.issues.length) return { ok: false as const };
71
- return { ok: true as const, value: initializeSchemaForm(schema, value) };
113
+ if (model.issues.length && !coreSchema) return { ok: false as const };
114
+ const seeded = mergeDefaults(schema, value, true);
115
+ return { ok: true as const, value: initializeSchemaForm(schema, seeded as Record<string, unknown>) };
116
+ } catch {
117
+ return { ok: false as const };
72
118
  }
73
- catch { return { ok: false as const }; }
74
119
  });
75
120
  const locked = $derived(disabled || readonly || isSubmitting || !prepared.ok);
76
- // 按键是否存在初始化,不能每次输入都用 ?? 把清空的默认值补回来。
77
- $effect.pre(() => {
78
- if (prepared.ok && prepared.value !== value) value = prepared.value;
79
- });
80
121
  $effect.pre(() => {
81
122
  const next = [schema, value, disabled, readonly, onsubmit];
82
123
  untrack(() => {
@@ -133,31 +174,39 @@
133
174
  }
134
175
  function readPath(path: string[]): unknown {
135
176
  return path.reduce<unknown>((current, key) => (
136
- current && typeof current === 'object' && Object.hasOwn(current, key)
137
- ? (current as Record<string, unknown>)[key] : undefined
177
+ current && typeof current === 'object' && Object.hasOwn(current, key) ? (current as Record<string, unknown>)[key] : undefined
138
178
  ), prepared.ok ? prepared.value : {});
139
179
  }
180
+
140
181
  function writePath(path: string[], nextValue: unknown): boolean {
141
182
  if (!editable() || !prepared.ok || pathReadonly(path)) return false;
142
183
  try {
143
184
  value = writeSchemaFormPath(prepared.value, path, nextValue);
144
- // Capture the actual bindable value after Svelte wraps it, not its raw source.
145
185
  internalValue = value;
146
186
  failure = null;
147
187
  validationIssues = [];
148
188
  return true;
149
- }
150
- catch { failure = 'invalid'; return false; }
189
+ } catch { failure = 'invalid'; return false; }
151
190
  }
152
- function choose(path: string[], raw: string, choices: JsonSchema['enum']): void {
153
- if (!editable()) return;
154
- try { writePath(path, schemaFormEnumValue(choices ?? [], raw)); }
155
- catch { failure = 'invalid'; }
191
+
192
+ function enumToken(index: number): string {
193
+ return coreSchema && model.issues.length ? `__json_enum_${index}` : String(index);
194
+ }
195
+
196
+ function enumIndex(current: unknown, options: JsonSchema['enum']): number {
197
+ return options?.findIndex((option) => Object.is(option, current)) ?? -1;
198
+ }
199
+
200
+ function enumValue(raw: string, options: JsonSchema['enum']): unknown {
201
+ if (!/^(?:__json_enum_)?(0|[1-9]\d*)$/.test(raw)) return undefined;
202
+ const index = Number(raw.replace(/^__json_enum_/, ''));
203
+ return Number.isSafeInteger(index) ? options?.[index] : undefined;
156
204
  }
157
205
  function addArrayItem(path: string[], itemSchema: JsonSchema): void {
158
206
  if (!editable()) return;
159
207
  const current = readPath(path);
160
- try { writePath(path, [...(Array.isArray(current) ? current : []), schemaFormArrayItem(itemSchema)]); }
208
+ const items = Array.isArray(current) ? current : [];
209
+ try { writePath(path, [...items, schemaFormArrayItem(itemSchema)]); }
161
210
  catch { failure = 'invalid'; }
162
211
  }
163
212
  function removeArrayItem(path: string[], index: number): void {
@@ -182,29 +231,40 @@
182
231
  }
183
232
  async function handleSubmit(event: SubmitEvent): Promise<void> {
184
233
  event.preventDefault();
185
- // 同步置锁,防止同一帧的双击/重入 submit;遵循宿主 fieldset 的预览禁用状态;权限仍需服务端校验。
186
234
  if (!editable() || !prepared.ok || !onsubmit) return;
187
- const problems = [...parseIssues, ...validateRecursiveSchemaForm(model, prepared.value)];
188
- if (problems.length) { invalid(problems); return; }
189
- if (formElement && !formElement.checkValidity()) { invalid([{ path: '', code: 'invalid-value' }]); return; }
190
235
  let payload: Record<string, unknown>;
191
- try { payload = schemaFormSnapshot(prepared.value); }
192
- catch { invalid([{ path: '', code: 'invalid-value' }]); return; }
236
+ try {
237
+ const result = coreSchema ? prepareSchemaFormValue(coreSchema, prepared.value) : undefined;
238
+ const detailedIssues = model.issues.length ? [] : validateRecursiveSchemaForm(model, prepared.value);
239
+ const problems = [...parseIssues, ...(result
240
+ ? result.errors.map(issue => {
241
+ const path = schemaFormPointer(JSON.parse(issue.path) as string[]);
242
+ // 共用子集保留公开错误码;核心校验仍决定哪些字段不可提交。
243
+ return detailedIssues.find(detail => detail.path === path) ?? { path, code: 'invalid-value' as const };
244
+ })
245
+ : detailedIssues)];
246
+ if (problems.length) {
247
+ invalid(problems);
248
+ await tick();
249
+ formElement?.querySelector<HTMLElement>('[aria-invalid="true"]')?.focus();
250
+ return;
251
+ }
252
+ payload = schemaFormSnapshot(result?.data ?? prepared.value);
253
+ } catch { invalid([{ path: '', code: 'invalid-value' }]); return; }
193
254
  const currentValue = value, currentSchema = schema, handler = onsubmit, revision = authorityRevision;
194
- const fingerprint = JSON.stringify(payload);
255
+ const fingerprint = JSON.stringify(schemaFormSnapshot(prepared.value));
195
256
  failure = null;
196
257
  isSubmitting = true;
197
258
  try {
198
259
  await handler(payload);
199
260
  } catch (error) {
200
- // 回调只能拿到副本;被替换/卸载实例的失败不能污染新的表单上下文。
201
261
  if (!destroyed && revision === authorityRevision && !disabled && !readonly && currentValue === value && currentSchema === schema && handler === onsubmit) {
202
262
  try {
203
263
  if (JSON.stringify(schemaFormSnapshot(prepared.ok ? prepared.value : value)) === fingerprint) {
204
264
  failure = 'failed';
205
265
  try { onerror?.(error); } catch { /* 错误观察者不改变提交状态。 */ }
206
266
  }
207
- } catch { /* 新上下文不再是可比较的 JSON 草稿,不发布旧错误。 */ }
267
+ } catch { /* 新草稿不接收旧请求的错误。 */ }
208
268
  }
209
269
  } finally {
210
270
  if (!destroyed) isSubmitting = false;
@@ -234,13 +294,17 @@
234
294
 
235
295
  {#snippet renderField(node: JsonSchema, path: string[], title: string, required = false)}
236
296
  {@const current = readPath(path)}
237
- {@const options = choices(node)}
297
+ {@const id = `${prefix}_${path.map(encodeURIComponent).join('/')}`}
238
298
  {@const type = nodeType(node)}
239
299
  {@const fieldLocked = locked || pathReadonly(path)}
240
- {@const id = `${prefix}_${path.map(encodeURIComponent).join('/')}`}
241
- {#if type === 'object' || node.properties}
242
- <fieldset class="svadmin-u-da7c36cd8867 svadmin-u-421ac2be5045">
300
+ {@const error = fieldInvalid(path)}
301
+ {@const options = choices(node)}
302
+ {#if coreSchema && !isSchemaFormNodeVisible(node as SchemaFormSchema, prepared.ok ? prepared.value : {})}
303
+ <!-- 隐藏字段保留草稿,但不进入提交数据。 -->
304
+ {:else if type === 'object' || node.properties}
305
+ <fieldset disabled={fieldLocked} class="svadmin-u-da7c36cd8867 svadmin-u-421ac2be5045">
243
306
  <legend class="svadmin-u-0214b4b355d1 svadmin-u-2689f3958069">{title}</legend>
307
+ {#if error}<p id={`${id}-error`}>{i18n.t('validation.invalidFormat')}</p>{/if}
244
308
  {#if node.description}<p class="svadmin-u-d058ca6de60f svadmin-u-bfa603190748">{node.description}</p>{/if}
245
309
  <div class="svadmin-u-9c6cdfa2ba3d">
246
310
  {#each Object.entries(node.properties ?? {}) as [key, child] (key)}
@@ -250,18 +314,19 @@
250
314
  </fieldset>
251
315
  {:else if type === 'array'}
252
316
  {@const items = Array.isArray(current) ? current : []}
253
- <fieldset class="svadmin-u-da7c36cd8867 svadmin-u-421ac2be5045">
317
+ <fieldset disabled={fieldLocked} class="svadmin-u-da7c36cd8867 svadmin-u-421ac2be5045">
254
318
  <legend class="svadmin-u-0214b4b355d1 svadmin-u-2689f3958069">{title}</legend>
319
+ {#if error}<p id={`${id}-error`}>{i18n.t('validation.invalidFormat')}</p>{/if}
255
320
  {#if node.description}<p class="svadmin-u-d058ca6de60f svadmin-u-bfa603190748">{node.description}</p>{/if}
256
321
  {#each items as _, index (index)}
257
- {@render renderField(node.items ?? { type: 'string' }, [...path, String(index)], `${title} ${index + 1}`, true)}
258
- <Button type="button" disabled={fieldLocked} variant="ghost" size="sm" onclick={() => removeArrayItem(path, index)}>
322
+ {@render renderField(node.items ?? { type: 'string' }, [...path, String(index)], `${title} ${index + 1}`)}
323
+ <Button type="button" variant="ghost" size="sm" disabled={fieldLocked || (node.minItems !== undefined && items.length <= node.minItems)} onclick={() => removeArrayItem(path, index)}>
259
324
  <Trash2 class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" /> {labels.remove}
260
325
  </Button>
261
326
  {:else}
262
327
  <span class="svadmin-u-bfa603190748">{labels.empty}</span>
263
328
  {/each}
264
- <Button type="button" disabled={fieldLocked} variant="outline" size="sm" onclick={() => addArrayItem(path, node.items ?? { type: 'string' })}>
329
+ <Button type="button" variant="outline" size="sm" disabled={fieldLocked || (node.maxItems !== undefined && items.length >= node.maxItems)} onclick={() => addArrayItem(path, node.items ?? { type: 'string' })}>
265
330
  <Plus class="svadmin-u-7fc7f732bf7e svadmin-u-bf600f8e029c" /> {labels.add}
266
331
  </Button>
267
332
  </fieldset>
@@ -272,17 +337,20 @@
272
337
  </label>
273
338
  {#if node.description}<p class="svadmin-u-d058ca6de60f svadmin-u-bfa603190748">{node.description}</p>{/if}
274
339
  {#if options}
275
- <select id={id} name={path.join('.')} aria-invalid={fieldInvalid(path)} disabled={fieldLocked} required={required} value={schemaFormEnumIndex(options, current)} onchange={(event) => choose(path, event.currentTarget.value, options)} class="svadmin-u-ed8a5df7b2fb svadmin-u-6da6a3c3f741 svadmin-u-421ac2be5045 svadmin-u-ca6bcd4b6f3f svadmin-u-e5795dad4d22 svadmin-u-e6f9e383a762 svadmin-u-d5eab218aa34 svadmin-u-359090c2d529">
340
+ <select id={id} name={path.join('.')} disabled={fieldLocked} aria-invalid={error ? 'true' : undefined} aria-describedby={error ? `${id}-error` : undefined} required={required} value={enumIndex(current, options) < 0 ? '' : enumToken(enumIndex(current, options))} onchange={(event) => writePath(path, enumValue(event.currentTarget.value, options))} class="svadmin-u-ed8a5df7b2fb svadmin-u-6da6a3c3f741 svadmin-u-421ac2be5045 svadmin-u-ca6bcd4b6f3f svadmin-u-e5795dad4d22 svadmin-u-e6f9e383a762 svadmin-u-d5eab218aa34 svadmin-u-359090c2d529">
276
341
  <option value="">{i18n.t('common.selectOption')}</option>
277
- {#each options as option, optionIndex (optionIndex)}<option value={String(optionIndex)}>{String(option)}</option>{/each}
342
+ {#each options as option, index (index)}<option value={enumToken(index)}>{String(option)}</option>{/each}
278
343
  </select>
344
+ {:else if node.type === 'null'}
345
+ <input id={id} disabled={fieldLocked} type="text" value="null" readonly />
279
346
  {:else if type === 'boolean'}
280
- <input id={id} name={path.join('.')} aria-invalid={fieldInvalid(path)} disabled={fieldLocked} type="checkbox" checked={Boolean(current)} onchange={(event) => writePath(path, event.currentTarget.checked)} />
347
+ <input id={id} name={path.join('.')} disabled={fieldLocked} aria-invalid={error ? 'true' : undefined} aria-describedby={error ? `${id}-error` : undefined} type="checkbox" checked={Boolean(current)} onchange={(event) => writePath(path, event.currentTarget.checked)} />
281
348
  {:else if type === 'number' || type === 'integer'}
282
- <input id={id} name={path.join('.')} aria-invalid={fieldInvalid(path)} disabled={fieldLocked} type="number" step={type === 'integer' ? 1 : 'any'} required={required} value={current === undefined ? '' : String(current)} oninput={(event) => writeNumber(path, event.currentTarget)} class="svadmin-u-ed8a5df7b2fb svadmin-u-6da6a3c3f741 svadmin-u-421ac2be5045 svadmin-u-ca6bcd4b6f3f svadmin-u-e5795dad4d22 svadmin-u-e6f9e383a762 svadmin-u-d5eab218aa34 svadmin-u-359090c2d529" />
349
+ <input id={id} name={path.join('.')} disabled={fieldLocked} aria-invalid={error ? 'true' : undefined} aria-describedby={error ? `${id}-error` : undefined} type="number" step={type === 'integer' ? 1 : 'any'} min={node.minimum} max={node.maximum} required={required} value={current == null ? '' : String(current)} oninput={(event) => writeNumber(path, event.currentTarget)} class="svadmin-u-ed8a5df7b2fb svadmin-u-6da6a3c3f741 svadmin-u-421ac2be5045 svadmin-u-ca6bcd4b6f3f svadmin-u-e5795dad4d22 svadmin-u-e6f9e383a762 svadmin-u-d5eab218aa34 svadmin-u-359090c2d529" />
283
350
  {:else}
284
- <input id={id} name={path.join('.')} aria-invalid={fieldInvalid(path)} disabled={fieldLocked} type="text" required={required} value={String(current ?? '')} oninput={(event) => writePath(path, event.currentTarget.value)} class="svadmin-u-ed8a5df7b2fb svadmin-u-6da6a3c3f741 svadmin-u-421ac2be5045 svadmin-u-ca6bcd4b6f3f svadmin-u-e5795dad4d22 svadmin-u-e6f9e383a762 svadmin-u-d5eab218aa34 svadmin-u-359090c2d529" />
351
+ <input id={id} name={path.join('.')} disabled={fieldLocked} aria-invalid={error ? 'true' : undefined} aria-describedby={error ? `${id}-error` : undefined} type="text" minlength={node.minLength} maxlength={node.maxLength} required={required} value={String(current ?? '')} oninput={(event) => writePath(path, event.currentTarget.value)} class="svadmin-u-ed8a5df7b2fb svadmin-u-6da6a3c3f741 svadmin-u-421ac2be5045 svadmin-u-ca6bcd4b6f3f svadmin-u-e5795dad4d22 svadmin-u-e6f9e383a762 svadmin-u-d5eab218aa34 svadmin-u-359090c2d529" />
285
352
  {/if}
353
+ {#if error}<p id={`${id}-error`}>{i18n.t('validation.invalidFormat')}</p>{/if}
286
354
  </div>
287
355
  {/if}
288
356
  {/snippet}
@@ -1,6 +1,12 @@
1
+ import { type SchemaFormSchema } from '@svadmin/core/schema-form';
1
2
  import { type JsonSchemaFormSchema } from './json-schema-form-state.js';
2
3
  import { type SchemaFormIssue } from './enterprise/json-schema-form.js';
3
- type JsonSchema = JsonSchemaFormSchema;
4
+ interface JsonSchema extends JsonSchemaFormSchema {
5
+ properties?: Record<string, JsonSchema>;
6
+ items?: JsonSchema;
7
+ visibleWhen?: SchemaFormSchema['visibleWhen'];
8
+ pattern?: string;
9
+ }
4
10
  interface Props {
5
11
  schema: JsonSchema;
6
12
  value?: Record<string, unknown>;