@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
@@ -0,0 +1,196 @@
1
+ import { readSchemaForm, validateSchemaForm } from './json-schema-form.js';
2
+ import { SCHEMA_FORM_LIMITS, schemaFormSnapshot } from '../json-schema-form-state.js';
3
+ const annotationKeys = ['$schema', '$id', '$comment', 'title', 'description', 'type', 'default', 'readOnly', 'writeOnly', 'deprecated', 'examples'];
4
+ const objectKeys = new Set([...annotationKeys, 'properties', 'required', 'additionalProperties']);
5
+ const arrayKeys = new Set([...annotationKeys, 'items', 'minItems', 'maxItems']);
6
+ const unsafe = new Set(['__proto__', 'prototype', 'constructor']);
7
+ export const schemaFormPointer = (path) => path.map(key => '/' + key.replaceAll('~', '~0').replaceAll('/', '~1')).join('');
8
+ function record(value) {
9
+ return value !== null && typeof value === 'object' && !Array.isArray(value)
10
+ && (Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null);
11
+ }
12
+ function dataRecord(value) {
13
+ return record(value) && Object.keys(value).every(key => {
14
+ const descriptor = Object.getOwnPropertyDescriptor(value, key);
15
+ return descriptor !== undefined && 'value' in descriptor;
16
+ });
17
+ }
18
+ /** Compile bounded objects/arrays, delegating scalar assertions to the reviewed leaf validator.
19
+ * Unsupported assertions fail closed; this is deliberately not a complete JSON Schema engine.
20
+ * TypeBox's symbol annotations are not JSON assertion keywords and are not executed.
21
+ */
22
+ export function readRecursiveSchemaForm(schema) {
23
+ const issues = [];
24
+ const active = new Set();
25
+ let visited = 0;
26
+ const issue = (path, code) => {
27
+ if (issues.length < 128)
28
+ issues.push({ path, code });
29
+ };
30
+ function read(raw, path, depth, root = false) {
31
+ if (++visited > SCHEMA_FORM_LIMITS.nodes || depth > SCHEMA_FORM_LIMITS.depth || !dataRecord(raw) || active.has(raw)) {
32
+ issue(path, 'invalid-schema');
33
+ return;
34
+ }
35
+ active.add(raw);
36
+ 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')) {
42
+ issue(path, 'unsupported-schema');
43
+ return;
44
+ }
45
+ nullable = true;
46
+ type = concrete[0];
47
+ }
48
+ if (type === undefined && (root || Object.hasOwn(raw, 'properties')))
49
+ type = 'object';
50
+ if (root && type !== 'object') {
51
+ issue(path, 'unsupported-schema');
52
+ return;
53
+ }
54
+ for (const key of ['readOnly', 'writeOnly', 'deprecated']) {
55
+ if (raw[key] !== undefined && typeof raw[key] !== 'boolean')
56
+ issue(path + '/' + key, 'invalid-schema');
57
+ }
58
+ if (Object.hasOwn(raw, 'default')) {
59
+ try {
60
+ schemaFormSnapshot({ value: raw['default'] });
61
+ }
62
+ catch {
63
+ issue(path + '/default', 'invalid-schema');
64
+ }
65
+ }
66
+ if (type !== 'object' && type !== 'array') {
67
+ const model = readSchemaForm({ type: 'object', properties: { value: raw } });
68
+ for (const failure of model.issues)
69
+ issue(path + failure.path.replace(/^\/value(?=\/|$)/u, ''), failure.code);
70
+ return { kind: 'scalar', model };
71
+ }
72
+ const allowed = type === 'object' ? objectKeys : arrayKeys;
73
+ for (const key of Object.keys(raw))
74
+ if (!allowed.has(key))
75
+ issue(path + '/' + key, 'unsupported-schema');
76
+ if (type === 'object') {
77
+ const properties = raw['properties'] ?? {};
78
+ if (!dataRecord(properties) || Object.keys(properties).length > 256 || raw['properties'] === null) {
79
+ issue(path + '/properties', 'invalid-schema');
80
+ return;
81
+ }
82
+ const keys = raw['required'] ?? [];
83
+ if (!Array.isArray(keys) || keys.length > 256 || raw['required'] === null
84
+ || new Set(keys).size !== keys.length || keys.some(key => typeof key !== 'string' || !Object.hasOwn(properties, key))) {
85
+ issue(path + '/required', 'invalid-schema');
86
+ return;
87
+ }
88
+ if (raw['additionalProperties'] !== undefined && typeof raw['additionalProperties'] !== 'boolean') {
89
+ issue(path + '/additionalProperties', 'unsupported-schema');
90
+ }
91
+ const result = Object.create(null);
92
+ for (const [key, child] of Object.entries(properties)) {
93
+ const childPath = path + schemaFormPointer([key]);
94
+ if (unsafe.has(key)) {
95
+ issue(childPath, 'invalid-schema');
96
+ continue;
97
+ }
98
+ const node = read(child, childPath, depth + 1);
99
+ if (node)
100
+ result[key] = node;
101
+ }
102
+ return { kind: 'object', properties: result, required: new Set(keys), additional: raw['additionalProperties'] !== false, nullable };
103
+ }
104
+ const item = read(raw['items'], path + '/items', depth + 1);
105
+ const definition = raw;
106
+ function bound(key) {
107
+ const value = definition[key];
108
+ if (value === undefined)
109
+ return;
110
+ if (typeof value !== 'number' || !Number.isSafeInteger(value) || value < 0) {
111
+ issue(path + '/' + key, 'invalid-schema');
112
+ return;
113
+ }
114
+ return value;
115
+ }
116
+ const min = bound('minItems'), max = bound('maxItems');
117
+ if (min !== undefined && max !== undefined && min > max)
118
+ issue(path, 'invalid-schema');
119
+ return item ? { kind: 'array', item, min, max, nullable } : undefined;
120
+ }
121
+ finally {
122
+ active.delete(raw);
123
+ }
124
+ }
125
+ let node;
126
+ try {
127
+ node = read(schema, '', 0, true);
128
+ }
129
+ catch {
130
+ issue('', 'invalid-schema');
131
+ }
132
+ return { node, issues };
133
+ }
134
+ /** Validate a detached JSON payload without defaults, coercion, cleaning or partial acceptance. */
135
+ export function validateRecursiveSchemaForm(model, value) {
136
+ if (model.issues.length || !model.node)
137
+ return model.issues.length ? [...model.issues] : [{ path: '', code: 'invalid-schema' }];
138
+ const issues = [];
139
+ const issue = (path, code) => {
140
+ if (issues.length < 128)
141
+ issues.push({ path, code });
142
+ };
143
+ let payload;
144
+ try {
145
+ payload = schemaFormSnapshot(value);
146
+ }
147
+ catch {
148
+ return [{ path: '', code: 'invalid-value' }];
149
+ }
150
+ let visited = 0;
151
+ function visit(node, current, path, required, depth) {
152
+ if (++visited > SCHEMA_FORM_LIMITS.nodes || depth > SCHEMA_FORM_LIMITS.depth) {
153
+ issue(path, 'invalid-value');
154
+ return;
155
+ }
156
+ if (current === undefined) {
157
+ if (required)
158
+ issue(path, 'required');
159
+ return;
160
+ }
161
+ if (node.kind === 'scalar') {
162
+ for (const failure of validateSchemaForm(node.model, { value: current }))
163
+ issue(path + failure.path.replace(/^\/value(?=\/|$)/u, ''), failure.code);
164
+ return;
165
+ }
166
+ if (current === null && node.nullable)
167
+ return;
168
+ if (node.kind === 'object') {
169
+ if (!record(current)) {
170
+ issue(path, 'type');
171
+ return;
172
+ }
173
+ for (const [key, child] of Object.entries(node.properties)) {
174
+ visit(child, Object.hasOwn(current, key) ? current[key] : undefined, path + schemaFormPointer([key]), node.required.has(key), depth + 1);
175
+ }
176
+ if (!node.additional)
177
+ for (const key of Object.keys(current)) {
178
+ if (!Object.hasOwn(node.properties, key))
179
+ issue(path + schemaFormPointer([key]), 'additionalProperties');
180
+ }
181
+ return;
182
+ }
183
+ if (!Array.isArray(current)) {
184
+ issue(path, 'type');
185
+ return;
186
+ }
187
+ if (node.min !== undefined && current.length < node.min)
188
+ issue(path, 'minItems');
189
+ if (node.max !== undefined && current.length > node.max)
190
+ issue(path, 'maxItems');
191
+ for (const [index, item] of current.entries())
192
+ visit(node.item, item, path + '/' + index, true, depth + 1);
193
+ }
194
+ visit(model.node, payload, '', true, 0);
195
+ return issues;
196
+ }
@@ -0,0 +1,12 @@
1
+ /** 有界、无动态代码执行的轻量公式解析器;不是完整 Excel 公式引擎。 */
2
+ export declare const FORMULA_LIMITS: Readonly<{
3
+ length: 2048;
4
+ tokens: 1024;
5
+ depth: 64;
6
+ work: 10000;
7
+ range: 10000;
8
+ }>;
9
+ export type FormulaErrorCode = '#VALUE!' | '#REF!' | '#NAME?' | '#DIV/0!' | '#NUM!' | '#CYCLE!' | '#LIMIT!';
10
+ export declare function evaluateFormula(raw: string, cells: Readonly<Record<string, string>>): number | string;
11
+ /** 文本导出不能把危险前缀当成外部电子表格公式;调用者仍需完成 CSV 引号转义。 */
12
+ export declare function safeSpreadsheetCsvText(value: string): string;
@@ -0,0 +1,245 @@
1
+ /** 有界、无动态代码执行的轻量公式解析器;不是完整 Excel 公式引擎。 */
2
+ export const FORMULA_LIMITS = Object.freeze({ length: 2048, tokens: 1024, depth: 64, work: 10000, range: 10000 });
3
+ class FormulaError extends Error {
4
+ code;
5
+ constructor(code) { super(code); this.code = code; }
6
+ }
7
+ function fail(code) { throw new FormulaError(code); }
8
+ function finite(value) { return Number.isFinite(value) ? value : fail('#NUM!'); }
9
+ function numeric(value) {
10
+ if (value === null)
11
+ return 0;
12
+ return typeof value === 'number' ? value : fail('#VALUE!');
13
+ }
14
+ const decimal = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
15
+ function address(key) {
16
+ const match = /^\$?([A-Z]+)\$?(\d+)$/i.exec(key);
17
+ if (!match?.[1] || !match[2])
18
+ return fail('#REF!');
19
+ let column = 0;
20
+ for (const char of match[1].toUpperCase()) {
21
+ column = column * 26 + char.charCodeAt(0) - 64;
22
+ if (column > 16384)
23
+ return fail('#REF!');
24
+ }
25
+ const row = Number(match[2]);
26
+ if (row < 1 || row > 1048576 || !Number.isSafeInteger(row))
27
+ return fail('#REF!');
28
+ return { column, row };
29
+ }
30
+ function columnName(column) {
31
+ let name = '';
32
+ while (column > 0) {
33
+ column--;
34
+ name = String.fromCharCode(65 + column % 26) + name;
35
+ column = Math.floor(column / 26);
36
+ }
37
+ return name;
38
+ }
39
+ class Evaluation {
40
+ work = 0;
41
+ depth = 0;
42
+ active = new Set();
43
+ cache = new Map();
44
+ cells;
45
+ constructor(cells) { this.cells = cells; }
46
+ tick() { if (++this.work > FORMULA_LIMITS.work)
47
+ fail('#LIMIT!'); }
48
+ bounded(fn) {
49
+ if (++this.depth > FORMULA_LIMITS.depth) {
50
+ this.depth--;
51
+ return fail('#LIMIT!');
52
+ }
53
+ try {
54
+ return fn();
55
+ }
56
+ finally {
57
+ this.depth--;
58
+ }
59
+ }
60
+ cell(key) {
61
+ this.tick();
62
+ const parsed = address(key);
63
+ const normalized = `${columnName(parsed.column)}${parsed.row}`;
64
+ if (this.active.has(normalized))
65
+ return fail('#CYCLE!');
66
+ if (this.cache.has(normalized))
67
+ return this.cache.get(normalized) ?? null;
68
+ const raw = Object.hasOwn(this.cells, normalized) ? this.cells[normalized] : '';
69
+ if (typeof raw !== 'string')
70
+ return fail('#VALUE!');
71
+ this.active.add(normalized);
72
+ try {
73
+ const trimmed = raw.trim();
74
+ const value = raw.startsWith('=')
75
+ ? new Parser(raw.slice(1), this).parse()
76
+ : trimmed === '' ? null : decimal.test(trimmed) ? finite(Number(trimmed)) : raw;
77
+ this.cache.set(normalized, value);
78
+ return value;
79
+ }
80
+ finally {
81
+ this.active.delete(normalized);
82
+ }
83
+ }
84
+ range(start, end) {
85
+ const a = address(start);
86
+ const b = address(end);
87
+ const left = Math.min(a.column, b.column), right = Math.max(a.column, b.column);
88
+ const top = Math.min(a.row, b.row), bottom = Math.max(a.row, b.row);
89
+ if ((right - left + 1) * (bottom - top + 1) > FORMULA_LIMITS.range)
90
+ return fail('#LIMIT!');
91
+ const values = [];
92
+ for (let row = top; row <= bottom; row++) {
93
+ for (let column = left; column <= right; column++)
94
+ values.push(this.cell(`${columnName(column)}${row}`));
95
+ }
96
+ return values;
97
+ }
98
+ }
99
+ class Parser {
100
+ tokens = [];
101
+ position = 0;
102
+ context;
103
+ constructor(source, context) {
104
+ this.context = context;
105
+ if (source.length + 1 > FORMULA_LIMITS.length)
106
+ fail('#LIMIT!');
107
+ let rest = source;
108
+ while (rest.length) {
109
+ rest = rest.trimStart();
110
+ if (!rest)
111
+ break;
112
+ context.tick();
113
+ if (this.tokens.length >= FORMULA_LIMITS.tokens)
114
+ fail('#LIMIT!');
115
+ const number = /^(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?/i.exec(rest)?.[0];
116
+ const cell = /^\$?[A-Z]+\$?\d+/i.exec(rest)?.[0];
117
+ const name = /^[A-Z_][A-Z_0-9]*/i.exec(rest)?.[0];
118
+ let token;
119
+ if (number)
120
+ token = { kind: 'number', text: number };
121
+ else if (cell)
122
+ token = { kind: 'cell', text: cell.toUpperCase() };
123
+ else if (name)
124
+ token = { kind: 'name', text: name.toUpperCase() };
125
+ else if (/^[+\-*/(),:]/.test(rest))
126
+ token = { kind: 'symbol', text: rest[0] ?? '' };
127
+ else
128
+ return fail('#VALUE!');
129
+ this.tokens.push(token);
130
+ rest = rest.slice(token.text.length);
131
+ }
132
+ this.tokens.push({ kind: 'end', text: '' });
133
+ }
134
+ peek() { return this.tokens[this.position] ?? { kind: 'end', text: '' }; }
135
+ take() { this.context.tick(); const token = this.peek(); this.position++; return token; }
136
+ expect(text) { if (this.take().text !== text)
137
+ fail('#VALUE!'); }
138
+ parse() {
139
+ return this.context.bounded(() => {
140
+ const result = this.expression();
141
+ if (this.peek().kind !== 'end')
142
+ return fail('#VALUE!');
143
+ return result;
144
+ });
145
+ }
146
+ expression() {
147
+ let value = this.product();
148
+ while (this.peek().text === '+' || this.peek().text === '-') {
149
+ const op = this.take().text;
150
+ const right = numeric(this.product()), left = numeric(value);
151
+ value = finite(op === '+' ? left + right : left - right);
152
+ }
153
+ return value;
154
+ }
155
+ product() {
156
+ let value = this.unary();
157
+ while (this.peek().text === '*' || this.peek().text === '/') {
158
+ const op = this.take().text;
159
+ const right = numeric(this.unary()), left = numeric(value);
160
+ if (op === '/' && right === 0)
161
+ return fail('#DIV/0!');
162
+ value = finite(op === '*' ? left * right : left / right);
163
+ }
164
+ return value;
165
+ }
166
+ unary() {
167
+ if (this.peek().text === '+' || this.peek().text === '-') {
168
+ const negative = this.take().text === '-';
169
+ return this.context.bounded(() => finite(numeric(this.unary()) * (negative ? -1 : 1)));
170
+ }
171
+ return this.primary();
172
+ }
173
+ primary() {
174
+ const token = this.take();
175
+ if (token.kind === 'number')
176
+ return finite(Number(token.text));
177
+ if (token.kind === 'cell')
178
+ return this.context.cell(token.text);
179
+ if (token.text === '(')
180
+ return this.context.bounded(() => {
181
+ const value = this.expression();
182
+ this.expect(')');
183
+ return value;
184
+ });
185
+ if (token.kind === 'name')
186
+ return this.context.bounded(() => this.aggregate(token.text));
187
+ return fail('#VALUE!');
188
+ }
189
+ aggregate(name) {
190
+ if (!['SUM', 'AVG', 'AVERAGE', 'COUNT', 'MIN', 'MAX'].includes(name))
191
+ return fail('#NAME?');
192
+ this.expect('(');
193
+ const values = [];
194
+ if (this.peek().text !== ')') {
195
+ while (true) {
196
+ if (this.peek().kind === 'cell' && this.tokens[this.position + 1]?.text === ':') {
197
+ const start = this.take().text;
198
+ this.expect(':');
199
+ const end = this.take();
200
+ if (end.kind !== 'cell')
201
+ return fail('#REF!');
202
+ for (const value of this.context.range(start, end.text))
203
+ if (typeof value === 'number')
204
+ values.push(value);
205
+ }
206
+ else {
207
+ const value = this.expression();
208
+ if (typeof value === 'number')
209
+ values.push(value);
210
+ }
211
+ if (this.peek().text !== ',')
212
+ break;
213
+ this.take();
214
+ }
215
+ }
216
+ this.expect(')');
217
+ if (name === 'COUNT')
218
+ return values.length;
219
+ if (name === 'MIN' || name === 'MAX') {
220
+ if (!values.length)
221
+ return 0;
222
+ return values.reduce((a, b) => name === 'MIN' ? Math.min(a, b) : Math.max(a, b));
223
+ }
224
+ const sum = finite(values.reduce((a, b) => finite(a + b), 0));
225
+ if (name === 'SUM')
226
+ return sum;
227
+ return values.length ? finite(sum / values.length) : fail('#DIV/0!');
228
+ }
229
+ }
230
+ export function evaluateFormula(raw, cells) {
231
+ if (!raw.startsWith('='))
232
+ return raw;
233
+ try {
234
+ return new Parser(raw.slice(1), new Evaluation(cells)).parse() ?? 0;
235
+ }
236
+ catch (error) {
237
+ if (error instanceof FormulaError)
238
+ return error.code;
239
+ throw error;
240
+ }
241
+ }
242
+ /** 文本导出不能把危险前缀当成外部电子表格公式;调用者仍需完成 CSV 引号转义。 */
243
+ export function safeSpreadsheetCsvText(value) {
244
+ return /^[\s\uFEFF]*[=+\-@]/u.test(value) || /^[\t\r\n]/.test(value) ? `'${value}` : value;
245
+ }
@@ -0,0 +1,2 @@
1
+ /** 导出当前展示的记录;不声称导出了服务端完整数据集。 */
2
+ export declare function downloadRows(filename: string, rows: readonly object[]): void;
@@ -0,0 +1,9 @@
1
+ /** 导出当前展示的记录;不声称导出了服务端完整数据集。 */
2
+ export function downloadRows(filename, rows) {
3
+ const url = URL.createObjectURL(new Blob([JSON.stringify(rows, null, 2)], { type: 'application/json' }));
4
+ const link = document.createElement('a');
5
+ link.href = url;
6
+ link.download = filename;
7
+ link.click();
8
+ setTimeout(() => URL.revokeObjectURL(url), 0);
9
+ }
@@ -70,6 +70,8 @@ export const builtinDisplayComponents = Object.freeze({
70
70
  images: validatedDisplay(props(nullable(Type.Array(Type.String()))), ImagesField),
71
71
  tags: validatedDisplay(props(nullable(Type.Union([Type.String(), Type.Array(Type.String())]))), TagField),
72
72
  date: validatedDisplay(props(date), DateField),
73
+ datetime: validatedDisplay(Type.Object({ value: date, format: Type.Literal('datetime') }, { additionalProperties: false }), DateField, input => ({ value: input.value, format: 'datetime' })),
74
+ time: validatedDisplay(Type.Object({ value: date, format: Type.Literal('iso') }, { additionalProperties: false }), DateField, input => ({ value: input.value, format: 'iso' })),
73
75
  daterange: validatedDisplay(props(nullable(Type.Union([
74
76
  Type.Tuple([date, date]),
75
77
  Type.Object({ start: dateProperty, end: dateProperty, from: dateProperty, to: dateProperty }, { additionalProperties: false }),
@@ -4,6 +4,7 @@
4
4
 
5
5
  <script lang="ts">
6
6
  import { cn } from '../../utils.js';
7
+ import { parseDisplayDate } from './date-display';
7
8
 
8
9
  interface Props {
9
10
  value: string | number | Date | null | undefined;
@@ -43,13 +44,13 @@
43
44
  return new Intl.DateTimeFormat(loc, { dateStyle: 'medium' }).format(date);
44
45
  }
45
46
 
47
+ const parsed = $derived(parseDisplayDate(value));
46
48
  const formatted = $derived.by(() => {
47
- if (value == null || value === '') return nullLabel;
48
- const d = value instanceof Date ? value : new Date(value);
49
- if (isNaN(d.getTime())) return nullLabel;
49
+ if (!parsed) return nullLabel;
50
+ const d = parsed.date;
50
51
 
51
- if (format === 'iso') return d.toISOString();
52
- if (format === 'relative') return formatRelative(d, locale);
52
+ if (format === 'iso') return parsed.title;
53
+ if (format === 'relative' && !parsed.civil) return formatRelative(d, locale);
53
54
 
54
55
  const defaultOptions: Intl.DateTimeFormatOptions =
55
56
  options ??
@@ -60,17 +61,16 @@
60
61
  : { dateStyle: 'medium' });
61
62
 
62
63
  try {
63
- return new Intl.DateTimeFormat(locale, defaultOptions).format(d);
64
+ return new Intl.DateTimeFormat(locale, {
65
+ ...defaultOptions,
66
+ ...(parsed.civil ? { timeZone: 'UTC' } : {}),
67
+ }).format(d);
64
68
  } catch {
65
- return d.toLocaleDateString();
69
+ return parsed.title;
66
70
  }
67
71
  });
68
72
 
69
- const fullIsoTitle = $derived.by(() => {
70
- if (value == null || value === '') return undefined;
71
- const d = value instanceof Date ? value : new Date(value);
72
- return isNaN(d.getTime()) ? undefined : d.toISOString();
73
- });
73
+ const fullIsoTitle = $derived(parsed?.title);
74
74
  </script>
75
75
 
76
76
  <span class={cn('field-date svadmin-u-fc7473ca09eb', className)} title={fullIsoTitle}>{formatted}</span>
@@ -0,0 +1,6 @@
1
+ export interface DisplayDate {
2
+ date: Date;
3
+ civil: boolean;
4
+ title: string;
5
+ }
6
+ export declare function parseDisplayDate(value: string | number | Date | null | undefined): DisplayDate | undefined;
@@ -0,0 +1,24 @@
1
+ export function parseDisplayDate(value) {
2
+ if (value == null || value === '')
3
+ return undefined;
4
+ if (typeof value === 'string') {
5
+ const calendar = /^(\d{4}-\d{2}-\d{2})(?:T(\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?))?$/.exec(value);
6
+ const time = /^\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?$/.test(value);
7
+ if (calendar || time) {
8
+ const day = calendar?.[1] ?? '2000-01-01';
9
+ const clock = calendar ? calendar[2] ?? '00:00' : value;
10
+ const [hours, minutes, seconds = '00'] = clock.split(':');
11
+ const [wholeSeconds, fraction = ''] = seconds.split('.');
12
+ const canonical = `${day}T${hours}:${minutes}:${wholeSeconds}.${fraction.padEnd(3, '0')}Z`;
13
+ // UTC 仅作为无时区日历值的格式化载体,绝不作为真实时间点输出。
14
+ const date = new Date(canonical);
15
+ if (!Number.isFinite(date.getTime()) || date.toISOString() !== canonical)
16
+ return undefined;
17
+ return { date, civil: true, title: value };
18
+ }
19
+ }
20
+ const date = value instanceof Date ? value : new Date(value);
21
+ if (!Number.isFinite(date.getTime()))
22
+ return undefined;
23
+ return { date, civil: false, title: date.toISOString() };
24
+ }
@@ -0,0 +1,2 @@
1
+ /** 原生 time 输入没有日期/时区;使用本地锚点格式化,不能按 ISO 时间戳解析。 */
2
+ export declare function dateFieldValue(value: string | number | Date, timeOnly: boolean): Date;
@@ -0,0 +1,13 @@
1
+ /** 原生 time 输入没有日期/时区;使用本地锚点格式化,不能按 ISO 时间戳解析。 */
2
+ export function dateFieldValue(value, timeOnly) {
3
+ if (timeOnly && typeof value === 'string') {
4
+ const match = /^(\d{2}):(\d{2})(?::(\d{2})(?:\.(\d{1,3}))?)?$/u.exec(value);
5
+ if (match) {
6
+ const hour = Number(match[1]), minute = Number(match[2]), second = Number(match[3] ?? 0);
7
+ if (hour > 23 || minute > 59 || second > 59)
8
+ return new Date(NaN);
9
+ return new Date(1970, 0, 1, hour, minute, second, Number((match[4] ?? '').padEnd(3, '0')));
10
+ }
11
+ }
12
+ return value instanceof Date ? value : new Date(value);
13
+ }
@@ -0,0 +1,37 @@
1
+ /** 嵌套表单的草稿与传输语义;不是替代服务端的 JSON Schema 校验器。 */
2
+ export interface JsonSchemaFormSchema {
3
+ type?: string | string[];
4
+ const?: string | number | boolean | null;
5
+ readOnly?: boolean;
6
+ minimum?: number;
7
+ maximum?: number;
8
+ exclusiveMinimum?: number;
9
+ exclusiveMaximum?: number;
10
+ multipleOf?: number;
11
+ minLength?: number;
12
+ maxLength?: number;
13
+ minItems?: number;
14
+ maxItems?: number;
15
+ additionalProperties?: boolean;
16
+ title?: string;
17
+ description?: string;
18
+ properties?: Record<string, JsonSchemaFormSchema>;
19
+ items?: JsonSchemaFormSchema;
20
+ enum?: Array<string | number | boolean | null>;
21
+ default?: unknown;
22
+ required?: string[];
23
+ }
24
+ export declare const SCHEMA_FORM_LIMITS: Readonly<{
25
+ depth: 32;
26
+ nodes: 10000;
27
+ characters: 1048576;
28
+ }>;
29
+ /** Svelte 代理也通过普通数据属性读取;回调永远收不到活跃草稿的引用。 */
30
+ export declare function schemaFormSnapshot(value: unknown): Record<string, unknown>;
31
+ /** 默认值只填“缺失的键”;显式 undefined/null/false/0/空串都表示调用方的决定。 */
32
+ export declare function initializeSchemaForm(schema: JsonSchemaFormSchema, value: Record<string, unknown>): Record<string, unknown>;
33
+ export declare function schemaFormArrayItem(schema: JsonSchemaFormSchema): unknown;
34
+ export declare function writeSchemaFormPath(value: Record<string, unknown>, path: readonly string[], nextValue: unknown): Record<string, unknown>;
35
+ /** option.value 是索引,不是 String(value):1 与 "1"、null 与 "null" 必须可区分。 */
36
+ export declare function schemaFormEnumIndex(choices: readonly unknown[], value: unknown): string;
37
+ export declare function schemaFormEnumValue(choices: readonly unknown[], index: string): unknown;