@getdom/studio 0.1.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 (213) hide show
  1. package/LICENSE.md +109 -0
  2. package/README.md +44 -0
  3. package/SKILL.md +83 -0
  4. package/package.json +73 -0
  5. package/public/llms.txt +57 -0
  6. package/public/skills.sh +11 -0
  7. package/src/pages/_layout/componentCatalog.generated.js +4514 -0
  8. package/src/pages/_layout/componentInspector.js +60 -0
  9. package/src/pages/_layout/componentManager.js +192 -0
  10. package/src/pages/_layout/componentRuntimeRecords.js +49 -0
  11. package/src/pages/_layout/inspector/componentRegistry.js +639 -0
  12. package/src/pages/_layout/inspector/serialize.js +134 -0
  13. package/src/pages/_layout/inspector/useInspector.js +293 -0
  14. package/src/pages/components/accordion/DomAccordion.vue +39 -0
  15. package/src/pages/components/breadcrumbs/DomBreadcrumbs.vue +347 -0
  16. package/src/pages/components/button/DomButton.vue +132 -0
  17. package/src/pages/components/command-palette/DomCommandPalette.vue +482 -0
  18. package/src/pages/components/context-menu/DomContextMenu.vue +178 -0
  19. package/src/pages/components/dialog/DomDialog.vue +271 -0
  20. package/src/pages/components/dialog/DomDialogSchemaForm.vue +72 -0
  21. package/src/pages/components/dialog/DomDialogStack.vue +289 -0
  22. package/src/pages/components/dialog/useDialogs.js +253 -0
  23. package/src/pages/components/drawer/DomDrawer.vue +159 -0
  24. package/src/pages/components/dropdown/DomDropdown.vue +261 -0
  25. package/src/pages/components/icon-button/DomIconButton.vue +155 -0
  26. package/src/pages/components/lightbox/DomLightbox.vue +1561 -0
  27. package/src/pages/components/menu/DomMenu.vue +359 -0
  28. package/src/pages/components/menu/DomMenuItem.vue +233 -0
  29. package/src/pages/components/menu/DomMenuLabel.vue +19 -0
  30. package/src/pages/components/menu/DomMenuSeparator.vue +14 -0
  31. package/src/pages/components/menu/menuContext.js +11 -0
  32. package/src/pages/components/menu/menuPlacement.js +26 -0
  33. package/src/pages/components/menu/submenu.js +58 -0
  34. package/src/pages/components/pagination/DomPagination.vue +265 -0
  35. package/src/pages/components/playground/DomRenderer.vue +48 -0
  36. package/src/pages/components/popover/DomPopover.vue +244 -0
  37. package/src/pages/components/profile-menu/DomProfileMenu.vue +408 -0
  38. package/src/pages/components/profile-menu/DomProfileMenuItemContent.vue +45 -0
  39. package/src/pages/components/scroll-menu/DomScrollMenu.vue +430 -0
  40. package/src/pages/components/scroll-menu/DomScrollMenuItem.vue +212 -0
  41. package/src/pages/components/scroll-menu/scrollMenuContext.js +22 -0
  42. package/src/pages/components/splitter-panel/DomSplitterPanel.vue +283 -0
  43. package/src/pages/components/stepper/DomStepper.vue +386 -0
  44. package/src/pages/components/tabs/DomTabs.vue +149 -0
  45. package/src/pages/components/theme-switcher/DomThemeSwitcher.vue +226 -0
  46. package/src/pages/components/toast/DomToastItem.vue +52 -0
  47. package/src/pages/components/toast/DomToastStack.vue +768 -0
  48. package/src/pages/components/toast/useToasts.js +237 -0
  49. package/src/pages/components/toolbar/DomToolbar.vue +158 -0
  50. package/src/pages/components/tooltip/DomTooltip.vue +69 -0
  51. package/src/pages/components/tooltip/vDomTooltip.js +106 -0
  52. package/src/pages/components/tree-view/DomTreeView.vue +961 -0
  53. package/src/pages/components/tree-view/treeSchemas.js +129 -0
  54. package/src/pages/components/tree-view/treeUtils.js +228 -0
  55. package/src/pages/data/_shared/calculateVirtualWindow.js +101 -0
  56. package/src/pages/data/_shared/resourceRecord.js +29 -0
  57. package/src/pages/data/activity-timeline/DomActivityTimeline.vue +417 -0
  58. package/src/pages/data/agent-chat/DomAgentChatShell.vue +565 -0
  59. package/src/pages/data/agent-chat/DomAgentConversation.vue +737 -0
  60. package/src/pages/data/agent-chat/DomAgentConversationList.vue +319 -0
  61. package/src/pages/data/agent-chat/examples/agentChatMockApi.js +620 -0
  62. package/src/pages/data/agent-chat/examples/useAgentChatDemoStore.js +730 -0
  63. package/src/pages/data/data-grid/DataGridVirtualRows.vue +220 -0
  64. package/src/pages/data/data-grid/DomDataGrid.vue +1643 -0
  65. package/src/pages/data/document-editor/DomDocumentEditor.vue +3030 -0
  66. package/src/pages/data/document-editor/documentEditorCodeBlock.js +282 -0
  67. package/src/pages/data/document-editor/examples/documentEditorMockApi.js +272 -0
  68. package/src/pages/data/document-editor/examples/useDocumentEditorDemoStore.js +284 -0
  69. package/src/pages/data/filter-bar/DomFilterBar.vue +533 -0
  70. package/src/pages/data/kanban/DomKanban.vue +1703 -0
  71. package/src/pages/data/kanban/kanbanApi.js +80 -0
  72. package/src/pages/data/media-browser/DomMediaBrowser.vue +2344 -0
  73. package/src/pages/data/media-browser/examples/mediaBrowserMockApi.js +653 -0
  74. package/src/pages/data/media-browser/examples/useMediaBrowserDemoStore.js +1157 -0
  75. package/src/pages/data/month-calendar/DomMonthCalendar.vue +1718 -0
  76. package/src/pages/data/week-planner/DomWeekPlanner.vue +2308 -0
  77. package/src/pages/dev/code-block/DomCodeBlock.vue +263 -0
  78. package/src/pages/dev/json-viewer/DomJsonViewer.vue +962 -0
  79. package/src/pages/forms/_shared/DomFieldLoadingSpinner.vue +12 -0
  80. package/src/pages/forms/_shared/options.js +61 -0
  81. package/src/pages/forms/_shared/useOptionMenu.js +59 -0
  82. package/src/pages/forms/autocomplete/DomAutocomplete.vue +180 -0
  83. package/src/pages/forms/calendar/DomCalendar.vue +442 -0
  84. package/src/pages/forms/checkbox/DomCheckbox.vue +69 -0
  85. package/src/pages/forms/class-toggle-input/DomClassToggleInput.vue +316 -0
  86. package/src/pages/forms/code-input/DomCodeInput.vue +202 -0
  87. package/src/pages/forms/color-input/DomColorInput.vue +118 -0
  88. package/src/pages/forms/combobox/DomCombobox.vue +237 -0
  89. package/src/pages/forms/confirmation-code-input/DomConfirmationCodeInput.vue +345 -0
  90. package/src/pages/forms/date-picker/DomDatePicker.vue +299 -0
  91. package/src/pages/forms/date-time-picker/DomDateTimePicker.vue +630 -0
  92. package/src/pages/forms/email-input/DomEmailInput.vue +55 -0
  93. package/src/pages/forms/field/DomField.vue +267 -0
  94. package/src/pages/forms/field/fieldLayout.js +1 -0
  95. package/src/pages/forms/field/fieldProps.js +114 -0
  96. package/src/pages/forms/field/layouts/DomGridFieldLayout.vue +38 -0
  97. package/src/pages/forms/field/useField.js +343 -0
  98. package/src/pages/forms/field/validators.js +364 -0
  99. package/src/pages/forms/file-upload/DomFileUpload.vue +361 -0
  100. package/src/pages/forms/form/DomForm.vue +985 -0
  101. package/src/pages/forms/form/adapters/fieldBuilder.js +45 -0
  102. package/src/pages/forms/form/adapters/jsonSchema.js +264 -0
  103. package/src/pages/forms/form/adapters/shared.js +53 -0
  104. package/src/pages/forms/form/adapters/zod.js +235 -0
  105. package/src/pages/forms/form/formApi.js +38 -0
  106. package/src/pages/forms/form/formDefinition.js +392 -0
  107. package/src/pages/forms/form/schemaAdapter.js +54 -0
  108. package/src/pages/forms/icon-selector/DomIconSelector.vue +460 -0
  109. package/src/pages/forms/icon-selector/iconSelectorItems.js +490 -0
  110. package/src/pages/forms/input-group/DomInputGroup.vue +232 -0
  111. package/src/pages/forms/json-input/DomJsonInput.vue +227 -0
  112. package/src/pages/forms/json-list-input/DomJsonListInput.vue +206 -0
  113. package/src/pages/forms/listbox/DomListbox.vue +105 -0
  114. package/src/pages/forms/money-input/DomMoneyInput.vue +147 -0
  115. package/src/pages/forms/native-select/DomNativeSelect.vue +67 -0
  116. package/src/pages/forms/number-input/DomNumberInput.vue +47 -0
  117. package/src/pages/forms/password-input/DomPasswordInput.vue +218 -0
  118. package/src/pages/forms/password-input/pwnedPasswords.js +47 -0
  119. package/src/pages/forms/password-strength/DomPasswordStrength.vue +128 -0
  120. package/src/pages/forms/password-strength/passwordStrength.js +92 -0
  121. package/src/pages/forms/position-input/DomPositionInput.vue +83 -0
  122. package/src/pages/forms/radio-group/DomRadioGroup.vue +102 -0
  123. package/src/pages/forms/range-input/DomRangeInput.vue +296 -0
  124. package/src/pages/forms/select/DomSelect.vue +479 -0
  125. package/src/pages/forms/select-input/DomSelectInput.vue +155 -0
  126. package/src/pages/forms/tag-combobox/DomTagCombobox.vue +358 -0
  127. package/src/pages/forms/text-input/DomTextInput.vue +48 -0
  128. package/src/pages/forms/textarea-composer/DomTextareaComposer.vue +234 -0
  129. package/src/pages/forms/textarea-input/DomTextareaInput.vue +86 -0
  130. package/src/pages/forms/textarea-input/useTextareaAutosize.js +147 -0
  131. package/src/pages/forms/time-selector/DomTimeSelector.vue +389 -0
  132. package/src/pages/forms/time-selector/timeUtils.js +194 -0
  133. package/src/pages/forms/toggle/DomToggle.vue +158 -0
  134. package/src/pages/forms/toggle-button/DomToggleButton.vue +88 -0
  135. package/src/pages/forms/toggle-button-group/DomToggleButtonGroup.vue +340 -0
  136. package/src/pages/forms/url-input/DomUrlInput.vue +55 -0
  137. package/src/pages/forms/wysiwyg/DomWysiwyg.vue +204 -0
  138. package/src/pages/lib/css/color.js +245 -0
  139. package/src/pages/lib/headless/accordion.d.ts +7 -0
  140. package/src/pages/lib/headless/accordion.js +246 -0
  141. package/src/pages/lib/headless/autocomplete.d.ts +4 -0
  142. package/src/pages/lib/headless/autocomplete.js +33 -0
  143. package/src/pages/lib/headless/base.d.ts +25 -0
  144. package/src/pages/lib/headless/base.js +127 -0
  145. package/src/pages/lib/headless/checkbox.d.ts +19 -0
  146. package/src/pages/lib/headless/checkbox.js +99 -0
  147. package/src/pages/lib/headless/combobox.d.ts +7 -0
  148. package/src/pages/lib/headless/combobox.js +488 -0
  149. package/src/pages/lib/headless/dialog.d.ts +23 -0
  150. package/src/pages/lib/headless/dialog.js +384 -0
  151. package/src/pages/lib/headless/drawer.d.ts +21 -0
  152. package/src/pages/lib/headless/drawer.js +682 -0
  153. package/src/pages/lib/headless/dropdown.d.ts +19 -0
  154. package/src/pages/lib/headless/dropdown.js +412 -0
  155. package/src/pages/lib/headless/floating-effect.js +118 -0
  156. package/src/pages/lib/headless/floating.d.ts +34 -0
  157. package/src/pages/lib/headless/floating.js +382 -0
  158. package/src/pages/lib/headless/index.d.ts +19 -0
  159. package/src/pages/lib/headless/index.js +19 -0
  160. package/src/pages/lib/headless/listbox.d.ts +19 -0
  161. package/src/pages/lib/headless/listbox.js +126 -0
  162. package/src/pages/lib/headless/menu.d.ts +18 -0
  163. package/src/pages/lib/headless/menu.js +117 -0
  164. package/src/pages/lib/headless/popover-panel.d.ts +18 -0
  165. package/src/pages/lib/headless/popover-panel.js +309 -0
  166. package/src/pages/lib/headless/popover.d.ts +19 -0
  167. package/src/pages/lib/headless/popover.js +391 -0
  168. package/src/pages/lib/headless/radio-group.d.ts +19 -0
  169. package/src/pages/lib/headless/radio-group.js +100 -0
  170. package/src/pages/lib/headless/tabs.d.ts +18 -0
  171. package/src/pages/lib/headless/tabs.js +119 -0
  172. package/src/pages/lib/headless/toast.d.ts +13 -0
  173. package/src/pages/lib/headless/toast.js +65 -0
  174. package/src/pages/lib/headless/toggle.d.ts +19 -0
  175. package/src/pages/lib/headless/toggle.js +162 -0
  176. package/src/pages/lib/headless/tooltip-controller.d.ts +22 -0
  177. package/src/pages/lib/headless/tooltip-controller.js +549 -0
  178. package/src/pages/lib/headless/tooltip.d.ts +19 -0
  179. package/src/pages/lib/headless/tooltip.js +153 -0
  180. package/src/pages/lib/headless/transitions.d.ts +5 -0
  181. package/src/pages/lib/headless/transitions.js +89 -0
  182. package/src/pages/lib/vue/_internal/DomLiquidGlassSurface.vue +310 -0
  183. package/src/pages/lib/vue/_internal/liquidGlass.js +112 -0
  184. package/src/pages/lib/vue/_internal/liquidGlassConfig.js +10 -0
  185. package/src/pages/lib/vue/codeHighlighter.js +132 -0
  186. package/src/pages/lib/vue/floatingTeleportTarget.js +23 -0
  187. package/src/pages/lib/vue/index.d.ts +6806 -0
  188. package/src/pages/lib/vue/index.js +218 -0
  189. package/src/pages/lib/vue/useTheme.js +116 -0
  190. package/src/pages/mobile/action-sheet/DomActionSheet.vue +186 -0
  191. package/src/pages/mobile/bottom-nav/DomAppBottomNav.vue +94 -0
  192. package/src/pages/mobile/list-item/DomAppListItem.vue +90 -0
  193. package/src/pages/mobile/safe-area/DomSafeArea.vue +39 -0
  194. package/src/pages/mobile/shell/DomAppShell.vue +51 -0
  195. package/src/pages/mobile/stack/AppStackScreen.vue +63 -0
  196. package/src/pages/mobile/stack/DomAppStack.vue +711 -0
  197. package/src/pages/mobile/top-bar/DomAppTopBar.vue +67 -0
  198. package/src/pages/visual/alert/DomAlert.vue +363 -0
  199. package/src/pages/visual/avatar/DomAvatar.vue +110 -0
  200. package/src/pages/visual/avatar-stack/DomAvatarStack.vue +89 -0
  201. package/src/pages/visual/badge/DomBadge.vue +105 -0
  202. package/src/pages/visual/card/DomCard.vue +65 -0
  203. package/src/pages/visual/card-rail/DomCardRail.vue +99 -0
  204. package/src/pages/visual/empty-state/DomEmptyState.vue +88 -0
  205. package/src/pages/visual/progress/DomProgress.vue +212 -0
  206. package/src/pages/visual/progress/DomRadialProgress.vue +186 -0
  207. package/src/pages/visual/progress/DomSegmentedProgress.vue +350 -0
  208. package/src/pages/visual/skeleton/DomSkeleton.vue +174 -0
  209. package/src/pages/visual/spinner/DomSpinner.vue +122 -0
  210. package/src/pages/visual/status-pill/DomStatusPill.vue +75 -0
  211. package/src/pages/visual/text-diff/DomTextDiff.vue +577 -0
  212. package/src/pages/visual/text-diff/textDiff.js +300 -0
  213. package/src/style.css +446 -0
