@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,267 @@
1
+ <script setup>
2
+ import { computed, inject, provide, reactive, unref, useAttrs } from 'vue';
3
+ import { fieldLayoutProviderKey } from './fieldLayout.js';
4
+ import { fieldDisplayProviderKey, normalizeErrors } from './useField.js';
5
+
6
+ defineOptions({
7
+ inheritAttrs: false,
8
+ __doc: {
9
+ name: 'Field',
10
+ tag: '<DomField>',
11
+ description: 'Shared form chrome for labels, required markers, descriptions, errors, and custom controls.',
12
+ slots: [
13
+ { name: '(default)', payload: '{ invalid, errors, htmlFor, errorId, fields, fieldAttrs }', description: 'The form control or custom interactive content.' },
14
+ { name: 'label', payload: '{ label, required, htmlFor, invalid, errors, fields, fieldAttrs }', description: 'Replace the default label while keeping field layout.' },
15
+ { name: 'errors', payload: '{ errors, errorId, invalid, fields, fieldAttrs }', description: 'Replace the default error list.' },
16
+ { name: 'description', payload: '{ description, descriptionId, fields, fieldAttrs }', description: 'Replace the default helper text.' },
17
+ ],
18
+ },
19
+ });
20
+
21
+ // Field chrome shared by every form input: a small upper label, the control,
22
+ // and a description below. All form components in the library wrap their
23
+ // control in <DomField> so they pick up consistent labelling out of the box.
24
+ //
25
+ // <DomField label="Email" description="We'll never share it.">
26
+ // <input … />
27
+ // </DomField>
28
+ const props = defineProps({
29
+ label: {
30
+ type: String,
31
+ default: '',
32
+ _edit: { description: 'Small label shown above the control.' },
33
+ },
34
+ description: {
35
+ type: String,
36
+ default: '',
37
+ _edit: { description: 'Helper text shown below the control.' },
38
+ },
39
+ htmlFor: {
40
+ type: String,
41
+ default: '',
42
+ _edit: { description: 'ID of the control the label should target.' },
43
+ },
44
+ descriptionId: {
45
+ type: String,
46
+ default: '',
47
+ },
48
+ errorId: {
49
+ type: String,
50
+ default: '',
51
+ },
52
+ invalid: {
53
+ type: Boolean,
54
+ default: false,
55
+ _edit: { description: 'Mark the field invalid and expose data-invalid for styling.' },
56
+ },
57
+ errors: {
58
+ type: [Array, Object, String],
59
+ default: () => [],
60
+ _edit: { component: 'DomJsonInput', description: 'Validation errors to show below the control.' },
61
+ },
62
+ required: {
63
+ type: Boolean,
64
+ default: false,
65
+ _edit: { description: 'Show a required marker next to the label.' },
66
+ },
67
+ visible: {
68
+ type: Boolean,
69
+ default: true,
70
+ _edit: { description: 'Show or hide the field wrapper.' },
71
+ },
72
+ chrome: {
73
+ type: [String, Boolean],
74
+ default: 'field',
75
+ _edit: { options: ['field', false], description: 'Render the field wrapper, or false to provide state without rendering chrome.' },
76
+ },
77
+ fieldLayout: {
78
+ type: [Object, Function, String],
79
+ default: null,
80
+ _edit: { description: 'Optional component used to render this field layout.' },
81
+ },
82
+ });
83
+
84
+ const attrs = useAttrs();
85
+ const providedFieldLayout = inject(fieldLayoutProviderKey, null);
86
+ const registeredFields = reactive(new Map());
87
+
88
+ provide(fieldDisplayProviderKey, {
89
+ registerField(key, field) {
90
+ registeredFields.set(key, field);
91
+ },
92
+ unregisterField(key) {
93
+ registeredFields.delete(key);
94
+ },
95
+ });
96
+
97
+ const uniqueErrors = (errors) => {
98
+ const seen = new Set();
99
+ return errors.filter((error) => {
100
+ const key = String(error);
101
+ if (seen.has(key)) return false;
102
+ seen.add(key);
103
+ return true;
104
+ });
105
+ };
106
+ const unrefOr = (value, fallback = undefined) => {
107
+ const resolved = unref(value);
108
+ return resolved === undefined ? fallback : resolved;
109
+ };
110
+ const fieldAttrsFromRegistration = (field) => unrefOr(field?.fieldAttrs, {}) || {};
111
+ const fieldValue = (field, key) => {
112
+ if (!field) return undefined;
113
+ if (key in field) return unrefOr(field[key]);
114
+ return unrefOr(fieldAttrsFromRegistration(field)[key]);
115
+ };
116
+ const fieldVisible = (field) => fieldValue(field, 'visible') !== false;
117
+ const fieldErrors = (field) => normalizeErrors(fieldValue(field, 'errors'));
118
+ const fieldInvalid = (field) => Boolean(fieldValue(field, 'invalid') || fieldErrors(field).length);
119
+ const hasInvalidAttr = computed(() => (
120
+ attrs['data-invalid'] === ''
121
+ || attrs['data-invalid'] === true
122
+ || attrs['data-invalid'] === 'true'
123
+ ));
124
+ const fields = computed(() => Array.from(registeredFields.values()));
125
+ const visibleFields = computed(() => fields.value.filter(fieldVisible));
126
+ const firstField = computed(() => visibleFields.value[0] || fields.value[0] || null);
127
+ const firstFieldAttrs = computed(() => fieldAttrsFromRegistration(firstField.value));
128
+ const hasChrome = computed(() => props.chrome !== false && props.chrome !== 'none');
129
+ const resolvedFieldLayout = computed(() => props.fieldLayout || unref(providedFieldLayout) || null);
130
+ const errorMessages = computed(() => uniqueErrors([
131
+ ...normalizeErrors(props.errors),
132
+ ...visibleFields.value.flatMap(fieldErrors),
133
+ ]));
134
+ const resolvedHtmlFor = computed(() => (
135
+ props.htmlFor
136
+ || firstFieldAttrs.value.htmlFor
137
+ || fieldValue(firstField.value, 'htmlId')
138
+ || fieldValue(firstField.value, 'id')
139
+ || ''
140
+ ));
141
+ const resolvedErrorId = computed(() => (
142
+ props.errorId
143
+ || firstFieldAttrs.value.errorId
144
+ || (errorMessages.value.length && resolvedHtmlFor.value ? `${resolvedHtmlFor.value}-error` : '')
145
+ ));
146
+ const resolvedDescription = computed(() => props.description || firstFieldAttrs.value.description || '');
147
+ const resolvedDescriptionId = computed(() => (
148
+ props.descriptionId
149
+ || firstFieldAttrs.value.descriptionId
150
+ || (resolvedDescription.value && resolvedHtmlFor.value ? `${resolvedHtmlFor.value}-description` : '')
151
+ ));
152
+ const resolvedLabel = computed(() => props.label || firstFieldAttrs.value.label || '');
153
+ const resolvedRequired = computed(() => Boolean(
154
+ props.required
155
+ || firstFieldAttrs.value.required
156
+ || visibleFields.value.some((field) => fieldValue(field, 'required'))
157
+ ));
158
+ const isInvalid = computed(() => (
159
+ props.invalid
160
+ || errorMessages.value.length > 0
161
+ || hasInvalidAttr.value
162
+ || visibleFields.value.some(fieldInvalid)
163
+ ));
164
+ const isVisible = computed(() => (
165
+ props.visible !== false
166
+ && (!fields.value.length || visibleFields.value.length > 0)
167
+ ));
168
+ const fieldAttrs = computed(() => ({
169
+ label: resolvedLabel.value,
170
+ description: resolvedDescription.value,
171
+ htmlFor: resolvedHtmlFor.value,
172
+ descriptionId: resolvedDescriptionId.value,
173
+ errorId: resolvedErrorId.value,
174
+ invalid: isInvalid.value,
175
+ required: resolvedRequired.value,
176
+ errors: errorMessages.value,
177
+ visible: isVisible.value,
178
+ }));
179
+ const slotProps = computed(() => ({
180
+ ...fieldAttrs.value,
181
+ fields: visibleFields.value,
182
+ fieldAttrs: fieldAttrs.value,
183
+ }));
184
+
185
+ provide(fieldLayoutProviderKey, resolvedFieldLayout);
186
+ </script>
187
+
188
+ <template>
189
+ <template v-if="isVisible">
190
+ <slot v-if="!hasChrome" v-bind="slotProps" />
191
+ <component
192
+ :is="resolvedFieldLayout"
193
+ v-else-if="resolvedFieldLayout"
194
+ v-bind="{ ...attrs, ...slotProps }"
195
+ :data-invalid="isInvalid ? '' : undefined"
196
+ >
197
+ <template #label="layoutSlotProps">
198
+ <slot name="label" v-bind="{ ...slotProps, ...layoutSlotProps }">
199
+ <component
200
+ :is="resolvedHtmlFor ? 'label' : 'p'"
201
+ v-if="resolvedLabel"
202
+ v-bind="resolvedHtmlFor ? { for: resolvedHtmlFor } : {}"
203
+ class="block text-[11px] font-medium uppercase tracking-wider text-muted-fg"
204
+ >
205
+ {{ resolvedLabel }}
206
+ <span v-if="resolvedRequired" class="text-destructive">*</span>
207
+ </component>
208
+ </slot>
209
+ </template>
210
+ <template #default="layoutSlotProps">
211
+ <slot v-bind="{ ...slotProps, ...layoutSlotProps }" />
212
+ </template>
213
+ <template #errors="layoutSlotProps">
214
+ <slot name="errors" v-bind="{ ...slotProps, ...layoutSlotProps }">
215
+ <p
216
+ v-for="(error, index) in errorMessages"
217
+ :key="`${error}-${index}`"
218
+ :id="index === 0 ? resolvedErrorId : undefined"
219
+ class="text-[11px] leading-snug text-destructive"
220
+ >
221
+ {{ error }}
222
+ </p>
223
+ </slot>
224
+ </template>
225
+ <template #description="layoutSlotProps">
226
+ <slot name="description" v-bind="{ ...slotProps, ...layoutSlotProps }">
227
+ <p
228
+ v-if="resolvedDescription"
229
+ :id="resolvedDescriptionId || undefined"
230
+ class="text-[11px] leading-snug text-muted-fg"
231
+ >{{ resolvedDescription }}</p>
232
+ </slot>
233
+ </template>
234
+ </component>
235
+ <div v-else v-bind="attrs" class="space-y-1.5 w-full" :data-invalid="isInvalid ? '' : undefined">
236
+ <slot name="label" v-bind="slotProps">
237
+ <component
238
+ :is="resolvedHtmlFor ? 'label' : 'p'"
239
+ v-if="resolvedLabel"
240
+ v-bind="resolvedHtmlFor ? { for: resolvedHtmlFor } : {}"
241
+ class="block text-[11px] font-medium uppercase tracking-wider text-muted-fg"
242
+ >
243
+ {{ resolvedLabel }}
244
+ <span v-if="resolvedRequired" class="text-destructive">*</span>
245
+ </component>
246
+ </slot>
247
+ <slot v-bind="slotProps" />
248
+ <slot name="errors" v-bind="slotProps">
249
+ <p
250
+ v-for="(error, index) in errorMessages"
251
+ :key="`${error}-${index}`"
252
+ :id="index === 0 ? resolvedErrorId : undefined"
253
+ class="text-[11px] leading-snug text-destructive"
254
+ >
255
+ {{ error }}
256
+ </p>
257
+ </slot>
258
+ <slot name="description" v-bind="slotProps">
259
+ <p
260
+ v-if="resolvedDescription"
261
+ :id="resolvedDescriptionId || undefined"
262
+ class="text-[11px] leading-snug text-muted-fg"
263
+ >{{ resolvedDescription }}</p>
264
+ </slot>
265
+ </div>
266
+ </template>
267
+ </template>
@@ -0,0 +1 @@
1
+ export const fieldLayoutProviderKey = Symbol('domStudio.fieldLayoutProvider');
@@ -0,0 +1,114 @@
1
+ export const FIELD_PROPS_GROUP = 'Field props';
2
+
3
+ /**
4
+ * Adds the shared field prop group metadata to a field prop definition.
5
+ *
6
+ * @param {Record<string, unknown>} definition Vue prop definition.
7
+ * @returns {Record<string, unknown>} Field prop definition with editor metadata.
8
+ */
9
+ function fieldProp(definition) {
10
+ return {
11
+ ...definition,
12
+ _edit: {
13
+ group: FIELD_PROPS_GROUP,
14
+ ...(definition._edit || {}),
15
+ },
16
+ };
17
+ }
18
+
19
+ export const fieldProps = {
20
+ modelValue: fieldProp({
21
+ default: '',
22
+ _edit: { description: 'Current field value.' },
23
+ }),
24
+ id: fieldProp({
25
+ type: String,
26
+ default: '',
27
+ _edit: { description: 'Optional ID override. By default parent forms derive the input ID from the field path using underscores.' },
28
+ }),
29
+ name: fieldProp({
30
+ type: String,
31
+ default: '',
32
+ _edit: { description: 'Local field name. Parent forms derive the full field path and native HTML name from the form hierarchy.' },
33
+ }),
34
+ label: fieldProp({
35
+ type: String,
36
+ default: '',
37
+ _edit: { description: 'Visible field label.' },
38
+ }),
39
+ description: fieldProp({
40
+ type: String,
41
+ default: '',
42
+ _edit: { description: 'Optional helper copy below the field.' },
43
+ }),
44
+ placeholder: fieldProp({
45
+ type: String,
46
+ default: '',
47
+ _edit: { description: 'Placeholder shown when the control is empty.' },
48
+ }),
49
+ required: fieldProp({
50
+ type: Boolean,
51
+ default: false,
52
+ _edit: { description: 'Mark the field as required.' },
53
+ }),
54
+ disabled: fieldProp({
55
+ type: Boolean,
56
+ default: false,
57
+ _edit: { description: 'Disable field interaction.' },
58
+ }),
59
+ readOnly: fieldProp({
60
+ type: Boolean,
61
+ default: false,
62
+ _edit: { description: 'Show the value but prevent editing.' },
63
+ }),
64
+ invalid: fieldProp({
65
+ type: Boolean,
66
+ default: false,
67
+ _edit: { description: 'Mark the field invalid.' },
68
+ }),
69
+ errors: fieldProp({
70
+ type: [Array, Object, String],
71
+ default: () => [],
72
+ _edit: {
73
+ component: 'DomJsonListInput',
74
+ description: 'Validation errors for this field.',
75
+ schema: [
76
+ {
77
+ "key": "name",
78
+ "label": "Name",
79
+ "placeholder": "Validation name"
80
+ },
81
+ {
82
+ "key": "message",
83
+ "label": "Message",
84
+ "placeholder": "Error message"
85
+ },
86
+ ]
87
+ }
88
+ }),
89
+ visible: fieldProp({
90
+ type: Boolean,
91
+ default: true,
92
+ _edit: { description: 'Show or hide the field.' },
93
+ }),
94
+ validators: fieldProp({
95
+ type: Array,
96
+ default: () => [],
97
+ _edit: {
98
+ component: 'DomValidatorInput',
99
+ description: 'Validators attached to this field. Use functions in Vue code, or serializable records such as { name: "minLength", props: { min: 2 } } in generated schemas.',
100
+ },
101
+ }),
102
+ validateOnBlur: fieldProp({
103
+ type: Boolean,
104
+ default: true,
105
+ _edit: { description: 'Run validators when the field loses focus.' },
106
+ }),
107
+ chrome: fieldProp({
108
+ type: [String, Boolean],
109
+ default: 'field',
110
+ _edit: { options: ['field', 'none', false], description: 'Render default field chrome, or hide chrome while keeping form state wiring.' },
111
+ }),
112
+ };
113
+
114
+ export const fieldPropNames = Object.keys(fieldProps);
@@ -0,0 +1,38 @@
1
+ <script setup>
2
+ import { useAttrs } from 'vue';
3
+
4
+ defineOptions({
5
+ inheritAttrs: false,
6
+ });
7
+
8
+ defineProps({
9
+ label: { type: String, default: '' },
10
+ htmlFor: { type: String, default: '' },
11
+ description: { type: String, default: '' },
12
+ descriptionId: { type: String, default: '' },
13
+ errorId: { type: String, default: '' },
14
+ invalid: { type: Boolean, default: false },
15
+ errors: { type: Array, default: () => [] },
16
+ required: { type: Boolean, default: false },
17
+ visible: { type: Boolean, default: true },
18
+ fields: { type: Array, default: () => [] },
19
+ fieldAttrs: { type: Object, default: () => ({}) },
20
+ });
21
+
22
+ const attrs = useAttrs();
23
+ </script>
24
+
25
+ <template>
26
+ <div
27
+ v-bind="attrs"
28
+ class="min-w-0 space-y-1"
29
+ :data-invalid="invalid ? '' : undefined"
30
+ >
31
+ <label v-if="label" :for="htmlFor || undefined" class="sr-only">
32
+ {{ label }}<span v-if="required"> required</span>
33
+ </label>
34
+ <slot />
35
+ <slot name="errors" :errors="errors" :error-id="errorId" :invalid="invalid" />
36
+ <slot name="description" :description="description" :description-id="descriptionId" />
37
+ </div>
38
+ </template>