@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,296 @@
1
+ <script setup>
2
+ import { computed, onBeforeUnmount, ref } from 'vue';
3
+ import DomField from '../field/DomField.vue';
4
+ import { fieldProps } from '../field/fieldProps.js';
5
+ import { useField } from '../field/useField.js';
6
+
7
+ defineOptions({
8
+ __doc: {
9
+ name: 'Range input',
10
+ tag: '<DomRangeInput>',
11
+ description: 'A compact slider input for numeric values with a stylable track and handle.',
12
+ icon: 'M5 12h14M8 12a2 2 0 1 0 0.01 0M16 12a2 2 0 1 0 0.01 0',
13
+ slots: [],
14
+ playground: {
15
+ initial: {
16
+ label: 'Confidence',
17
+ modelValue: 64,
18
+ suffix: '%',
19
+ handleClass: 'size-5 border-2 border-canvas shadow-md group-hover:scale-110',
20
+ activeHandleClass: 'scale-125 ring-4 ring-primary/20',
21
+ },
22
+ },
23
+ },
24
+ });
25
+
26
+ const props = defineProps({
27
+ ...fieldProps,
28
+ modelValue: {
29
+ type: [Number, String],
30
+ default: 0,
31
+ _edit: { description: 'Current numeric value.' },
32
+ },
33
+ min: {
34
+ type: Number,
35
+ default: 0,
36
+ _edit: { description: 'Minimum slider value.' },
37
+ },
38
+ max: {
39
+ type: Number,
40
+ default: 100,
41
+ _edit: { description: 'Maximum slider value.' },
42
+ },
43
+ step: {
44
+ type: Number,
45
+ default: 1,
46
+ _edit: { description: 'Slider increment.' },
47
+ },
48
+ suffix: {
49
+ type: String,
50
+ default: '',
51
+ _edit: { description: 'Optional unit shown beside the value.' },
52
+ },
53
+ showValue: {
54
+ type: Boolean,
55
+ default: true,
56
+ _edit: { description: 'Show the current value beside the slider.' },
57
+ },
58
+ trackClass: {
59
+ type: [String, Array, Object],
60
+ default: '',
61
+ _edit: { description: 'Classes applied to the visual range track.' },
62
+ },
63
+ progressClass: {
64
+ type: [String, Array, Object],
65
+ default: '',
66
+ _edit: { description: 'Classes applied to the filled range track.' },
67
+ },
68
+ handleClass: {
69
+ type: [String, Array, Object],
70
+ default: '',
71
+ _edit: { description: 'Classes applied to the visual slider handle.' },
72
+ },
73
+ activeHandleClass: {
74
+ type: [String, Array, Object],
75
+ default: '',
76
+ _edit: { description: 'Additional classes applied to the handle while it is being dragged.' },
77
+ },
78
+ });
79
+ const emit = defineEmits(['update:modelValue']);
80
+ const field = useField(props, emit, { idPrefix: 'dom-range-input' });
81
+ const control = ref(null);
82
+ const rangeInput = ref(null);
83
+ const dragging = ref(false);
84
+
85
+ const displayValue = computed(() => {
86
+ const value = field.value.value === '' ? props.min : Number(field.value.value);
87
+ return Number.isFinite(value) ? value : props.min;
88
+ });
89
+
90
+ const progress = computed(() => {
91
+ const min = Number(props.min);
92
+ const max = Number(props.max);
93
+ const value = displayValue.value;
94
+ const range = max - min;
95
+
96
+ if (!Number.isFinite(min) || !Number.isFinite(max) || range <= 0) return 0;
97
+
98
+ return Math.min(100, Math.max(0, ((value - min) / range) * 100));
99
+ });
100
+
101
+ const rangeStyle = computed(() => ({
102
+ '--dom-range-progress': `${progress.value}%`,
103
+ }));
104
+
105
+ const progressStyle = computed(() => ({
106
+ width: `${progress.value}%`,
107
+ }));
108
+
109
+ const handleStyle = computed(() => ({
110
+ left: `${progress.value}%`,
111
+ }));
112
+
113
+ const controlClass = computed(() => [
114
+ 'dom-range-input__control group relative min-h-7 w-full rounded-full outline-none',
115
+ field.disabled.value || field.readOnly.value ? 'cursor-not-allowed' : 'cursor-pointer',
116
+ (field.disabled.value || field.readOnly.value) && 'opacity-50',
117
+ ]);
118
+
119
+ const inputClass = computed(() => [
120
+ 'pointer-events-none absolute inset-x-0 top-1/2 z-10 m-0 h-8 w-full -translate-y-1/2 opacity-0',
121
+ ]);
122
+
123
+ const rangeInputAttrs = computed(() => ({
124
+ ...field.inputAttrs.value,
125
+ 'aria-readonly': field.readOnly.value || undefined,
126
+ }));
127
+
128
+ const trackClassName = computed(() => [
129
+ 'dom-range-input__bar pointer-events-none absolute inset-x-0 top-1/2 h-2 -translate-y-1/2 rounded-full bg-secondary',
130
+ props.trackClass,
131
+ ]);
132
+
133
+ const progressClassName = computed(() => [
134
+ 'dom-range-input__bar pointer-events-none absolute left-0 top-1/2 h-2 -translate-y-1/2 rounded-full bg-primary',
135
+ props.progressClass,
136
+ ]);
137
+
138
+ const handleClassName = computed(() => [
139
+ 'pointer-events-none absolute top-1/2 z-0 size-4 -translate-x-1/2 -translate-y-1/2 rounded-full bg-primary shadow-sm transition duration-150',
140
+ props.handleClass,
141
+ dragging.value && props.activeHandleClass,
142
+ ]);
143
+
144
+ function toNumber(value) {
145
+ const number = Number(value);
146
+ return Number.isNaN(number) ? value : number;
147
+ }
148
+
149
+ function onInput(value, event) {
150
+ if (field.readOnly.value) {
151
+ event.target.value = displayValue.value;
152
+ return;
153
+ }
154
+
155
+ field.onInput(toNumber(value));
156
+ }
157
+
158
+ function beginDrag(event) {
159
+ if (field.disabled.value || field.readOnly.value) {
160
+ event.preventDefault();
161
+ return;
162
+ }
163
+
164
+ event.preventDefault();
165
+ rangeInput.value?.focus({ preventScroll: true });
166
+ dragging.value = true;
167
+ updateFromPointer(event);
168
+ window.addEventListener('pointermove', moveDrag);
169
+ window.addEventListener('pointerup', endDrag, { once: true });
170
+ window.addEventListener('pointercancel', endDrag, { once: true });
171
+ }
172
+
173
+ function moveDrag(event) {
174
+ event.preventDefault();
175
+ updateFromPointer(event);
176
+ }
177
+
178
+ function endDrag() {
179
+ dragging.value = false;
180
+ window.removeEventListener('pointermove', moveDrag);
181
+ window.removeEventListener('pointerup', endDrag);
182
+ window.removeEventListener('pointercancel', endDrag);
183
+ }
184
+
185
+ function updateFromPointer(event) {
186
+ field.onInput(valueFromPointer(event));
187
+ }
188
+
189
+ function valueFromPointer(event) {
190
+ const rect = control.value?.getBoundingClientRect();
191
+ const min = Number(props.min);
192
+ const max = Number(props.max);
193
+ const range = max - min;
194
+
195
+ if (!rect || !Number.isFinite(min) || !Number.isFinite(max) || range <= 0) return displayValue.value;
196
+
197
+ const ratio = Math.min(1, Math.max(0, (event.clientX - rect.left) / rect.width));
198
+ return snapValue(min + ratio * range);
199
+ }
200
+
201
+ function onKeydown(event) {
202
+ if (field.disabled.value || field.readOnly.value) return;
203
+
204
+ const step = stepSize();
205
+ const current = displayValue.value;
206
+ const min = Number(props.min);
207
+ const max = Number(props.max);
208
+ let next;
209
+
210
+ if (event.key === 'ArrowRight' || event.key === 'ArrowUp') next = current + step;
211
+ else if (event.key === 'ArrowLeft' || event.key === 'ArrowDown') next = current - step;
212
+ else if (event.key === 'PageUp') next = current + step * 10;
213
+ else if (event.key === 'PageDown') next = current - step * 10;
214
+ else if (event.key === 'Home' && Number.isFinite(min)) next = min;
215
+ else if (event.key === 'End' && Number.isFinite(max)) next = max;
216
+ else return;
217
+
218
+ event.preventDefault();
219
+ field.onInput(snapValue(next));
220
+ }
221
+
222
+ function snapValue(value) {
223
+ const min = Number(props.min);
224
+ const max = Number(props.max);
225
+ const step = stepSize();
226
+ let next = value;
227
+
228
+ if (Number.isFinite(min)) next = Math.max(min, next);
229
+ if (Number.isFinite(max)) next = Math.min(max, next);
230
+ if (Number.isFinite(min) && step > 0) next = min + Math.round((next - min) / step) * step;
231
+ if (Number.isFinite(min)) next = Math.max(min, next);
232
+ if (Number.isFinite(max)) next = Math.min(max, next);
233
+
234
+ return roundToStep(next, step);
235
+ }
236
+
237
+ function stepSize() {
238
+ const step = Number(props.step);
239
+ return Number.isFinite(step) && step > 0 ? step : 1;
240
+ }
241
+
242
+ function roundToStep(value, step) {
243
+ const decimals = decimalPlaces(step);
244
+ return Number(value.toFixed(Math.min(12, decimals + 2)));
245
+ }
246
+
247
+ function decimalPlaces(value) {
248
+ const text = String(value);
249
+ if (text.includes('e-')) return Number(text.split('e-')[1]) || 0;
250
+ const decimal = text.split('.')[1];
251
+ return decimal ? decimal.length : 0;
252
+ }
253
+
254
+ onBeforeUnmount(endDrag);
255
+ </script>
256
+
257
+ <template>
258
+ <DomField v-bind="field.fieldAttrs.value" :chrome="chrome">
259
+ <div class="flex w-full items-center gap-3">
260
+ <div
261
+ ref="control"
262
+ :class="controlClass"
263
+ :style="rangeStyle"
264
+ :data-dragging="dragging ? 'true' : undefined"
265
+ @pointerdown="beginDrag"
266
+ >
267
+ <span aria-hidden="true" :class="trackClassName"></span>
268
+ <span aria-hidden="true" :class="progressClassName" :style="progressStyle"></span>
269
+ <span aria-hidden="true" :class="handleClassName" :style="handleStyle"></span>
270
+ <input
271
+ ref="rangeInput"
272
+ v-bind="rangeInputAttrs"
273
+ type="range"
274
+ :min="min"
275
+ :max="max"
276
+ :step="step"
277
+ :value="displayValue"
278
+ :class="inputClass"
279
+ @input="onInput($event.target.value, $event)"
280
+ @keydown="onKeydown"
281
+ @focus="field.onFocus"
282
+ @blur="field.onBlur"
283
+ />
284
+ </div>
285
+ <output v-if="showValue" class="min-w-10 text-right font-mono text-xs text-muted-fg">
286
+ {{ displayValue }}{{ suffix }}
287
+ </output>
288
+ </div>
289
+ </DomField>
290
+ </template>
291
+
292
+ <style scoped>
293
+ .dom-range-input__bar {
294
+ box-shadow: inset 0 1px 2px color-mix(in oklch, var(--border) 70%, transparent);
295
+ }
296
+ </style>