@svadmin/ui 0.72.0 → 0.74.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 (276) hide show
  1. package/README.md +62 -22
  2. package/THIRD_PARTY_NOTICES.md +52 -0
  3. package/dist/app.css +8488 -6297
  4. package/dist/app.theme.css +8514 -6374
  5. package/dist/class-properties.generated.d.ts +1 -0
  6. package/dist/class-properties.generated.js +2 -0
  7. package/dist/classnames.d.ts +8 -0
  8. package/dist/classnames.js +26 -0
  9. package/dist/component-registry.d.ts +14977 -0
  10. package/dist/component-registry.js +216 -0
  11. package/dist/components/AboutSettings.svelte +7 -1
  12. package/dist/components/AboutSettings.svelte.d.ts +4 -1
  13. package/dist/components/ActivityFeed.svelte +9 -3
  14. package/dist/components/ActivityFeed.svelte.d.ts +3 -0
  15. package/dist/components/AdminApp.svelte +19 -10
  16. package/dist/components/AdminApp.svelte.d.ts +6 -0
  17. package/dist/components/ApiSettings.svelte +41 -5
  18. package/dist/components/ApprovalCenter.svelte +283 -0
  19. package/dist/components/ApprovalCenter.svelte.d.ts +10 -0
  20. package/dist/components/AuditLogViewer.svelte +3 -1
  21. package/dist/components/AutoForm.svelte +53 -9
  22. package/dist/components/AutoForm.svelte.d.ts +3 -0
  23. package/dist/components/AutoTable.svelte +846 -43
  24. package/dist/components/AutoTable.svelte.d.ts +22 -1
  25. package/dist/components/BoundRecordDetailDrawer.svelte +17 -2
  26. package/dist/components/BoundRecordDetailDrawer.svelte.d.ts +4 -0
  27. package/dist/components/Breadcrumbs.svelte +11 -1
  28. package/dist/components/CancelTaskButton.svelte +12 -42
  29. package/dist/components/ColorPicker.svelte +73 -0
  30. package/dist/components/ColorPicker.svelte.d.ts +15 -0
  31. package/dist/components/ComboboxField.svelte +143 -34
  32. package/dist/components/ComboboxField.svelte.d.ts +4 -2
  33. package/dist/components/CreatePage.svelte +4 -0
  34. package/dist/components/CreatePage.svelte.d.ts +2 -0
  35. package/dist/components/DashboardView.svelte +99 -0
  36. package/dist/components/DashboardView.svelte.d.ts +14 -0
  37. package/dist/components/DatePicker.svelte +10 -0
  38. package/dist/components/DatePicker.svelte.d.ts +6 -0
  39. package/dist/components/DateRangeInput.svelte +155 -0
  40. package/dist/components/DateRangeInput.svelte.d.ts +35 -0
  41. package/dist/components/DateRangePicker.svelte +10 -0
  42. package/dist/components/DateRangePicker.svelte.d.ts +6 -0
  43. package/dist/components/DateTimeInput.svelte +134 -0
  44. package/dist/components/DateTimeInput.svelte.d.ts +27 -0
  45. package/dist/components/DateTimePicker.svelte +10 -0
  46. package/dist/components/DateTimePicker.svelte.d.ts +6 -0
  47. package/dist/components/DevTools.svelte +36 -28
  48. package/dist/components/DevframePanel.svelte +123 -0
  49. package/dist/components/DevframePanel.svelte.d.ts +3 -0
  50. package/dist/components/EditPage.svelte +4 -0
  51. package/dist/components/EditPage.svelte.d.ts +2 -0
  52. package/dist/components/ErrorSummary.svelte +52 -0
  53. package/dist/components/ErrorSummary.svelte.d.ts +14 -0
  54. package/dist/components/FieldRenderer.svelte +172 -99
  55. package/dist/components/FileUpload.svelte +412 -0
  56. package/dist/components/FileUpload.svelte.d.ts +43 -0
  57. package/dist/components/FilterBuilder.svelte +244 -265
  58. package/dist/components/FilterBuilder.svelte.d.ts +3 -0
  59. package/dist/components/FilterCollectionInput.svelte +80 -0
  60. package/dist/components/FilterCollectionInput.svelte.d.ts +10 -0
  61. package/dist/components/GanttChart.svelte +66 -14
  62. package/dist/components/GanttChart.svelte.d.ts +6 -0
  63. package/dist/components/Header.svelte +18 -2
  64. package/dist/components/ImportWizard.svelte +509 -100
  65. package/dist/components/ImportWizard.svelte.d.ts +11 -0
  66. package/dist/components/JsonSchemaForm.svelte +322 -98
  67. package/dist/components/JsonSchemaForm.svelte.d.ts +18 -1
  68. package/dist/components/KanbanBoard.svelte +195 -40
  69. package/dist/components/KanbanBoard.svelte.d.ts +9 -2
  70. package/dist/components/Layout.svelte +4 -3
  71. package/dist/components/LazyAutoForm.svelte +28 -0
  72. package/dist/components/LazyAutoForm.svelte.d.ts +8 -0
  73. package/dist/components/LazyAutoTable.svelte +28 -0
  74. package/dist/components/LazyAutoTable.svelte.d.ts +8 -0
  75. package/dist/components/LazySettingsPage.svelte +28 -0
  76. package/dist/components/LazySettingsPage.svelte.d.ts +8 -0
  77. package/dist/components/LazyShowPage.svelte +28 -0
  78. package/dist/components/LazyShowPage.svelte.d.ts +8 -0
  79. package/dist/components/LazyTaskQueueDrawer.svelte.d.ts +1 -1
  80. package/dist/components/ListPage.svelte +9 -1
  81. package/dist/components/ListPage.svelte.d.ts +5 -0
  82. package/dist/components/LoginPage.svelte +2 -2
  83. package/dist/components/MoneyInput.svelte +25 -0
  84. package/dist/components/MoneyInput.svelte.d.ts +20 -0
  85. package/dist/components/MultiTabKeepAlive.svelte +11 -6
  86. package/dist/components/NotificationsSettings.svelte +69 -2
  87. package/dist/components/NotificationsSettings.svelte.d.ts +25 -1
  88. package/dist/components/NumberInput.svelte +25 -0
  89. package/dist/components/NumberInput.svelte.d.ts +19 -0
  90. package/dist/components/NumericInput.svelte +110 -0
  91. package/dist/components/NumericInput.svelte.d.ts +23 -0
  92. package/dist/components/PercentInput.svelte +25 -0
  93. package/dist/components/PercentInput.svelte.d.ts +20 -0
  94. package/dist/components/PermissionMatrix.svelte +20 -1
  95. package/dist/components/PivotExportButton.svelte +157 -0
  96. package/dist/components/PivotExportButton.svelte.d.ts +16 -0
  97. package/dist/components/PivotTable.svelte +229 -74
  98. package/dist/components/PivotTable.svelte.d.ts +19 -2
  99. package/dist/components/QuickEditDrawer.svelte +4 -1
  100. package/dist/components/QuickEditDrawer.svelte.d.ts +2 -0
  101. package/dist/components/RadioGroup.svelte +121 -0
  102. package/dist/components/RadioGroup.svelte.d.ts +43 -0
  103. package/dist/components/RangeSlider.svelte +127 -0
  104. package/dist/components/RangeSlider.svelte.d.ts +22 -0
  105. package/dist/components/Rate.svelte +121 -0
  106. package/dist/components/Rate.svelte.d.ts +16 -0
  107. package/dist/components/RecordDetailDrawer.svelte +6 -2
  108. package/dist/components/RecordDetailDrawer.svelte.d.ts +4 -0
  109. package/dist/components/RelationPicker.svelte +341 -0
  110. package/dist/components/RelationPicker.svelte.d.ts +26 -0
  111. package/dist/components/ResourceOperationsPage.svelte +25 -19
  112. package/dist/components/ResourceOperationsPage.svelte.d.ts +5 -1
  113. package/dist/components/RetryTaskButton.svelte +12 -42
  114. package/dist/components/SecuritySettings.svelte +29 -5
  115. package/dist/components/SegmentedControl.svelte +145 -0
  116. package/dist/components/SegmentedControl.svelte.d.ts +38 -0
  117. package/dist/components/SettingsPage.svelte +3 -1
  118. package/dist/components/SettingsPage.svelte.d.ts +1 -0
  119. package/dist/components/ShowInferencer.svelte +4 -2
  120. package/dist/components/ShowInferencer.svelte.d.ts +2 -0
  121. package/dist/components/ShowPage.svelte +25 -6
  122. package/dist/components/ShowPage.svelte.d.ts +5 -0
  123. package/dist/components/Sidebar.svelte +5 -5
  124. package/dist/components/Slider.svelte +100 -0
  125. package/dist/components/Slider.svelte.d.ts +19 -0
  126. package/dist/components/SpreadsheetView.svelte +1037 -124
  127. package/dist/components/SpreadsheetView.svelte.d.ts +19 -0
  128. package/dist/components/StepsForm.svelte +5 -5
  129. package/dist/components/SvarAutoTable.svelte +39 -0
  130. package/dist/components/SvarAutoTable.svelte.d.ts +13 -0
  131. package/dist/components/SvarDataGrid.svelte +362 -0
  132. package/dist/components/SvarDataGrid.svelte.d.ts +57 -0
  133. package/dist/components/SvarGridCell.svelte +10 -0
  134. package/dist/components/SvarGridCell.svelte.d.ts +7 -0
  135. package/dist/components/SvarResourceRead.svelte +50 -0
  136. package/dist/components/SvarResourceRead.svelte.d.ts +23 -0
  137. package/dist/components/SvarResourceTable.svelte +63 -0
  138. package/dist/components/SvarResourceTable.svelte.d.ts +42 -0
  139. package/dist/components/SvarResourceTableView.svelte +364 -0
  140. package/dist/components/SvarResourceTableView.svelte.d.ts +4 -0
  141. package/dist/components/TagsInput.svelte +178 -0
  142. package/dist/components/TagsInput.svelte.d.ts +17 -0
  143. package/dist/components/TaskQueueDrawer.svelte +72 -16
  144. package/dist/components/TimePicker.svelte +10 -0
  145. package/dist/components/TimePicker.svelte.d.ts +6 -0
  146. package/dist/components/TreeSelect.svelte +440 -46
  147. package/dist/components/TreeSelect.svelte.d.ts +6 -1
  148. package/dist/components/TreeTable.svelte +484 -82
  149. package/dist/components/TreeTable.svelte.d.ts +24 -1
  150. package/dist/components/TwoFactorAuthPage.svelte +85 -48
  151. package/dist/components/TwoFactorAuthPage.svelte.d.ts +12 -1
  152. package/dist/components/VirtualTable.svelte +116 -20
  153. package/dist/components/VirtualTable.svelte.d.ts +5 -0
  154. package/dist/components/account/CompanyProfilePage.svelte +74 -38
  155. package/dist/components/account/GetStartedPage.svelte +2 -1
  156. package/dist/components/account/ImportMembersPage.svelte +22 -167
  157. package/dist/components/account/ImportMembersPage.svelte.d.ts +4 -1
  158. package/dist/components/account/MemberDirectory.svelte +156 -0
  159. package/dist/components/account/MemberDirectory.svelte.d.ts +19 -0
  160. package/dist/components/account/MembersStarterPage.svelte +9 -55
  161. package/dist/components/account/MembersStarterPage.svelte.d.ts +5 -1
  162. package/dist/components/account/SecurityLogPage.svelte +4 -1
  163. package/dist/components/account/SettingsPlainPage.svelte +7 -38
  164. package/dist/components/account/SettingsPlainPage.svelte.d.ts +4 -1
  165. package/dist/components/account/SettingsSidebarPage.svelte +7 -20
  166. package/dist/components/account/TeamMembersPage.svelte +4 -20
  167. package/dist/components/account/TeamMembersPage.svelte.d.ts +5 -1
  168. package/dist/components/account/UserProfilePage.svelte +3 -25
  169. package/dist/components/account/UserProfilePage.svelte.d.ts +17 -2
  170. package/dist/components/auto-table-grid.d.ts +26 -0
  171. package/dist/components/auto-table-grid.js +1 -0
  172. package/dist/components/buttons/ExportButton.svelte +56 -12
  173. package/dist/components/buttons/ExportButton.svelte.d.ts +11 -0
  174. package/dist/components/content/ContentPageHeader.svelte +12 -9
  175. package/dist/components/content/ContentPageShell.svelte +6 -9
  176. package/dist/components/content/FileBrowser.svelte +35 -0
  177. package/dist/components/content/FileBrowser.svelte.d.ts +14 -0
  178. package/dist/components/content/MetricBlock.svelte +10 -13
  179. package/dist/components/content/NetworkTable.svelte +3 -3
  180. package/dist/components/content/NetworkTable.svelte.d.ts +1 -0
  181. package/dist/components/content/PageToolbar.svelte +6 -10
  182. package/dist/components/content/ReferenceNotice.svelte +5 -0
  183. package/dist/components/content/ReferenceNotice.svelte.d.ts +18 -0
  184. package/dist/components/content/SectionHeader.svelte +8 -13
  185. package/dist/components/content/SettingsFieldRow.svelte +9 -23
  186. package/dist/components/content/SettingsGroup.svelte +11 -27
  187. package/dist/components/content/StatusBadge.svelte +3 -8
  188. package/dist/components/content/WorkspaceLayout.svelte +8 -12
  189. package/dist/components/enterprise/filter-tree.d.ts +48 -0
  190. package/dist/components/enterprise/filter-tree.js +224 -0
  191. package/dist/components/enterprise/json-schema-form.d.ts +39 -0
  192. package/dist/components/enterprise/json-schema-form.js +195 -0
  193. package/dist/components/enterprise/recursive-schema-form.d.ts +30 -0
  194. package/dist/components/enterprise/recursive-schema-form.js +196 -0
  195. package/dist/components/enterprise/spreadsheet-formula.d.ts +12 -0
  196. package/dist/components/enterprise/spreadsheet-formula.js +245 -0
  197. package/dist/components/export-reference.d.ts +2 -0
  198. package/dist/components/export-reference.js +9 -0
  199. package/dist/components/fieldComponentMap.js +2 -0
  200. package/dist/components/fields/DateField.svelte +12 -12
  201. package/dist/components/fields/date-display.d.ts +6 -0
  202. package/dist/components/fields/date-display.js +24 -0
  203. package/dist/components/fields/date-field-value.d.ts +2 -0
  204. package/dist/components/fields/date-field-value.js +13 -0
  205. package/dist/components/json-schema-form-state.d.ts +37 -0
  206. package/dist/components/json-schema-form-state.js +168 -0
  207. package/dist/components/network/TeamCrewTablePage.svelte +6 -1
  208. package/dist/components/network/UserCardsNFTPage.svelte +2 -0
  209. package/dist/components/profile/PublicProfilePage.svelte +2 -0
  210. package/dist/components/record-detail.svelte.d.ts +3 -0
  211. package/dist/components/record-detail.svelte.js +10 -4
  212. package/dist/components/saved-list-views.d.ts +74 -1
  213. package/dist/components/saved-list-views.js +191 -3
  214. package/dist/components/svar-grid-cells.d.ts +14 -0
  215. package/dist/components/svar-grid-cells.js +2 -0
  216. package/dist/components/svar-grid-contract.d.ts +80 -0
  217. package/dist/components/svar-grid-contract.js +159 -0
  218. package/dist/components/svar-grid-interactions.d.ts +54 -0
  219. package/dist/components/svar-grid-interactions.js +174 -0
  220. package/dist/components/svar-grid-loading.d.ts +40 -0
  221. package/dist/components/svar-grid-loading.js +180 -0
  222. package/dist/components/svar-grid-operations.d.ts +37 -0
  223. package/dist/components/svar-grid-operations.js +186 -0
  224. package/dist/components/svar-grid-panes.d.ts +34 -0
  225. package/dist/components/svar-grid-panes.js +66 -0
  226. package/dist/components/svar-grid-resource-loading.d.ts +18 -0
  227. package/dist/components/svar-grid-resource-loading.js +68 -0
  228. package/dist/components/svar-grid-resource-session.svelte.d.ts +29 -0
  229. package/dist/components/svar-grid-resource-session.svelte.js +61 -0
  230. package/dist/components/table-contract.d.ts +12 -1
  231. package/dist/components/task-action.svelte.d.ts +13 -0
  232. package/dist/components/task-action.svelte.js +73 -0
  233. package/dist/components/ui/DesignSystemShowcase.svelte +164 -0
  234. package/dist/components/ui/DesignSystemShowcase.svelte.d.ts +3 -0
  235. package/dist/components/ui/badge/badge-variants.d.ts +3 -2
  236. package/dist/components/ui/badge/badge-variants.js +2 -1
  237. package/dist/components/ui/badge/badge.svelte +3 -0
  238. package/dist/components/ui/badge/badge.svelte.d.ts +1 -0
  239. package/dist/components/ui/button/button-variants.d.ts +5 -4
  240. package/dist/components/ui/button/button-variants.js +7 -2
  241. package/dist/components/ui/checkbox/checkbox.svelte +5 -1
  242. package/dist/components/ui/checkbox/checkbox.svelte.d.ts +5 -1
  243. package/dist/components/ui/input/input.svelte +8 -6
  244. package/dist/components/ui/input/input.svelte.d.ts +4 -1
  245. package/dist/components/ui/select/select.svelte +4 -1
  246. package/dist/components/ui/select/select.svelte.d.ts +2 -1
  247. package/dist/components/ui/textarea/textarea.svelte +2 -1
  248. package/dist/components.css +3110 -0
  249. package/dist/date-input-policy.d.ts +4 -0
  250. package/dist/date-input-policy.js +35 -0
  251. package/dist/design-contract.d.ts +13 -0
  252. package/dist/design-contract.js +11 -0
  253. package/dist/design-system-components.d.ts +100 -0
  254. package/dist/design-system-components.js +101 -0
  255. package/dist/design-system.d.ts +15 -0
  256. package/dist/design-system.js +7 -0
  257. package/dist/devtools-bridge.d.ts +80 -0
  258. package/dist/devtools-bridge.js +92 -0
  259. package/dist/index.d.ts +50 -1
  260. package/dist/index.js +32 -2
  261. package/dist/page-chrome.css +12 -49
  262. package/dist/recipes.d.ts +960 -0
  263. package/dist/recipes.js +264 -0
  264. package/dist/rendering/index.d.ts +87 -0
  265. package/dist/rendering/index.js +117 -0
  266. package/dist/styles/aliases.css +49 -0
  267. package/dist/styles/compatibility.epilogue.css +384 -0
  268. package/dist/styles/compatibility.prelude.css +5513 -0
  269. package/dist/styles/conditional-states.css +28 -0
  270. package/dist/styles/conditional-utilities.css +22 -0
  271. package/dist/tree-table-model.d.ts +24 -0
  272. package/dist/tree-table-model.js +111 -0
  273. package/dist/utils.d.ts +1 -1
  274. package/dist/utils.js +1 -1
  275. package/package.json +40 -10
  276. package/styles-compatibility.json +12 -0