@@ -0,0 +1,45 @@
1
+ export function fieldPath(scope, key) {
2
+ return scope ? `${scope}.${key}` : key;
3
+ }
4
+
5
+ export function fieldId(prefix, path) {
6
+ return `${prefix}-${path.replace(/\W+/g, '-')}`;
7
+ }
8
+
9
+ export function nestedFormNode({ idPrefix, path, key, field, children }) {
10
+ return {
11
+ id: fieldId(idPrefix, path),
12
+ component: 'DomForm',
13
+ props: {
14
+ name: key,
15
+ class: field.class || 'space-y-3 rounded-xl border border-border bg-secondary/25 p-4',
16
+ ...(field.props || {}),
17
+ },
18
+ children,
19
+ };
20
+ }
21
+
22
+ export function childrenForFieldShape(shape, scope, options, adapter) {
23
+ return Object.entries(shape || {}).map(([key, childSchema]) => {
24
+ const path = fieldPath(scope, key);
25
+ const nested = adapter.nestedShape(childSchema, path, key, options);
26
+ const nestedShape = nested?.shape || nested;
27
+ const nestedAdapter = nested?.adapter || adapter;
28
+
29
+ if (nestedShape) {
30
+ return nestedFormNode({
31
+ idPrefix: adapter.idPrefix,
32
+ path,
33
+ key,
34
+ field: adapter.fieldForNested(childSchema, path, key, options),
35
+ children: childrenForFieldShape(nestedShape, path, options, nestedAdapter),
36
+ });
37
+ }
38
+
39
+ return {
40
+ id: fieldId(adapter.idPrefix, path),
41
+ component: adapter.componentFor(childSchema, path, options),
42
+ props: adapter.fieldPropsFor(childSchema, path, key, options),
43
+ };
44
+ });
45
+ }
@@ -0,0 +1,264 @@
1
+ import {
2
+ clone,
3
+ fieldOptions,
4
+ formatLabel,
5
+ formDecoration,
6
+ hasOwn,
7
+ isEmpty,
8
+ optionObjects,
9
+ raw,
10
+ } from './shared.js';
11
+ import { childrenForFieldShape } from './fieldBuilder.js';
12
+
13
+ function jsonSchemaType(schema) {
14
+ const plain = raw(schema) || {};
15
+ const type = Array.isArray(plain.type)
16
+ ? plain.type.find((item) => item !== 'null') || plain.type[0]
17
+ : plain.type;
18
+
19
+ if (type) return String(type).toLowerCase();
20
+ if (hasOwn(plain, 'const')) return plain.const === null ? 'null' : typeof plain.const;
21
+ if (plain.enum?.length) {
22
+ const value = plain.enum.find((item) => item !== null);
23
+ return value === undefined ? 'null' : typeof value;
24
+ }
25
+ if (plain.properties) return 'object';
26
+ if (plain.items) return 'array';
27
+ return '';
28
+ }
29
+
30
+ function jsonSchemaFormat(schema) {
31
+ const plain = raw(schema) || {};
32
+ return String(plain.format || '').toLowerCase();
33
+ }
34
+
35
+ function jsonSchemaValues(schema) {
36
+ const plain = raw(schema) || {};
37
+ if (plain.enum) return plain.enum;
38
+ if (hasOwn(plain, 'const')) return [plain.const];
39
+ return null;
40
+ }
41
+
42
+ function jsonSchemaAllowsNull(schema) {
43
+ const plain = raw(schema) || {};
44
+ return Array.isArray(plain.type) && plain.type.includes('null');
45
+ }
46
+
47
+ export function isJsonSchema(schema) {
48
+ const plain = raw(schema) || {};
49
+ return Boolean(
50
+ plain.$schema
51
+ || plain.properties
52
+ || plain.items
53
+ || plain.enum
54
+ || hasOwn(plain, 'const')
55
+ || ['object', 'array', 'string', 'number', 'integer', 'boolean', 'null'].includes(jsonSchemaType(plain)),
56
+ );
57
+ }
58
+
59
+ function emailLike(value) {
60
+ return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(String(value));
61
+ }
62
+
63
+ function uriLike(value) {
64
+ try {
65
+ const url = new URL(String(value));
66
+ return Boolean(url.protocol);
67
+ } catch {
68
+ return false;
69
+ }
70
+ }
71
+
72
+ function jsonTypeMatches(value, type, schema) {
73
+ if (!type || value === null) return value !== null || jsonSchemaAllowsNull(schema);
74
+ if (type === 'integer') return Number.isInteger(Number(value));
75
+ if (type === 'number') return Number.isFinite(Number(value));
76
+ if (type === 'array') return Array.isArray(value);
77
+ if (type === 'object') return value && typeof value === 'object' && !Array.isArray(value);
78
+ if (type === 'boolean') return typeof value === 'boolean';
79
+ if (type === 'string') return typeof value === 'string';
80
+ return true;
81
+ }
82
+
83
+ function jsonSchemaValidator(schema) {
84
+ const plain = raw(schema);
85
+ if (!plain || typeof plain !== 'object') return null;
86
+
87
+ const type = jsonSchemaType(plain);
88
+ const format = jsonSchemaFormat(plain);
89
+ const values = jsonSchemaValues(plain);
90
+
91
+ return {
92
+ name: 'json-schema',
93
+ message: 'This field does not match the schema.',
94
+ validate(value) {
95
+ if (isEmpty(value)) return true;
96
+ if (!jsonTypeMatches(value, type, plain)) return `Use a ${type} value.`;
97
+ if (values && !values.includes(value)) return 'Choose one of the allowed values.';
98
+ if ((type === 'string' || !type) && plain.minLength != null && String(value).length < plain.minLength) return `Use at least ${plain.minLength} characters.`;
99
+ if ((type === 'string' || !type) && plain.maxLength != null && String(value).length > plain.maxLength) return `Use ${plain.maxLength} characters or fewer.`;
100
+ if (plain.pattern) {
101
+ try {
102
+ if (!new RegExp(plain.pattern).test(String(value))) return plain.errorMessage?.pattern || 'This field does not match the required pattern.';
103
+ } catch {
104
+ return true;
105
+ }
106
+ }
107
+ if ((type === 'number' || type === 'integer') && plain.minimum != null && Number(value) < plain.minimum) return `Use a value of ${plain.minimum} or greater.`;
108
+ if ((type === 'number' || type === 'integer') && plain.maximum != null && Number(value) > plain.maximum) return `Use a value of ${plain.maximum} or less.`;
109
+ if ((type === 'number' || type === 'integer') && plain.exclusiveMinimum != null && Number(value) <= plain.exclusiveMinimum) return `Use a value greater than ${plain.exclusiveMinimum}.`;
110
+ if ((type === 'number' || type === 'integer') && plain.exclusiveMaximum != null && Number(value) >= plain.exclusiveMaximum) return `Use a value less than ${plain.exclusiveMaximum}.`;
111
+ if ((type === 'number' || type === 'integer') && plain.multipleOf != null && Number(value) % plain.multipleOf !== 0) return `Use a multiple of ${plain.multipleOf}.`;
112
+ if (type === 'array' && plain.minItems != null && value.length < plain.minItems) return `Add at least ${plain.minItems} items.`;
113
+ if (type === 'array' && plain.maxItems != null && value.length > plain.maxItems) return `Use ${plain.maxItems} items or fewer.`;
114
+ if (format === 'email' && !emailLike(value)) return 'Enter a valid email address.';
115
+ if ((format === 'uri' || format === 'url') && !uriLike(value)) return 'Enter a valid URL.';
116
+ if (format === 'date' && !/^\d{4}-\d{2}-\d{2}$/.test(String(value))) return 'Use YYYY-MM-DD.';
117
+ return true;
118
+ },
119
+ };
120
+ }
121
+
122
+ function jsonSchemaComponentFor(schema, path, options) {
123
+ const plain = raw(schema) || {};
124
+ const type = jsonSchemaType(plain);
125
+ const format = jsonSchemaFormat(plain);
126
+ const values = jsonSchemaValues(plain);
127
+ const field = fieldOptions(options, path, path.split('.').pop(), schema);
128
+ const override = field.component || plain.component;
129
+
130
+ if (override) return override;
131
+ if (values) return 'DomSelect';
132
+ if (format === 'binary' || format === 'file' || plain.contentMediaType || plain.contentEncoding === 'base64') return 'DomFileUpload';
133
+ if (format === 'currency' || format === 'money' || format === 'decimal') return 'DomMoneyInput';
134
+ if (type === 'decimal' || type === 'currency' || type === 'money') return 'DomMoneyInput';
135
+ if (type === 'file') return 'DomFileUpload';
136
+ if (type === 'array') return 'DomJsonListInput';
137
+ if (format === 'email') return 'DomEmailInput';
138
+ if (format === 'uri' || format === 'url') return 'DomUrlInput';
139
+ if (format === 'time') return 'DomTimeSelector';
140
+ if (format === 'date-time' || format === 'datetime') return 'DomDateTimePicker';
141
+ if (format === 'date') return 'DomDatePicker';
142
+ if (type === 'number' || type === 'integer') return 'DomNumberInput';
143
+ if (type === 'boolean') return 'DomToggle';
144
+ if (plain.maxLength && plain.maxLength > 160) return 'DomTextareaInput';
145
+ return 'DomTextInput';
146
+ }
147
+
148
+ function jsonListFieldType(schema) {
149
+ const type = jsonSchemaType(schema);
150
+ const format = jsonSchemaFormat(schema);
151
+ if (format === 'email') return 'email';
152
+ if (format === 'uri' || format === 'url') return 'url';
153
+ if (format === 'time') return 'time';
154
+ if (format === 'date-time' || format === 'datetime') return 'datetime';
155
+ if (format === 'date') return 'date';
156
+ if (format === 'currency' || format === 'money' || format === 'decimal') return 'number';
157
+ if (format === 'binary' || format === 'file') return 'file';
158
+ if (type === 'decimal' || type === 'currency' || type === 'money') return 'number';
159
+ if (type === 'file') return 'file';
160
+ if (type === 'number' || type === 'integer') return 'number';
161
+ if (type === 'object' || type === 'array') return 'json';
162
+ return 'text';
163
+ }
164
+
165
+ function jsonListSchemaForItems(items) {
166
+ const plain = raw(items) || {};
167
+ const properties = plain.properties || {};
168
+ return Object.entries(properties).map(([key, child]) => {
169
+ const childSchema = raw(child) || {};
170
+ const decoration = formDecoration(child);
171
+ return {
172
+ key,
173
+ label: decoration.label || childSchema.title || formatLabel(key),
174
+ type: decoration.type || jsonListFieldType(childSchema),
175
+ placeholder: decoration.placeholder || childSchema.examples?.[0] || '',
176
+ ...(hasOwn(childSchema, 'default') ? { default: clone(childSchema.default) } : {}),
177
+ };
178
+ });
179
+ }
180
+
181
+ function defaultValueForJsonSchema(schema) {
182
+ const plain = raw(schema) || {};
183
+ const type = jsonSchemaType(plain);
184
+
185
+ if (hasOwn(plain, 'default')) return clone(plain.default);
186
+ if (hasOwn(plain, 'const')) return clone(plain.const);
187
+ if (type === 'array') return [];
188
+ if (type === 'boolean') return false;
189
+ return undefined;
190
+ }
191
+
192
+ function jsonSchemaFieldPropsFor(schema, path, required, options) {
193
+ const key = path.split('.').pop();
194
+ const plain = raw(schema) || {};
195
+ const field = fieldOptions(options, path, key, schema);
196
+ const type = jsonSchemaType(plain);
197
+ const values = jsonSchemaValues(plain);
198
+ const validators = [
199
+ jsonSchemaValidator(schema),
200
+ ...(field.props?.validators || []),
201
+ ].filter(Boolean);
202
+ const defaultValue = defaultValueForJsonSchema(plain);
203
+
204
+ const props = {
205
+ name: key,
206
+ label: field.label || plain.title || formatLabel(key),
207
+ description: field.description ?? plain.description ?? '',
208
+ required,
209
+ ...(plain.examples?.[0] ? { placeholder: plain.examples[0] } : {}),
210
+ ...(validators.length ? { validators } : {}),
211
+ ...(defaultValue !== undefined ? { modelValue: defaultValue } : {}),
212
+ ...(field.props || {}),
213
+ };
214
+
215
+ if (values && !props.options) props.options = optionObjects(values);
216
+ if (plain.minimum != null && props.min == null) props.min = plain.minimum;
217
+ if (plain.maximum != null && props.max == null) props.max = plain.maximum;
218
+ if (type === 'integer' && props.step == null) props.step = 1;
219
+ if (plain.multipleOf != null && props.step == null) props.step = plain.multipleOf;
220
+ if (type === 'array' && plain.items?.properties && !props.schema) props.schema = jsonListSchemaForItems(plain.items);
221
+
222
+ return props;
223
+ }
224
+
225
+ function jsonSchemaObjectAdapter(schema) {
226
+ const plain = raw(schema) || {};
227
+ const required = new Set(Array.isArray(plain.required) ? plain.required : []);
228
+
229
+ return {
230
+ idPrefix: 'json-schema',
231
+ nestedShape(childSchema) {
232
+ const child = raw(childSchema) || {};
233
+ return jsonSchemaType(child) === 'object' && child.properties
234
+ ? { shape: child.properties, adapter: jsonSchemaObjectAdapter(child) }
235
+ : null;
236
+ },
237
+ fieldForNested(childSchema, path, key, adapterOptions) {
238
+ return fieldOptions(adapterOptions, path, key, childSchema);
239
+ },
240
+ componentFor: jsonSchemaComponentFor,
241
+ fieldPropsFor(childSchema, path, key, adapterOptions) {
242
+ return jsonSchemaFieldPropsFor(childSchema, path, required.has(key), adapterOptions);
243
+ },
244
+ };
245
+ }
246
+
247
+ function childrenForJsonSchemaObject(schema, scope, options) {
248
+ const plain = raw(schema) || {};
249
+
250
+ return childrenForFieldShape(plain.properties || {}, scope, options, jsonSchemaObjectAdapter(plain));
251
+ }
252
+
253
+ export function jsonSchemaToChildren(schema, options = {}) {
254
+ const plain = raw(schema);
255
+ if (!plain || typeof plain !== 'object' || !isJsonSchema(plain)) return [];
256
+ if (jsonSchemaType(plain) !== 'object' || !plain.properties) return [];
257
+ return childrenForJsonSchemaObject(plain, '', options);
258
+ }
259
+
260
+ export const jsonSchemaAdapter = {
261
+ name: 'json-schema',
262
+ matches: isJsonSchema,
263
+ toChildren: jsonSchemaToChildren,
264
+ };
@@ -0,0 +1,53 @@
1
+ export function raw(value) {
2
+ return value && typeof value === 'object' && '__v_raw' in value ? value.__v_raw : value;
3
+ }
4
+
5
+ export function hasOwn(value, key) {
6
+ return Object.prototype.hasOwnProperty.call(value || {}, key);
7
+ }
8
+
9
+ export function clone(value) {
10
+ if (value == null || typeof value !== 'object') return value;
11
+ if (Array.isArray(value)) return value.map((item) => clone(item));
12
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, clone(item)]));
13
+ }
14
+
15
+ export function formatLabel(value) {
16
+ return String(value || '')
17
+ .replace(/[-_]+/g, ' ')
18
+ .replace(/([a-z0-9])([A-Z])/g, '$1 $2')
19
+ .replace(/^./, (letter) => letter.toUpperCase());
20
+ }
21
+
22
+ export function optionObjects(values) {
23
+ return values.map((value) => ({
24
+ label: formatLabel(value),
25
+ value,
26
+ }));
27
+ }
28
+
29
+ export function isEmpty(value) {
30
+ return value === undefined || value === null || value === '';
31
+ }
32
+
33
+ export function mergeFieldConfig(base = {}, override = {}) {
34
+ return {
35
+ ...base,
36
+ ...override,
37
+ props: {
38
+ ...(base.props || {}),
39
+ ...(override.props || {}),
40
+ },
41
+ };
42
+ }
43
+
44
+ export function formDecoration(schema) {
45
+ const plain = raw(schema) || {};
46
+ return plain['x-el'] || plain.xEl || plain['x-form'] || plain.xForm || {};
47
+ }
48
+
49
+ export function fieldOptions(options, path, key, schema) {
50
+ const inline = formDecoration(schema);
51
+ const external = options.fields?.[path] || options.fields?.[key] || {};
52
+ return mergeFieldConfig(inline, external);
53
+ }
@@ -0,0 +1,235 @@
1
+ import {
2
+ fieldOptions,
3
+ formatLabel,
4
+ isEmpty,
5
+ optionObjects,
6
+ raw,
7
+ } from './shared.js';
8
+ import { childrenForFieldShape } from './fieldBuilder.js';
9
+
10
+ function defOf(schema) {
11
+ return raw(schema)?._def || raw(schema)?.def || raw(schema)?._zod?.def || {};
12
+ }
13
+
14
+ function typeOf(schema) {
15
+ const schemaDef = defOf(schema);
16
+ return schemaDef.typeName || schemaDef.type || raw(schema)?.type || raw(schema)?.kind || '';
17
+ }
18
+
19
+ function hasCallable(schema, name) {
20
+ return typeof raw(schema)?.[name] === 'function';
21
+ }
22
+
23
+ function call(schema, name, ...args) {
24
+ if (!hasCallable(schema, name)) return undefined;
25
+ try {
26
+ return raw(schema)[name](...args);
27
+ } catch {
28
+ return undefined;
29
+ }
30
+ }
31
+
32
+ function unwrap(schema) {
33
+ let current = raw(schema);
34
+ let optional = Boolean(current?.optional || current?.isOptional || current?.required === false);
35
+ let nullable = Boolean(current?.nullable || current?.isNullable);
36
+
37
+ for (let index = 0; index < 12; index += 1) {
38
+ const schemaDef = defOf(current);
39
+ const type = typeOf(current);
40
+ if (/optional/i.test(type)) optional = true;
41
+ if (/nullable/i.test(type)) nullable = true;
42
+ if (/default|catch/i.test(type)) optional = true;
43
+
44
+ const next = schemaDef.innerType
45
+ || schemaDef.schema
46
+ || schemaDef.type
47
+ || schemaDef.in
48
+ || schemaDef.out;
49
+
50
+ if (!next || next === current || typeof next === 'string') break;
51
+ current = raw(next);
52
+ }
53
+
54
+ if (typeof current?.isOptional === 'function') optional = optional || current.isOptional();
55
+ if (typeof current?.isNullable === 'function') nullable = nullable || current.isNullable();
56
+
57
+ return { schema: current, optional, nullable };
58
+ }
59
+
60
+ export function objectShape(schema) {
61
+ const unwrapped = unwrap(schema).schema;
62
+ const schemaDef = defOf(unwrapped);
63
+ const shape = schemaDef.shape || unwrapped?.shape || unwrapped?.fields;
64
+ if (typeof shape === 'function') return shape();
65
+ if (shape && typeof shape === 'object') return shape;
66
+ return null;
67
+ }
68
+
69
+ export function isZodLikeSchema(schema) {
70
+ return Boolean(objectShape(schema));
71
+ }
72
+
73
+ function enumValues(schema) {
74
+ const unwrapped = unwrap(schema).schema;
75
+ const schemaDef = defOf(unwrapped);
76
+ const values = schemaDef.values
77
+ || schemaDef.entries
78
+ || schemaDef.options
79
+ || unwrapped?.values
80
+ || unwrapped?.options
81
+ || unwrapped?.enum;
82
+
83
+ if (Array.isArray(values)) return values;
84
+ if (values && typeof values === 'object') return Object.values(values);
85
+ return null;
86
+ }
87
+
88
+ function checksOf(schema) {
89
+ const schemaDef = defOf(unwrap(schema).schema);
90
+ const checks = [
91
+ ...(schemaDef.checks || []),
92
+ ...(raw(schema)?.checks || []),
93
+ ];
94
+ return checks.map((check) => ({
95
+ raw: check,
96
+ kind: check.kind || check.check || check._zod?.def?.check || check.def?.check || check._def?.check || '',
97
+ value: check.value ?? check.minimum ?? check.maximum ?? check.length ?? check._zod?.def?.value,
98
+ format: check.format || check._zod?.def?.format || check.def?.format || '',
99
+ message: check.message || check._zod?.def?.message || check.def?.message || '',
100
+ }));
101
+ }
102
+
103
+ function schemaFormat(schema) {
104
+ const plain = raw(schema);
105
+ if (plain?.format) return plain.format;
106
+ if (plain?.email) return 'email';
107
+ if (plain?.url) return 'url';
108
+ const checks = checksOf(schema);
109
+ if (checks.some((check) => /email/i.test(`${check.kind} ${check.format}`))) return 'email';
110
+ if (checks.some((check) => /url/i.test(`${check.kind} ${check.format}`))) return 'url';
111
+ return '';
112
+ }
113
+
114
+ function schemaValidator(schema, optional) {
115
+ if (!schema || (!hasCallable(schema, 'safeParse') && !hasCallable(schema, 'parse'))) return null;
116
+
117
+ return {
118
+ name: 'schema',
119
+ message: 'This field does not match the schema.',
120
+ validate(value) {
121
+ const normalized = optional && value === '' ? undefined : value;
122
+ const result = call(schema, 'safeParse', normalized);
123
+ if (result) {
124
+ if (result.success) return true;
125
+ return result.error?.issues?.[0]?.message || result.error?.message || 'This field does not match the schema.';
126
+ }
127
+ try {
128
+ call(schema, 'parse', normalized);
129
+ return true;
130
+ } catch (error) {
131
+ return error?.issues?.[0]?.message || error?.message || 'This field does not match the schema.';
132
+ }
133
+ },
134
+ };
135
+ }
136
+
137
+ function plainValidator(schema) {
138
+ const plain = raw(schema);
139
+ if (!plain || typeof plain !== 'object' || hasCallable(plain, 'safeParse') || hasCallable(plain, 'parse')) return null;
140
+
141
+ return {
142
+ name: 'schema',
143
+ validate(value) {
144
+ if ((plain.optional || plain.required === false) && isEmpty(value)) return true;
145
+ if (plain.minLength != null && String(value || '').length < plain.minLength) return `Use at least ${plain.minLength} characters.`;
146
+ if (plain.maxLength != null && String(value || '').length > plain.maxLength) return `Use ${plain.maxLength} characters or fewer.`;
147
+ if (plain.min != null && Number(value) < plain.min) return `Use a value of ${plain.min} or greater.`;
148
+ if (plain.max != null && Number(value) > plain.max) return `Use a value of ${plain.max} or less.`;
149
+ if (plain.regex && !new RegExp(plain.regex).test(String(value || ''))) return plain.message || 'This field does not match the required pattern.';
150
+ return true;
151
+ },
152
+ };
153
+ }
154
+
155
+ function componentFor(schema, path, options) {
156
+ const plain = raw(schema) || {};
157
+ const type = String(typeOf(schema) || plain.type || '').toLowerCase();
158
+ const format = schemaFormat(schema);
159
+ const values = enumValues(schema) || plain.values || plain.options || plain.enum;
160
+ const override = fieldOptions(options, path, path.split('.').pop(), schema).component || plain.component;
161
+
162
+ if (override) return override;
163
+ if (values) return 'DomSelect';
164
+ if (format === 'binary' || format === 'file') return 'DomFileUpload';
165
+ if (format === 'currency' || format === 'money' || format === 'decimal') return 'DomMoneyInput';
166
+ if (format === 'email') return 'DomEmailInput';
167
+ if (format === 'url') return 'DomUrlInput';
168
+ if (format === 'time') return 'DomTimeSelector';
169
+ if (format === 'date-time' || format === 'datetime') return 'DomDateTimePicker';
170
+ if (/decimal|currency|money/.test(type)) return 'DomMoneyInput';
171
+ if (/file/.test(type)) return 'DomFileUpload';
172
+ if (/number|bigint|int|float|double/.test(type)) return 'DomNumberInput';
173
+ if (/boolean|bool/.test(type)) return 'DomToggle';
174
+ if (/datetime|date-time/.test(type)) return 'DomDateTimePicker';
175
+ if (/time/.test(type)) return 'DomTimeSelector';
176
+ if (/date/.test(type)) return 'DomCalendar';
177
+ return 'DomTextInput';
178
+ }
179
+
180
+ function fieldPropsFor(schema, path, options) {
181
+ const key = path.split('.').pop();
182
+ const plain = raw(schema) || {};
183
+ const field = fieldOptions(options, path, key, schema);
184
+ const unwrapped = unwrap(schema);
185
+ const values = enumValues(schema) || plain.values || plain.options || plain.enum;
186
+ const validators = [
187
+ schemaValidator(schema, unwrapped.optional),
188
+ plainValidator(schema),
189
+ ...(field.props?.validators || []),
190
+ ].filter(Boolean);
191
+
192
+ const props = {
193
+ name: key,
194
+ label: field.label || plain.label || plain.description || raw(schema)?.description || formatLabel(key),
195
+ description: field.description || plain.help || plain.hint || '',
196
+ required: field.required ?? plain.required ?? !unwrapped.optional,
197
+ ...(plain.placeholder ? { placeholder: plain.placeholder } : {}),
198
+ ...(validators.length ? { validators } : {}),
199
+ ...(field.props || {}),
200
+ };
201
+
202
+ if (values && !props.options) props.options = optionObjects(Array.isArray(values) ? values : Object.values(values));
203
+ if (plain.min != null && props.min == null) props.min = plain.min;
204
+ if (plain.max != null && props.max == null) props.max = plain.max;
205
+ if (plain.step != null && props.step == null) props.step = plain.step;
206
+
207
+ return props;
208
+ }
209
+
210
+ function childrenForShape(shape, scope, options) {
211
+ return childrenForFieldShape(shape, scope, options, {
212
+ idPrefix: 'schema',
213
+ nestedShape: objectShape,
214
+ fieldForNested(childSchema, path, key, adapterOptions) {
215
+ return fieldOptions(adapterOptions, path, key, childSchema);
216
+ },
217
+ componentFor,
218
+ fieldPropsFor(childSchema, path, _key, adapterOptions) {
219
+ return fieldPropsFor(childSchema, path, adapterOptions);
220
+ },
221
+ });
222
+ }
223
+
224
+ export function zodSchemaToChildren(schema, options = {}) {
225
+ if (!schema) return [];
226
+ const shape = objectShape(schema) || raw(schema)?.shape || raw(schema)?.fields;
227
+ if (!shape) return [];
228
+ return childrenForShape(typeof shape === 'function' ? shape() : shape, '', options);
229
+ }
230
+
231
+ export const zodSchemaAdapter = {
232
+ name: 'zod',
233
+ matches: isZodLikeSchema,
234
+ toChildren: zodSchemaToChildren,
235
+ };
@@ -0,0 +1,38 @@
1
+ import { reactive } from 'vue';
2
+
3
+ export const forms = reactive({});
4
+
5
+ export function getPathValue(source, path) {
6
+ if (!path) return source;
7
+ return String(path)
8
+ .split('.')
9
+ .filter(Boolean)
10
+ .reduce((value, part) => value?.[part], source);
11
+ }
12
+
13
+ export function setPathValue(source, path, value) {
14
+ const parts = String(path).split('.').filter(Boolean);
15
+ if (!parts.length) return;
16
+ let target = source;
17
+ for (let index = 0; index < parts.length - 1; index += 1) {
18
+ const part = parts[index];
19
+ const nextPart = parts[index + 1];
20
+ if (target[part] == null || typeof target[part] !== 'object') {
21
+ target[part] = /^\d+$/.test(nextPart) ? [] : {};
22
+ }
23
+ target = target[part];
24
+ }
25
+ target[parts[parts.length - 1]] = value;
26
+ }
27
+
28
+ export function deletePathValue(source, path) {
29
+ const parts = String(path).split('.').filter(Boolean);
30
+ if (!parts.length) return;
31
+ const key = parts.pop();
32
+ const target = getPathValue(source, parts.join('.'));
33
+ if (target && typeof target === 'object') delete target[key];
34
+ }
35
+
36
+ export function createFormRegistry() {
37
+ return reactive({});
38
+ }