@@ -1,33 +1,17 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { cn } from '../../utils.js';
4
-
5
- interface Props {
6
- title: string;
7
- description?: string;
8
- actions?: Snippet;
9
- children: Snippet;
10
- class?: string;
11
- bodyClass?: string;
12
- }
13
-
14
- let {
15
- title,
16
- description,
17
- actions,
18
- children,
19
- class: className = '',
20
- bodyClass = '',
21
- }: Props = $props();
3
+ import { productSettingsRecipe as productSettings } from '../../recipes.js';
4
+ interface Props { title: string; description?: string; actions?: Snippet; children: Snippet; class?: string; bodyClass?: string; }
5
+ let { title, description, actions, children, class: className = '', bodyClass = '' }: Props = $props();
6
+ const styles = productSettings();
22
7
  </script>
23
-
24
- <section class={cn('svadmin-u-2cd02d11d1af svadmin-u-5f22e64f2282 svadmin-u-ca6bcd4b6f3f svadmin-u-18049387f0af svadmin-u-cd0ad9a56558 svadmin-u-438b2237b8d6', className)} data-svadmin-settings-group>
25
- <header class="svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-1004c0c3954c svadmin-u-65fdbade2025 svadmin-u-18049387f0af svadmin-u-d139dd09e38d svadmin-u-cb11fec3bb46 svadmin-u-020ba687fa12 svadmin-u-64cac80d2e9a svadmin-u-3b9871a0bf93">
26
- <div class="svadmin-u-7e0b7cdf1a94 svadmin-u-da7c36cd8867">
27
- <h2 class="svadmin-u-fc7473ca09eb svadmin-u-e83a7042bc91 svadmin-u-d4108abe6359">{title}</h2>
28
- {#if description}<p class="svadmin-u-2cc8041eca99 svadmin-u-fc7473ca09eb svadmin-u-18550d5945ae svadmin-u-bfa603190748">{description}</p>{/if}
8
+ <section class={styles.root + ' ' + className} data-svadmin-settings-group>
9
+ <header class={styles.header}>
10
+ <div class={styles.heading}>
11
+ <h2 class={styles.title}>{title}</h2>
12
+ {#if description}<p class={styles.description}>{description}</p>{/if}
29
13
  </div>
30
- {#if actions}<div class="svadmin-u-60fbb7713999 svadmin-u-012fbd121f37 svadmin-u-1eb5c6df38c1 svadmin-u-3960ffc248d9 svadmin-u-77a2a20e90d4">{@render actions()}</div>{/if}
14
+ {#if actions}<div class={styles.actions}>{@render actions()}</div>{/if}
31
15
  </header>
32
- <div class={cn('svadmin-u-c07e54fd1439', bodyClass)}>{@render children()}</div>
16
+ <div class={styles.body + ' ' + bodyClass}>{@render children()}</div>
33
17
  </section>
@@ -1,5 +1,6 @@
1
1
  <script lang="ts">
2
2
  import { Badge } from '../ui/badge/index.js';
3
+ import { productStatusRecipe as productStatus } from '../../recipes.js';
3
4
  export type Status = 'success' | 'warning' | 'danger' | 'info' | 'neutral';
4
5
  interface Props {
5
6
  status: Status;
@@ -7,13 +8,7 @@
7
8
  class?: string;
8
9
  }
9
10
  let { status, label = status, class: className = '' }: Props = $props();
10
- const toneClass: Record<Status, string> = {
11
- success: 'svadmin-u-18a6e7a36f29 svadmin-u-17a9f7af2265 svadmin-u-76747e5e02ff',
12
- warning: 'svadmin-u-2f960aa0c478 svadmin-u-283481e780bb svadmin-u-3a4ff758c2ab',
13
- danger: 'svadmin-u-9d5d8b4711b4 svadmin-u-43928fcc832f svadmin-u-811148b13d1e',
14
- info: 'svadmin-u-05f954a846d6 svadmin-u-375dc44df6e9 svadmin-u-20aaf08a7ed1',
15
- neutral: 'svadmin-u-18049387f0af svadmin-u-2ef11f1cb219 svadmin-u-bfa603190748',
16
- };
11
+ const styles = $derived(productStatus({ status }));
17
12
  </script>
18
13
 
19
- <Badge variant="outline" class={'svadmin-u-2689f3958069 ' + toneClass[status] + ' ' + className}>{label}</Badge>
14
+ <Badge variant="outline" data-svadmin-status={status} class={styles.root + ' ' + className}>{label}</Badge>
@@ -1,6 +1,6 @@
1
1
  <script lang="ts">
2
2
  import type { Snippet } from 'svelte';
3
- import { cn } from '../../utils.js';
3
+ import { productWorkspaceRecipe as productWorkspace } from '../../recipes.js';
4
4
 
5
5
  interface Props {
6
6
  primary: Snippet;
@@ -24,19 +24,15 @@
24
24
  class: className = '',
25
25
  }: Props = $props();
26
26
 
27
+ const styles = $derived(productWorkspace({ hasSecondary: Boolean(secondary) }));
27
28
  const resolvedSecondaryWidth = $derived(secondaryCollapsed ? secondaryCollapsedWidth : secondaryWidth);
28
29
  </script>
29
30
 
30
- <div
31
- class={cn('svadmin-u-60fbb7713999 svadmin-u-8dddea0773ed svadmin-u-0d304f904cb0', className)}
32
- data-svadmin-workspace-layout
33
- data-secondary-collapsed={secondaryCollapsed}
34
- style:--workspace-secondary-width={resolvedSecondaryWidth}
35
- >
36
- {#if summary}<div data-svadmin-workspace-summary>{@render summary()}</div>{/if}
37
- <div class={cn('svadmin-u-f3c543ad5fe9 svadmin-u-7e0b7cdf1a94 svadmin-u-60541e1e26f8 svadmin-u-0d304f904cb0 svadmin-u-febbcd95bf06', mobileOrder === 'secondary-first' && 'svadmin-u-dfb5e722be57')}>
38
- {#if mobileOrder === 'secondary-first' && secondary}<aside class="svadmin-u-ea37b42b1c3b svadmin-u-7e0b7cdf1a94 svadmin-u-2c1f5e2b6a5b">{@render secondary()}</aside>{/if}
39
- <div class={cn('svadmin-u-7e0b7cdf1a94', mobileOrder === 'secondary-first' ? 'svadmin-u-ef72121aca75 svadmin-u-831f8770959d' : 'svadmin-u-ea37b42b1c3b')} data-svadmin-workspace-primary>{@render primary()}</div>
40
- {#if mobileOrder !== 'secondary-first' && secondary}<aside class="svadmin-u-ef72121aca75 svadmin-u-7e0b7cdf1a94 svadmin-u-2c1f5e2b6a5b">{@render secondary()}</aside>{/if}
31
+ <div class={styles.root + ' ' + className} data-svadmin-workspace-layout data-secondary-collapsed={secondaryCollapsed} style:--workspace-secondary-width={resolvedSecondaryWidth}>
32
+ {#if summary}<div class={styles.summary} data-svadmin-workspace-summary>{@render summary()}</div>{/if}
33
+ <div class={styles.columns}>
34
+ {#if mobileOrder === 'secondary-first' && secondary}<aside class={styles.secondary} data-svadmin-workspace-secondary>{@render secondary()}</aside>{/if}
35
+ <div class={styles.primary} data-svadmin-workspace-primary>{@render primary()}</div>
36
+ {#if mobileOrder !== 'secondary-first' && secondary}<aside class={styles.secondary} data-svadmin-workspace-secondary>{@render secondary()}</aside>{/if}
41
37
  </div>
42
38
  </div>
@@ -0,0 +1,48 @@
1
+ import type { CrudOperator, FieldDefinition, Filter } from '@svadmin/core';
2
+ export declare const FILTER_EDITOR_LIMITS: Readonly<{
3
+ depth: 12;
4
+ nodes: 256;
5
+ values: 1000;
6
+ }>;
7
+ export interface FilterRuleNode {
8
+ kind: 'rule';
9
+ id: string;
10
+ field: string;
11
+ operator: CrudOperator;
12
+ value: unknown;
13
+ readonly?: boolean;
14
+ }
15
+ export interface FilterGroupNode {
16
+ kind: 'group';
17
+ id: string;
18
+ operator: 'and' | 'or';
19
+ children: FilterNode[];
20
+ wrapped?: boolean;
21
+ }
22
+ export type FilterNode = FilterRuleNode | FilterGroupNode;
23
+ export type FilterIssueCode = 'invalid-filter' | 'limit' | 'unknown-field' | 'operator' | 'value' | 'empty-group';
24
+ export interface FilterEditorIssue {
25
+ path: string;
26
+ code: FilterIssueCode;
27
+ }
28
+ export type FilterTreeRead = {
29
+ ok: true;
30
+ root: FilterGroupNode;
31
+ } | {
32
+ ok: false;
33
+ issues: FilterEditorIssue[];
34
+ };
35
+ export type FilterTreeCompile = {
36
+ ok: true;
37
+ filters: Filter[];
38
+ } | {
39
+ ok: false;
40
+ issues: FilterEditorIssue[];
41
+ };
42
+ export declare const isCollectionOperator: (operator: CrudOperator) => boolean;
43
+ export declare const isNullOperator: (operator: CrudOperator) => boolean;
44
+ export declare function filterOperators(field: Pick<FieldDefinition, 'type'> | undefined): CrudOperator[];
45
+ /** Filter[] 的顶层是隐式 AND;只有单个显式组可以成为编辑器根组,且保留包装信息。 */
46
+ export declare function readFilterTree(input: unknown): FilterTreeRead;
47
+ export declare function compileFilterTree(root: FilterGroupNode, fields: readonly FieldDefinition[], preserved?: ReadonlyMap<string, Filter>): FilterTreeCompile;
48
+ export declare function parseFilterInput(raw: string, field: Pick<FieldDefinition, 'type'> | undefined, operator: CrudOperator): unknown;
@@ -0,0 +1,224 @@
1
+ export const FILTER_EDITOR_LIMITS = Object.freeze({ depth: 12, nodes: 256, values: 1000 });
2
+ const operators = ['eq', 'ne', 'lt', 'gt', 'lte', 'gte', 'contains', 'ncontains', 'startswith', 'endswith', 'in', 'nin', 'null', 'nnull', 'between', 'nbetween'];
3
+ const comparison = ['eq', 'ne', 'lt', 'gt', 'lte', 'gte', 'in', 'nin', 'between', 'nbetween', 'null', 'nnull'];
4
+ const selection = ['eq', 'ne', 'in', 'nin', 'null', 'nnull'];
5
+ const text = ['eq', 'ne', 'contains', 'ncontains', 'startswith', 'endswith', 'in', 'nin', 'null', 'nnull'];
6
+ const scalar = (value) => value === null || typeof value === 'string' || typeof value === 'boolean' || (typeof value === 'number' && Number.isFinite(value));
7
+ const object = (value) => value !== null && typeof value === 'object' && !Array.isArray(value);
8
+ export const isCollectionOperator = (operator) => ['in', 'nin', 'between', 'nbetween'].includes(operator);
9
+ export const isNullOperator = (operator) => operator === 'null' || operator === 'nnull';
10
+ export function filterOperators(field) {
11
+ if (!field)
12
+ return [];
13
+ if (field.type === 'number' || field.type === 'date')
14
+ return [...comparison];
15
+ if (field.type === 'boolean' || field.type === 'select' || field.type === 'multiselect')
16
+ return [...selection];
17
+ return [...text];
18
+ }
19
+ /** Filter[] 的顶层是隐式 AND;只有单个显式组可以成为编辑器根组,且保留包装信息。 */
20
+ export function readFilterTree(input) {
21
+ let count = 0;
22
+ const active = new Set();
23
+ const issues = [];
24
+ function read(raw, depth, path) {
25
+ if (++count > FILTER_EDITOR_LIMITS.nodes || depth > FILTER_EDITOR_LIMITS.depth) {
26
+ issues.push({ path, code: 'limit' });
27
+ return;
28
+ }
29
+ if (!object(raw) || active.has(raw)) {
30
+ issues.push({ path, code: 'invalid-filter' });
31
+ return;
32
+ }
33
+ active.add(raw);
34
+ try {
35
+ const id = `node-${count}`;
36
+ if ('field' in raw) {
37
+ if (typeof raw['field'] !== 'string' || !raw['field'] || !operators.includes(raw['operator'])
38
+ || Object.keys(raw).some((key) => !['field', 'operator', 'value'].includes(key))) {
39
+ issues.push({ path, code: 'invalid-filter' });
40
+ return;
41
+ }
42
+ const value = raw['value'];
43
+ if (!scalar(value) && !(Array.isArray(value) && value.length <= FILTER_EDITOR_LIMITS.values && Array.from(value).every(scalar))) {
44
+ issues.push({ path, code: 'value' });
45
+ return;
46
+ }
47
+ return { kind: 'rule', id, field: raw['field'], operator: raw['operator'], value: Array.isArray(value) ? [...value] : value };
48
+ }
49
+ if (!['and', 'or'].includes(String(raw['operator'])) || !Array.isArray(raw['value'])
50
+ || Object.keys(raw).some((key) => !['operator', 'value'].includes(key))) {
51
+ issues.push({ path, code: 'invalid-filter' });
52
+ return;
53
+ }
54
+ if (raw['value'].length > FILTER_EDITOR_LIMITS.nodes) {
55
+ issues.push({ path, code: 'limit' });
56
+ return;
57
+ }
58
+ const children = [];
59
+ for (const [index, child] of raw['value'].entries()) {
60
+ const node = read(child, depth + 1, `${path}/value/${index}`);
61
+ if (node)
62
+ children.push(node);
63
+ }
64
+ return { kind: 'group', id, operator: raw['operator'], children };
65
+ }
66
+ finally {
67
+ active.delete(raw);
68
+ }
69
+ }
70
+ if (!Array.isArray(input))
71
+ return { ok: false, issues: [{ path: '', code: 'invalid-filter' }] };
72
+ if (input.length > FILTER_EDITOR_LIMITS.nodes)
73
+ return { ok: false, issues: [{ path: '', code: 'limit' }] };
74
+ const children = [];
75
+ for (const [index, raw] of input.entries()) {
76
+ const node = read(raw, 1, `/${index}`);
77
+ if (node)
78
+ children.push(node);
79
+ }
80
+ if (issues.length)
81
+ return { ok: false, issues };
82
+ const only = children[0];
83
+ return { ok: true, root: children.length === 1 && only?.kind === 'group'
84
+ ? { ...only, wrapped: true }
85
+ : { kind: 'group', id: 'root', operator: 'and', children, wrapped: false } };
86
+ }
87
+ export function compileFilterTree(root, fields, preserved = new Map()) {
88
+ const issues = [];
89
+ const fieldMap = new Map(fields.map((field) => [field.key, field]));
90
+ const active = new Set();
91
+ let count = 0;
92
+ function visit(node, depth) {
93
+ const issue = (code) => { issues.push({ path: node.id, code }); };
94
+ if (++count > FILTER_EDITOR_LIMITS.nodes || depth > FILTER_EDITOR_LIMITS.depth) {
95
+ issue('limit');
96
+ return;
97
+ }
98
+ if (active.has(node)) {
99
+ issue('invalid-filter');
100
+ return;
101
+ }
102
+ active.add(node);
103
+ try {
104
+ // Only unchanged host-owned readonly leaves and originally empty groups may bypass
105
+ // editor capability checks. A forged readonly flag or edited payload is not sufficient.
106
+ const original = preserved.get(node.id);
107
+ if (original && node.kind === 'rule' && node.readonly && 'field' in original
108
+ && node.field === original.field && node.operator === original.operator
109
+ && JSON.stringify(node.value) === JSON.stringify(original.value)) {
110
+ return { ...original, value: Array.isArray(original.value) ? [...original.value] : original.value };
111
+ }
112
+ if (original && node.kind === 'group' && !('field' in original)
113
+ && !node.children.length && !original.value.length && node.operator === original.operator) {
114
+ return { operator: node.operator, value: [] };
115
+ }
116
+ if (node.kind === 'group') {
117
+ if (node.operator !== 'and' && node.operator !== 'or') {
118
+ issue('operator');
119
+ return;
120
+ }
121
+ if (!node.children.length)
122
+ issue('empty-group');
123
+ if (node.children.length > FILTER_EDITOR_LIMITS.nodes) {
124
+ issue('limit');
125
+ return;
126
+ }
127
+ const children = [];
128
+ for (const child of node.children) {
129
+ const filter = visit(child, depth + 1);
130
+ if (filter)
131
+ children.push(filter);
132
+ }
133
+ return { operator: node.operator, value: children };
134
+ }
135
+ const field = fieldMap.get(node.field);
136
+ if (!field || field.filterable === false) {
137
+ issue('unknown-field');
138
+ return;
139
+ }
140
+ if (!filterOperators(field).includes(node.operator)) {
141
+ issue('operator');
142
+ return;
143
+ }
144
+ const validScalar = (value) => {
145
+ if (!scalar(value) || value === null)
146
+ return false;
147
+ if (field.type === 'number')
148
+ return typeof value === 'number';
149
+ if (field.type === 'boolean')
150
+ return typeof value === 'boolean';
151
+ if (field.options?.length)
152
+ return field.options.some((option) => !option.disabled && option.value === value);
153
+ return typeof value === 'string';
154
+ };
155
+ if (isNullOperator(node.operator)) {
156
+ if (node.value !== null) {
157
+ issue('value');
158
+ return;
159
+ }
160
+ }
161
+ else if (isCollectionOperator(node.operator)) {
162
+ if (!Array.isArray(node.value) || node.value.length === 0 || node.value.length > FILTER_EDITOR_LIMITS.values || !Array.from(node.value).every(validScalar)
163
+ || ((node.operator === 'between' || node.operator === 'nbetween') && node.value.length !== 2)) {
164
+ issue('value');
165
+ return;
166
+ }
167
+ if ((node.operator === 'between' || node.operator === 'nbetween') && typeof node.value[0] === 'number' && typeof node.value[1] === 'number' && node.value[0] > node.value[1]) {
168
+ issue('value');
169
+ return;
170
+ }
171
+ }
172
+ else if (!validScalar(node.value)) {
173
+ issue('value');
174
+ return;
175
+ }
176
+ return { field: node.field, operator: node.operator, value: Array.isArray(node.value) ? [...node.value] : node.value };
177
+ }
178
+ finally {
179
+ active.delete(node);
180
+ }
181
+ }
182
+ if (root.children.length > FILTER_EDITOR_LIMITS.nodes)
183
+ return { ok: false, issues: [{ path: root.id, code: 'limit' }] };
184
+ if (!root.children.length && !root.wrapped)
185
+ return { ok: true, filters: [] };
186
+ // 隐式根不占公开 Filter 的深度或节点预算;显式根与读入时一致。
187
+ let filters = [];
188
+ if (root.operator === 'and' && !root.wrapped) {
189
+ for (const child of root.children) {
190
+ const filter = visit(child, 1);
191
+ if (filter)
192
+ filters.push(filter);
193
+ }
194
+ }
195
+ else {
196
+ const filter = visit(root, 1);
197
+ if (filter)
198
+ filters = [filter];
199
+ }
200
+ return issues.length ? { ok: false, issues } : { ok: true, filters };
201
+ }
202
+ export function parseFilterInput(raw, field, operator) {
203
+ if (raw.length > 1_048_576)
204
+ throw new Error('value');
205
+ if (isNullOperator(operator))
206
+ return null;
207
+ if (isCollectionOperator(operator))
208
+ return raw.trim() ? JSON.parse(raw) : undefined;
209
+ if (field?.type === 'number') {
210
+ if (!raw.trim())
211
+ return undefined;
212
+ if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(raw.trim()) || !Number.isFinite(Number(raw)))
213
+ throw new Error('value');
214
+ return Number(raw);
215
+ }
216
+ if (field?.type === 'boolean') {
217
+ if (raw === '')
218
+ return undefined;
219
+ if (raw !== 'true' && raw !== 'false')
220
+ throw new Error('value');
221
+ return raw === 'true';
222
+ }
223
+ return raw;
224
+ }
@@ -0,0 +1,39 @@
1
+ /** JsonSchemaForm 当前支持的标量子集。未知断言必须显式拒绝,不能假装已校验。 */
2
+ export type JsonScalar = string | number | boolean | null;
3
+ export type SchemaIssueCode = 'unsupported-schema' | 'invalid-schema' | 'required' | 'type' | 'enum' | 'minimum' | 'maximum' | 'multipleOf' | 'minLength' | 'maxLength' | 'additionalProperties' | 'invalid-value' | 'minItems' | 'maxItems';
4
+ export interface SchemaFormIssue {
5
+ path: string;
6
+ code: SchemaIssueCode;
7
+ }
8
+ export interface SchemaFormField {
9
+ key: string;
10
+ title: string;
11
+ description: string;
12
+ type: 'string' | 'number' | 'integer' | 'boolean' | 'enum';
13
+ nullable: boolean;
14
+ required: boolean;
15
+ readonly: boolean;
16
+ choices: JsonScalar[] | undefined;
17
+ hasDefault: boolean;
18
+ defaultValue: JsonScalar | undefined;
19
+ minimum: number | undefined;
20
+ maximum: number | undefined;
21
+ exclusiveMinimum: number | undefined;
22
+ exclusiveMaximum: number | undefined;
23
+ multipleOf: number | undefined;
24
+ minLength: number | undefined;
25
+ maxLength: number | undefined;
26
+ }
27
+ export interface SchemaFormModel {
28
+ fields: SchemaFormField[];
29
+ issues: SchemaFormIssue[];
30
+ additionalProperties: boolean;
31
+ }
32
+ export declare function readSchemaForm(schema: Record<string, unknown>): SchemaFormModel;
33
+ /** 只为不存在的键补默认值;显式 undefined 表示用户已清空,不重新灌回默认值。 */
34
+ export declare function schemaFormValues(model: SchemaFormModel, value: Record<string, unknown>): Record<string, unknown>;
35
+ export declare function parseSchemaNumber(raw: string): number | undefined;
36
+ export declare function enumIndex(choices: readonly JsonScalar[], value: unknown): string;
37
+ export declare function enumValue(choices: readonly JsonScalar[], index: string): JsonScalar | undefined;
38
+ export declare function validateSchemaForm(model: SchemaFormModel, value: Record<string, unknown>): SchemaFormIssue[];
39
+ export { schemaFormSnapshot } from '../json-schema-form-state.js';
@@ -0,0 +1,195 @@
1
+ const rootKeys = new Set(['$schema', '$id', '$comment', 'title', 'description', 'type', 'properties', 'required', 'additionalProperties']);
2
+ const fieldKeys = new Set(['$id', '$comment', 'title', 'description', 'type', 'enum', 'const', 'default', 'minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf', 'minLength', 'maxLength', 'readOnly', 'writeOnly', 'deprecated', 'examples']);
3
+ const unsafeKeys = new Set(['__proto__', 'constructor', 'prototype']);
4
+ const pointer = (key) => `/${key.replaceAll('~', '~0').replaceAll('/', '~1')}`;
5
+ const record = (value) => value !== null && typeof value === 'object' && !Array.isArray(value) && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
6
+ const scalar = (value) => value === null || typeof value === 'string' || typeof value === 'boolean' || (typeof value === 'number' && Number.isFinite(value));
7
+ const same = (a, b) => a === b;
8
+ function matches(field, value) {
9
+ if (value === null)
10
+ return field.nullable;
11
+ if (field.type === 'enum')
12
+ return scalar(value);
13
+ if (field.type === 'integer')
14
+ return typeof value === 'number' && Number.isSafeInteger(value);
15
+ if (field.type === 'number')
16
+ return typeof value === 'number' && Number.isFinite(value);
17
+ return typeof value === field.type;
18
+ }
19
+ export function readSchemaForm(schema) {
20
+ const model = { fields: [], issues: [], additionalProperties: true };
21
+ const issue = (path, code) => { model.issues.push({ path, code }); };
22
+ if (!record(schema)) {
23
+ issue('', 'invalid-schema');
24
+ return model;
25
+ }
26
+ for (const key of Object.keys(schema))
27
+ if (!rootKeys.has(key))
28
+ issue(pointer(key), 'unsupported-schema');
29
+ if (schema['type'] !== undefined && schema['type'] !== 'object')
30
+ issue('/type', 'unsupported-schema');
31
+ if (schema['additionalProperties'] !== undefined && typeof schema['additionalProperties'] !== 'boolean')
32
+ issue('/additionalProperties', 'unsupported-schema');
33
+ model.additionalProperties = schema['additionalProperties'] !== false;
34
+ const properties = schema['properties'] === undefined ? {} : schema['properties'];
35
+ if (!record(properties) || Object.keys(properties).length > 256) {
36
+ issue('/properties', 'invalid-schema');
37
+ return model;
38
+ }
39
+ const required = schema['required'] === undefined ? [] : schema['required'];
40
+ if (!Array.isArray(required) || required.length > 256 || required.some((key) => typeof key !== 'string' || !Object.hasOwn(properties, key)))
41
+ issue('/required', 'invalid-schema');
42
+ const requiredKeys = new Set(Array.isArray(required) ? required : []);
43
+ for (const [key, raw] of Object.entries(properties)) {
44
+ const path = pointer(key);
45
+ if (unsafeKeys.has(key) || !record(raw)) {
46
+ issue(path, 'invalid-schema');
47
+ continue;
48
+ }
49
+ for (const keyword of Object.keys(raw))
50
+ if (!fieldKeys.has(keyword))
51
+ issue(`${path}${pointer(keyword)}`, 'unsupported-schema');
52
+ let type = raw['type'];
53
+ let nullable = false;
54
+ if (Array.isArray(type)) {
55
+ nullable = type.includes('null');
56
+ const nonNull = type.filter((item) => item !== 'null');
57
+ if (type.length !== 2 || nonNull.length !== 1)
58
+ issue(path, 'unsupported-schema');
59
+ type = nonNull[0];
60
+ }
61
+ const hasEnum = Object.hasOwn(raw, 'enum');
62
+ const hasConst = Object.hasOwn(raw, 'const');
63
+ const rawChoices = hasEnum ? raw['enum'] : hasConst ? [raw['const']] : undefined;
64
+ let choices;
65
+ if (rawChoices !== undefined) {
66
+ if (!Array.isArray(rawChoices) || rawChoices.length === 0 || rawChoices.length > 1000 || !rawChoices.every(scalar))
67
+ issue(path, 'invalid-schema');
68
+ else {
69
+ choices = [...rawChoices];
70
+ if (hasConst)
71
+ choices = choices.filter((item) => same(item, raw['const']));
72
+ if (!choices.length)
73
+ issue(path, 'invalid-schema');
74
+ }
75
+ }
76
+ else if (hasEnum)
77
+ issue(path, 'invalid-schema');
78
+ if (type === undefined)
79
+ type = choices ? 'enum' : 'string';
80
+ else if (type === 'enum') {
81
+ issue(path, 'invalid-schema');
82
+ continue;
83
+ }
84
+ if (!['string', 'number', 'integer', 'boolean', 'enum'].includes(String(type))) {
85
+ issue(path, 'unsupported-schema');
86
+ continue;
87
+ }
88
+ if (type === 'enum')
89
+ nullable = choices?.includes(null) ?? false;
90
+ const field = {
91
+ key, title: String(raw['title'] ?? key), description: String(raw['description'] ?? ''),
92
+ type: type, nullable, required: requiredKeys.has(key), readonly: raw['readOnly'] === true,
93
+ choices, hasDefault: Object.hasOwn(raw, 'default'), defaultValue: scalar(raw['default']) ? raw['default'] : undefined,
94
+ minimum: undefined, maximum: undefined, exclusiveMinimum: undefined, exclusiveMaximum: undefined,
95
+ multipleOf: undefined, minLength: undefined, maxLength: undefined,
96
+ };
97
+ for (const constraint of ['minimum', 'maximum', 'exclusiveMinimum', 'exclusiveMaximum', 'multipleOf', 'minLength', 'maxLength']) {
98
+ const value = raw[constraint];
99
+ if (value === undefined)
100
+ continue;
101
+ if (typeof value !== 'number' || !Number.isFinite(value)
102
+ || (constraint === 'multipleOf' && value <= 0)
103
+ || ((constraint === 'minLength' || constraint === 'maxLength') && (!Number.isSafeInteger(value) || value < 0)))
104
+ issue(`${path}/${constraint}`, 'invalid-schema');
105
+ else
106
+ field[constraint] = value;
107
+ }
108
+ if (choices?.some((choice) => !matches(field, choice)))
109
+ issue(path, 'invalid-schema');
110
+ if (field.hasDefault && (!scalar(raw['default']) || !matches(field, raw['default']) || (choices && !choices.some((choice) => same(choice, raw['default'])))))
111
+ issue(path, 'invalid-schema');
112
+ model.fields.push(field);
113
+ }
114
+ return model;
115
+ }
116
+ /** 只为不存在的键补默认值;显式 undefined 表示用户已清空,不重新灌回默认值。 */
117
+ export function schemaFormValues(model, value) {
118
+ const result = { ...value };
119
+ for (const field of model.fields)
120
+ if (!Object.hasOwn(value, field.key) && field.hasDefault)
121
+ result[field.key] = field.defaultValue;
122
+ return result;
123
+ }
124
+ export function parseSchemaNumber(raw) {
125
+ if (raw.trim() === '')
126
+ return undefined;
127
+ if (!/^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i.test(raw.trim()))
128
+ throw new Error('invalid-value');
129
+ const value = Number(raw);
130
+ if (!Number.isFinite(value))
131
+ throw new Error('invalid-value');
132
+ return value;
133
+ }
134
+ export function enumIndex(choices, value) {
135
+ const index = choices.findIndex((choice) => same(choice, value));
136
+ return index < 0 ? '' : String(index);
137
+ }
138
+ export function enumValue(choices, index) {
139
+ if (index === '')
140
+ return undefined;
141
+ if (!/^(0|[1-9]\d*)$/.test(index))
142
+ throw new Error('invalid-value');
143
+ const value = choices[Number(index)];
144
+ if (value === undefined)
145
+ throw new Error('invalid-value');
146
+ return value;
147
+ }
148
+ export function validateSchemaForm(model, value) {
149
+ const issues = [...model.issues];
150
+ if (!record(value))
151
+ return [...issues, { path: '', code: 'invalid-value' }];
152
+ for (const field of model.fields) {
153
+ const path = pointer(field.key), current = Object.hasOwn(value, field.key) ? value[field.key] : undefined;
154
+ const issue = (code) => { issues.push({ path, code }); };
155
+ if (current === undefined) {
156
+ if (field.required)
157
+ issue('required');
158
+ continue;
159
+ }
160
+ if (!matches(field, current)) {
161
+ issue('type');
162
+ continue;
163
+ }
164
+ if (field.choices && !field.choices.some((choice) => same(choice, current)))
165
+ issue('enum');
166
+ if (typeof current === 'number') {
167
+ if ((field.minimum !== undefined && current < field.minimum) || (field.exclusiveMinimum !== undefined && current <= field.exclusiveMinimum))
168
+ issue('minimum');
169
+ if ((field.maximum !== undefined && current > field.maximum) || (field.exclusiveMaximum !== undefined && current >= field.exclusiveMaximum))
170
+ issue('maximum');
171
+ if (field.multipleOf !== undefined) {
172
+ const quotient = current / field.multipleOf;
173
+ if (!Number.isFinite(quotient) || Math.abs(quotient - Math.round(quotient)) > Math.min(1e-8, Number.EPSILON * Math.max(1, Math.abs(quotient)) * 4))
174
+ issue('multipleOf');
175
+ }
176
+ }
177
+ if (typeof current === 'string') {
178
+ const length = Array.from(current).length;
179
+ if (field.minLength !== undefined && length < field.minLength)
180
+ issue('minLength');
181
+ if (field.maxLength !== undefined && length > field.maxLength)
182
+ issue('maxLength');
183
+ }
184
+ }
185
+ const keys = new Set(model.fields.map((field) => field.key));
186
+ for (const [key, current] of Object.entries(value)) {
187
+ if (unsafeKeys.has(key))
188
+ issues.push({ path: pointer(key), code: 'invalid-value' });
189
+ if (!model.additionalProperties && !keys.has(key) && current !== undefined)
190
+ issues.push({ path: pointer(key), code: 'additionalProperties' });
191
+ }
192
+ return issues;
193
+ }
194
+ // Reuse the hardened recursive form transport boundary, including accessor/symbol rejection.
195
+ export { schemaFormSnapshot } from '../json-schema-form-state.js';
@@ -0,0 +1,30 @@
1
+ import { type SchemaFormModel, type SchemaFormIssue } from './json-schema-form.js';
2
+ type Node = {
3
+ kind: 'object';
4
+ properties: Record<string, Node>;
5
+ required: Set<string>;
6
+ additional: boolean;
7
+ nullable: boolean;
8
+ } | {
9
+ kind: 'array';
10
+ item: Node;
11
+ min: number | undefined;
12
+ max: number | undefined;
13
+ nullable: boolean;
14
+ } | {
15
+ kind: 'scalar';
16
+ model: SchemaFormModel;
17
+ };
18
+ export interface RecursiveSchemaFormModel {
19
+ node: Node | undefined;
20
+ issues: SchemaFormIssue[];
21
+ }
22
+ export declare const schemaFormPointer: (path: readonly string[]) => string;
23
+ /** Compile bounded objects/arrays, delegating scalar assertions to the reviewed leaf validator.
24
+ * Unsupported assertions fail closed; this is deliberately not a complete JSON Schema engine.
25
+ * TypeBox's symbol annotations are not JSON assertion keywords and are not executed.
26
+ */
27
+ export declare function readRecursiveSchemaForm(schema: unknown): RecursiveSchemaFormModel;
28
+ /** Validate a detached JSON payload without defaults, coercion, cleaning or partial acceptance. */
29
+ export declare function validateRecursiveSchemaForm(model: RecursiveSchemaFormModel, value: unknown): SchemaFormIssue[];
30
+ export {};