@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,5 @@
1
+ /* eslint-disable */
2
+ // Generated by scripts/generate-types.mjs. Do not edit by hand.
3
+ export declare function hasCustomTransitions(host: HTMLElement): boolean;
4
+ export declare function runEnter(host: HTMLElement, el: HTMLElement, options?: { signal?: AbortSignal }): Promise<void>;
5
+ export declare function runLeave(host: HTMLElement, el: HTMLElement, options?: { signal?: AbortSignal }): Promise<void>;
@@ -0,0 +1,89 @@
1
+ // Headless UI-style enter/leave class transitions.
2
+ // Reads `enter`, `enter-from`, `enter-to`, `leave`, `leave-from`, `leave-to`
3
+ // attributes from a host element and applies the class strings to `el`.
4
+
5
+ const ENTER_KEYS = ['enter', 'enter-from', 'enter-to'];
6
+ const LEAVE_KEYS = ['leave', 'leave-from', 'leave-to'];
7
+
8
+ export function hasCustomTransitions(host) {
9
+ return ENTER_KEYS.some((k) => host.hasAttribute(k))
10
+ || LEAVE_KEYS.some((k) => host.hasAttribute(k));
11
+ }
12
+
13
+ function splitClasses(value) {
14
+ return (value ?? '').trim().split(/\s+/).filter(Boolean);
15
+ }
16
+
17
+ function durationMs(classLists) {
18
+ const text = classLists.flat().join(' ');
19
+ let ms = 0;
20
+ const bracket = text.match(/duration-\[(\d+(?:\.\d+)?)(ms|s)?\]/);
21
+ if (bracket) {
22
+ const n = parseFloat(bracket[1]);
23
+ ms = Math.max(ms, bracket[2] === 's' ? n * 1000 : n);
24
+ }
25
+ for (const m of text.matchAll(/\bduration-(\d+)\b/g)) {
26
+ ms = Math.max(ms, Number(m[1]));
27
+ }
28
+ return ms || 200;
29
+ }
30
+
31
+ function nextFrame() {
32
+ return new Promise((resolve) => {
33
+ requestAnimationFrame(() => requestAnimationFrame(resolve));
34
+ });
35
+ }
36
+
37
+ function waitForTransition(classLists, signal) {
38
+ return new Promise((resolve) => {
39
+ let settled = false;
40
+ const done = () => {
41
+ if (settled) return;
42
+ settled = true;
43
+ clearTimeout(timer);
44
+ signal?.removeEventListener('abort', done);
45
+ resolve();
46
+ };
47
+ const timer = setTimeout(done, durationMs(classLists) + 50);
48
+ signal?.addEventListener('abort', done, { once: true });
49
+ });
50
+ }
51
+
52
+ function setClasses(el, add, remove) {
53
+ if (remove.length) el.classList.remove(...remove);
54
+ if (add.length) el.classList.add(...add);
55
+ }
56
+
57
+ function forceStyleFlush(el) {
58
+ el.getBoundingClientRect();
59
+ }
60
+
61
+ export async function runEnter(host, el, { signal } = {}) {
62
+ const enter = splitClasses(host.getAttribute('enter'));
63
+ const from = splitClasses(host.getAttribute('enter-from'));
64
+ const to = splitClasses(host.getAttribute('enter-to'));
65
+ if (!enter.length && !from.length && !to.length) return;
66
+
67
+ setClasses(el, [...enter, ...from], []);
68
+ forceStyleFlush(el);
69
+ await nextFrame();
70
+ if (signal?.aborted) return;
71
+ setClasses(el, to, from);
72
+ await waitForTransition([enter, from, to], signal);
73
+ setClasses(el, [], [...enter, ...to]);
74
+ }
75
+
76
+ export async function runLeave(host, el, { signal } = {}) {
77
+ const leave = splitClasses(host.getAttribute('leave'));
78
+ const from = splitClasses(host.getAttribute('leave-from'));
79
+ const to = splitClasses(host.getAttribute('leave-to'));
80
+ if (!leave.length && !from.length && !to.length) return;
81
+
82
+ setClasses(el, [...leave, ...from], []);
83
+ forceStyleFlush(el);
84
+ await nextFrame();
85
+ if (signal?.aborted) return;
86
+ setClasses(el, to, from);
87
+ await waitForTransition([leave, from, to], signal);
88
+ setClasses(el, [], [...leave, ...to]);
89
+ }
@@ -0,0 +1,310 @@
1
+ <script setup>
2
+ import {
3
+ onBeforeUnmount,
4
+ onMounted,
5
+ ref,
6
+ useId,
7
+ } from 'vue';
8
+ import {
9
+ buildLiquidGlassMap,
10
+ supportsLiquidGlassRefraction,
11
+ } from './liquidGlass.js';
12
+
13
+ defineOptions({
14
+ name: 'DomLiquidGlassSurface',
15
+ __doc: {
16
+ description: 'An experimental standalone surface that refracts content behind it with a safe translucent fallback.',
17
+ },
18
+ });
19
+
20
+ const MAP_UPDATE_DELAY = 140;
21
+ const FALLBACK_FILTER = 'blur(var(--dom-liquid-glass-fallback-blur, 18px)) saturate(var(--dom-liquid-glass-fallback-saturation, 1.2))';
22
+
23
+ defineProps({
24
+ refraction: {
25
+ type: Number,
26
+ default: 50,
27
+ },
28
+ });
29
+
30
+ const surface = ref(null);
31
+ const mapHref = ref('');
32
+ const mode = ref('fallback');
33
+ const filterValue = ref(FALLBACK_FILTER);
34
+ const filterId = `dom-liquid-glass-${useId().replace(/[^a-zA-Z0-9_-]/g, '')}`;
35
+
36
+ let mapUpdateTimer = null;
37
+ let resizeObserver = null;
38
+
39
+ /**
40
+ * Rasterize a displacement map that matches the current surface geometry.
41
+ *
42
+ * @returns {void}
43
+ */
44
+ function updateGlassMap() {
45
+ if (!surface.value || mode.value !== 'enhanced') return;
46
+
47
+ const bounds = surface.value.getBoundingClientRect();
48
+ const styles = window.getComputedStyle(surface.value);
49
+ const radius = Number.parseFloat(styles.borderTopLeftRadius) || 0;
50
+
51
+ mapHref.value = buildLiquidGlassMap({
52
+ width: bounds.width,
53
+ height: bounds.height,
54
+ radius,
55
+ });
56
+ }
57
+
58
+ /**
59
+ * Debounce displacement-map generation while the surface is resizing.
60
+ *
61
+ * @returns {void}
62
+ */
63
+ function queueGlassMapUpdate() {
64
+ if (mapUpdateTimer !== null) window.clearTimeout(mapUpdateTimer);
65
+ mapUpdateTimer = window.setTimeout(updateGlassMap, MAP_UPDATE_DELAY);
66
+ }
67
+
68
+ /**
69
+ * Enable the enhanced filter when the current rendering engine supports it.
70
+ *
71
+ * @returns {void}
72
+ */
73
+ function activateLiquidGlass() {
74
+ if (!supportsLiquidGlassRefraction()) return;
75
+
76
+ mode.value = 'enhanced';
77
+ filterValue.value = `url("#${filterId}") blur(var(--dom-liquid-glass-blur, 7px)) saturate(var(--dom-liquid-glass-saturation, 1.4))`;
78
+ updateGlassMap();
79
+
80
+ if (typeof ResizeObserver === 'function') {
81
+ resizeObserver = new ResizeObserver(queueGlassMapUpdate);
82
+ resizeObserver.observe(surface.value);
83
+ }
84
+
85
+ window.addEventListener('resize', queueGlassMapUpdate, { passive: true });
86
+ }
87
+
88
+ /**
89
+ * Release observers and pending work owned by this surface instance.
90
+ *
91
+ * @returns {void}
92
+ */
93
+ function disposeLiquidGlass() {
94
+ resizeObserver?.disconnect();
95
+ resizeObserver = null;
96
+ window.removeEventListener('resize', queueGlassMapUpdate);
97
+
98
+ if (mapUpdateTimer !== null) {
99
+ window.clearTimeout(mapUpdateTimer);
100
+ mapUpdateTimer = null;
101
+ }
102
+ }
103
+
104
+ onMounted(activateLiquidGlass);
105
+ onBeforeUnmount(disposeLiquidGlass);
106
+ </script>
107
+
108
+ <template>
109
+ <div
110
+ ref="surface"
111
+ class="surface"
112
+ :data-liquid-glass-mode="mode"
113
+ :style="{ '--dom-liquid-glass-filter': filterValue }"
114
+ >
115
+ <div class="content">
116
+ <slot />
117
+ </div>
118
+
119
+ <svg
120
+ class="defs"
121
+ aria-hidden="true"
122
+ focusable="false"
123
+ width="0"
124
+ height="0"
125
+ >
126
+ <defs>
127
+ <filter
128
+ :id="filterId"
129
+ color-interpolation-filters="sRGB"
130
+ x="-25%"
131
+ y="-25%"
132
+ width="150%"
133
+ height="150%"
134
+ >
135
+ <feImage
136
+ x="0"
137
+ y="0"
138
+ width="100%"
139
+ height="100%"
140
+ preserveAspectRatio="none"
141
+ :href="mapHref || undefined"
142
+ :xlink:href="mapHref || undefined"
143
+ result="map"
144
+ />
145
+ <feDisplacementMap
146
+ in="SourceGraphic"
147
+ in2="map"
148
+ xChannelSelector="R"
149
+ yChannelSelector="B"
150
+ :scale="-refraction"
151
+ result="displaced-red"
152
+ />
153
+ <feColorMatrix
154
+ in="displaced-red"
155
+ type="matrix"
156
+ values="1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0"
157
+ result="red"
158
+ />
159
+ <feDisplacementMap
160
+ in="SourceGraphic"
161
+ in2="map"
162
+ xChannelSelector="R"
163
+ yChannelSelector="B"
164
+ :scale="-Math.max(0, refraction - 3)"
165
+ result="displaced-green"
166
+ />
167
+ <feColorMatrix
168
+ in="displaced-green"
169
+ type="matrix"
170
+ values="0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 1 0"
171
+ result="green"
172
+ />
173
+ <feDisplacementMap
174
+ in="SourceGraphic"
175
+ in2="map"
176
+ xChannelSelector="R"
177
+ yChannelSelector="B"
178
+ :scale="-Math.max(0, refraction - 6)"
179
+ result="displaced-blue"
180
+ />
181
+ <feColorMatrix
182
+ in="displaced-blue"
183
+ type="matrix"
184
+ values="0 0 0 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 1 0"
185
+ result="blue"
186
+ />
187
+ <feBlend in="red" in2="green" mode="screen" result="red-green" />
188
+ <feBlend in="red-green" in2="blue" mode="screen" result="output" />
189
+ <feGaussianBlur in="output" stdDeviation="0.7" />
190
+ </filter>
191
+ </defs>
192
+ </svg>
193
+ </div>
194
+ </template>
195
+
196
+ <style scoped>
197
+ .surface {
198
+ --dom-liquid-glass-default-surface-fill: var(
199
+ --dom-liquid-glass-fallback-background,
200
+ color-mix(in oklch, var(--canvas, white) 68%, transparent)
201
+ );
202
+ --dom-liquid-glass-default-edge-top: rgb(255 255 255 / 0.58);
203
+ --dom-liquid-glass-default-edge-top: light-dark(rgb(255 255 255 / 0.58), rgb(255 255 255 / 0.24));
204
+ --dom-liquid-glass-default-edge-upper: rgb(255 255 255 / 0.28);
205
+ --dom-liquid-glass-default-edge-upper: light-dark(rgb(255 255 255 / 0.28), rgb(255 255 255 / 0.11));
206
+ --dom-liquid-glass-default-edge-side: rgb(255 255 255 / 0.14);
207
+ --dom-liquid-glass-default-edge-side: light-dark(rgb(255 255 255 / 0.14), rgb(255 255 255 / 0.05));
208
+ --dom-liquid-glass-default-edge-bottom: rgb(255 255 255 / 0.09);
209
+ --dom-liquid-glass-default-edge-bottom: light-dark(rgb(255 255 255 / 0.09), rgb(255 255 255 / 0.03));
210
+ --dom-liquid-glass-default-shadow:
211
+ 0 1px 2px rgb(15 23 42 / 0.1),
212
+ 0 8px 24px -8px rgb(15 23 42 / 0.2),
213
+ 0 22px 52px -18px rgb(15 23 42 / 0.24);
214
+ --dom-liquid-glass-default-shadow:
215
+ 0 1px 2px light-dark(rgb(15 23 42 / 0.1), rgb(0 0 0 / 0.42)),
216
+ 0 8px 24px -8px light-dark(rgb(15 23 42 / 0.2), rgb(0 0 0 / 0.58)),
217
+ 0 22px 52px -18px light-dark(rgb(15 23 42 / 0.24), rgb(0 0 0 / 0.72));
218
+ --dom-liquid-glass-default-inner-edge-top: rgb(255 255 255 / 0.42);
219
+ --dom-liquid-glass-default-inner-edge-top: light-dark(rgb(255 255 255 / 0.42), rgb(255 255 255 / 0.18));
220
+ --dom-liquid-glass-default-inner-edge-glow: rgb(255 255 255 / 0.09);
221
+ --dom-liquid-glass-default-inner-edge-glow: light-dark(rgb(255 255 255 / 0.09), rgb(255 255 255 / 0.035));
222
+ --dom-liquid-glass-default-inner-edge-side: rgb(255 255 255 / 0.08);
223
+ --dom-liquid-glass-default-inner-edge-side: light-dark(rgb(255 255 255 / 0.08), rgb(255 255 255 / 0.03));
224
+ --dom-liquid-glass-default-inner-edge-bottom: rgb(15 23 42 / 0.08);
225
+ --dom-liquid-glass-default-inner-edge-bottom: light-dark(rgb(15 23 42 / 0.08), rgb(0 0 0 / 0.34));
226
+
227
+ position: var(--dom-liquid-glass-position, relative);
228
+ isolation: isolate;
229
+ overflow: hidden;
230
+ color: var(--canvas-fg, #171717);
231
+ background:
232
+ linear-gradient(
233
+ var(--dom-liquid-glass-surface-fill, var(--dom-liquid-glass-default-surface-fill)),
234
+ var(--dom-liquid-glass-surface-fill, var(--dom-liquid-glass-default-surface-fill))
235
+ ) padding-box,
236
+ linear-gradient(
237
+ 180deg,
238
+ var(--dom-liquid-glass-edge-top, var(--dom-liquid-glass-default-edge-top)) 0%,
239
+ var(--dom-liquid-glass-edge-upper, var(--dom-liquid-glass-default-edge-upper)) 20%,
240
+ var(--dom-liquid-glass-edge-side, var(--dom-liquid-glass-default-edge-side)) 58%,
241
+ var(--dom-liquid-glass-edge-bottom, var(--dom-liquid-glass-default-edge-bottom)) 100%
242
+ ) border-box;
243
+ border: 1px solid transparent;
244
+ border-radius: var(--dom-liquid-glass-radius, var(--radius-lg, 1.25rem));
245
+ box-shadow: var(
246
+ --dom-liquid-glass-shadow,
247
+ var(--dom-liquid-glass-default-shadow)
248
+ );
249
+ -webkit-backdrop-filter: var(--dom-liquid-glass-filter);
250
+ backdrop-filter: var(--dom-liquid-glass-filter);
251
+ }
252
+
253
+ .surface[data-liquid-glass-mode='enhanced'] {
254
+ --dom-liquid-glass-default-surface-fill: var(
255
+ --dom-liquid-glass-background,
256
+ color-mix(in oklch, var(--canvas, white) 40%, transparent)
257
+ );
258
+ }
259
+
260
+ .surface::before,
261
+ .surface::after {
262
+ position: absolute;
263
+ inset: 0;
264
+ pointer-events: none;
265
+ content: '';
266
+ border-radius: inherit;
267
+ }
268
+
269
+ .surface::before {
270
+ z-index: 1;
271
+ box-shadow:
272
+ inset 0 1px 0 var(--dom-liquid-glass-inner-edge-top, var(--dom-liquid-glass-default-inner-edge-top)),
273
+ inset 0 3px 8px var(--dom-liquid-glass-inner-edge-glow, var(--dom-liquid-glass-default-inner-edge-glow)),
274
+ inset 1px 0 0 var(--dom-liquid-glass-inner-edge-side, var(--dom-liquid-glass-default-inner-edge-side)),
275
+ inset -1px 0 0 var(--dom-liquid-glass-inner-edge-side, var(--dom-liquid-glass-default-inner-edge-side)),
276
+ inset 0 -1px 0 var(--dom-liquid-glass-inner-edge-bottom, var(--dom-liquid-glass-default-inner-edge-bottom));
277
+ }
278
+
279
+ .surface::after {
280
+ z-index: 0;
281
+ background: linear-gradient(
282
+ 155deg,
283
+ rgb(255 255 255 / 0.14),
284
+ transparent 34%,
285
+ rgb(255 255 255 / 0.04) 72%,
286
+ rgb(255 255 255 / 0.08)
287
+ );
288
+ }
289
+
290
+ .content {
291
+ position: relative;
292
+ z-index: 2;
293
+ }
294
+
295
+ .defs {
296
+ position: absolute;
297
+ width: 0;
298
+ height: 0;
299
+ overflow: hidden;
300
+ }
301
+
302
+ @media (prefers-reduced-transparency: reduce) {
303
+ .surface {
304
+ background: var(--canvas, white);
305
+ border-color: var(--border, rgb(0 0 0 / 0.14));
306
+ -webkit-backdrop-filter: none;
307
+ backdrop-filter: none;
308
+ }
309
+ }
310
+ </style>
@@ -0,0 +1,112 @@
1
+ const DEFAULT_BORDER_RATIO = 0.07;
2
+ const DEFAULT_LIGHTNESS = 50;
3
+ const DEFAULT_ALPHA = 0.93;
4
+ const DEFAULT_EDGE_BLUR = 11;
5
+
6
+ /**
7
+ * Clamp a numeric value to a finite range.
8
+ *
9
+ * @param {number} value Value to clamp.
10
+ * @param {number} min Minimum accepted value.
11
+ * @param {number} max Maximum accepted value.
12
+ * @returns {number} Clamped finite value.
13
+ */
14
+ function clamp(value, min, max) {
15
+ return Math.min(max, Math.max(min, Number.isFinite(value) ? value : min));
16
+ }
17
+
18
+ /**
19
+ * Normalize an observed CSS dimension for safe SVG interpolation.
20
+ *
21
+ * @param {number} value Observed element dimension in CSS pixels.
22
+ * @returns {number} Positive whole-pixel dimension.
23
+ */
24
+ function normalizeDimension(value) {
25
+ return Math.max(1, Math.round(Number(value) || 0));
26
+ }
27
+
28
+ /**
29
+ * Determine whether the current engine can render SVG displacement filters as
30
+ * backdrop filters. WebKit accepts the syntax but currently does not render
31
+ * the SVG filter, so it must remain on the CSS-only fallback.
32
+ *
33
+ * @returns {boolean} True when enhanced backdrop refraction should be enabled.
34
+ */
35
+ export function supportsLiquidGlassRefraction() {
36
+ if (typeof window === 'undefined' || typeof CSS === 'undefined') return false;
37
+ if (window.matchMedia?.('(prefers-reduced-transparency: reduce)').matches) return false;
38
+
39
+ const filter = 'url("#dom-liquid-glass-probe") blur(1px)';
40
+ const supportsSyntax = CSS.supports('backdrop-filter', filter)
41
+ || CSS.supports('-webkit-backdrop-filter', filter);
42
+ if (!supportsSyntax) return false;
43
+
44
+ const userAgent = window.navigator?.userAgent || '';
45
+ const usesWebKit = /AppleWebKit/i.test(userAgent)
46
+ && !/(Chrome|Chromium|Edg|OPR|SamsungBrowser)/i.test(userAgent);
47
+
48
+ return !usesWebKit;
49
+ }
50
+
51
+ /**
52
+ * Build a pill or rounded-rectangle displacement map for one glass surface.
53
+ * Red encodes horizontal displacement, blue encodes vertical displacement,
54
+ * and the blurred neutral interior keeps the centre comparatively stable.
55
+ *
56
+ * @param {object} options Map generation options.
57
+ * @param {number} options.width Surface width in CSS pixels.
58
+ * @param {number} options.height Surface height in CSS pixels.
59
+ * @param {number} [options.radius] Uniform surface radius in CSS pixels.
60
+ * @param {number} [options.borderRatio] Proportion of the shortest edge used for the refractive rim.
61
+ * @param {number} [options.lightness] Neutral interior lightness percentage.
62
+ * @param {number} [options.alpha] Neutral interior opacity.
63
+ * @param {number} [options.edgeBlur] Blur applied to the neutral interior edge.
64
+ * @returns {string} Encoded SVG data URI suitable for an SVG feImage href.
65
+ */
66
+ export function buildLiquidGlassMap({
67
+ width,
68
+ height,
69
+ radius,
70
+ borderRatio = DEFAULT_BORDER_RATIO,
71
+ lightness = DEFAULT_LIGHTNESS,
72
+ alpha = DEFAULT_ALPHA,
73
+ edgeBlur = DEFAULT_EDGE_BLUR,
74
+ }) {
75
+ const resolvedWidth = normalizeDimension(width);
76
+ const resolvedHeight = normalizeDimension(height);
77
+ const shortestEdge = Math.min(resolvedWidth, resolvedHeight);
78
+ const requestedRadius = Number(radius);
79
+ const resolvedRadius = clamp(
80
+ Number.isFinite(requestedRadius) ? requestedRadius : shortestEdge / 2,
81
+ 0,
82
+ shortestEdge / 2,
83
+ );
84
+ const resolvedBorderRatio = clamp(Number(borderRatio), 0, 0.5);
85
+ const resolvedLightness = clamp(Number(lightness), 0, 100);
86
+ const resolvedAlpha = clamp(Number(alpha), 0, 1);
87
+ const resolvedEdgeBlur = clamp(Number(edgeBlur), 0, shortestEdge / 2);
88
+ const inset = shortestEdge * resolvedBorderRatio;
89
+ const innerWidth = Math.max(1, resolvedWidth - (inset * 2));
90
+ const innerHeight = Math.max(1, resolvedHeight - (inset * 2));
91
+ const innerRadius = Math.max(0, resolvedRadius - inset);
92
+ const svg = [
93
+ `<svg viewBox="0 0 ${resolvedWidth} ${resolvedHeight}" xmlns="http://www.w3.org/2000/svg">`,
94
+ '<defs>',
95
+ '<linearGradient id="red" x1="100%" y1="0%" x2="0%" y2="0%">',
96
+ '<stop offset="0%" stop-color="#000"/>',
97
+ '<stop offset="100%" stop-color="red"/>',
98
+ '</linearGradient>',
99
+ '<linearGradient id="blue" x1="0%" y1="0%" x2="0%" y2="100%">',
100
+ '<stop offset="0%" stop-color="#000"/>',
101
+ '<stop offset="100%" stop-color="blue"/>',
102
+ '</linearGradient>',
103
+ '</defs>',
104
+ `<rect width="${resolvedWidth}" height="${resolvedHeight}" fill="black"/>`,
105
+ `<rect width="${resolvedWidth}" height="${resolvedHeight}" rx="${resolvedRadius}" fill="url(#red)"/>`,
106
+ `<rect width="${resolvedWidth}" height="${resolvedHeight}" rx="${resolvedRadius}" fill="url(#blue)" style="mix-blend-mode:difference"/>`,
107
+ `<rect x="${inset}" y="${inset}" width="${innerWidth}" height="${innerHeight}" rx="${innerRadius}" fill="hsl(0 0% ${resolvedLightness}% / ${resolvedAlpha})" style="filter:blur(${resolvedEdgeBlur}px)"/>`,
108
+ '</svg>',
109
+ ].join('');
110
+
111
+ return `data:image/svg+xml,${encodeURIComponent(svg)}`;
112
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Private feature switches for the promoted liquid-glass treatments.
3
+ *
4
+ * Set either value to false to restore the preceding surface treatment
5
+ * without changing component call sites or public package APIs.
6
+ */
7
+ export const LIQUID_GLASS_EFFECTS = Object.freeze({
8
+ commandPalette: false,
9
+ stickyHeader: true,
10
+ });
@@ -0,0 +1,132 @@
1
+ // Optional syntax highlighting via Shiki, loaded from an ESM CDN at runtime.
2
+ //
3
+ // Shiki is polish, not a hard runtime dependency. If the CDN is blocked,
4
+ // unavailable, or the app is rendering on the server, callers get null and
5
+ // can fall back to plain code rendering.
6
+
7
+ const SHIKI_URL = 'https://esm.sh/shiki@1';
8
+ const LIGHT = 'light-plus';
9
+ const DARK = 'dark-plus';
10
+ const LANGS = ['vue', 'html', 'javascript', 'typescript', 'css', 'json', 'markdown', 'python', 'bash', 'sql', 'text'];
11
+ const LANG_ALIASES = {
12
+ js: 'javascript',
13
+ ts: 'typescript',
14
+ md: 'markdown',
15
+ sh: 'bash',
16
+ txt: 'text',
17
+ };
18
+
19
+ export const CODE_LANGUAGE_OPTIONS = [
20
+ { label: 'Plain text', value: 'txt' },
21
+ { label: 'Vue', value: 'vue' },
22
+ { label: 'HTML', value: 'html' },
23
+ { label: 'JavaScript', value: 'js' },
24
+ { label: 'TypeScript', value: 'ts' },
25
+ { label: 'CSS', value: 'css' },
26
+ { label: 'JSON', value: 'json' },
27
+ { label: 'Markdown', value: 'md' },
28
+ { label: 'Python', value: 'python' },
29
+ { label: 'Bash', value: 'bash' },
30
+ { label: 'SQL', value: 'sql' },
31
+ ];
32
+
33
+ let highlighterPromise = null;
34
+ let loadFailed = false;
35
+ const languagePromises = new Map();
36
+
37
+ /**
38
+ * Loads the shared Shiki highlighter when the browser can reach the CDN.
39
+ *
40
+ * @returns {Promise<object|null>} Shiki highlighter instance, or null when unavailable.
41
+ */
42
+ async function getHighlighter() {
43
+ if (loadFailed) return null;
44
+ if (highlighterPromise) return highlighterPromise;
45
+
46
+ highlighterPromise = (async () => {
47
+ if (typeof window === 'undefined') return null;
48
+ try {
49
+ const shiki = await import(/* @vite-ignore */ SHIKI_URL);
50
+ const factory = shiki.createHighlighter || shiki.getHighlighter;
51
+ if (!factory) throw new Error('shiki: no factory export');
52
+ return await factory({ themes: [LIGHT, DARK], langs: [] });
53
+ } catch (err) {
54
+ loadFailed = true;
55
+ if (typeof console !== 'undefined') {
56
+ // eslint-disable-next-line no-console
57
+ console.info('[dom-studio] Shiki CDN unavailable; using plain code blocks.', err?.message);
58
+ }
59
+ return null;
60
+ }
61
+ })();
62
+
63
+ return highlighterPromise;
64
+ }
65
+
66
+ /**
67
+ * Loads one bundled language grammar and shares concurrent requests for it.
68
+ *
69
+ * @param {object} highlighter Shiki highlighter instance.
70
+ * @param {string} lang Normalized bundled language key.
71
+ * @returns {Promise<void>} Resolves when the language grammar is available.
72
+ */
73
+ async function ensureLanguage(highlighter, lang) {
74
+ if (!languagePromises.has(lang)) {
75
+ languagePromises.set(lang, highlighter.loadLanguage(lang));
76
+ }
77
+
78
+ try {
79
+ await languagePromises.get(lang);
80
+ } catch (err) {
81
+ languagePromises.delete(lang);
82
+ throw err;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Normalizes public language names and aliases to a bundled Shiki grammar.
88
+ *
89
+ * @param {string} lang Public language key.
90
+ * @param {string} fallback Bundled grammar used for unknown values.
91
+ * @returns {string} Normalized bundled language key.
92
+ */
93
+ export function normalizeCodeLanguage(lang, fallback = 'html') {
94
+ const normalizedLang = LANG_ALIASES[lang] || lang;
95
+ return LANGS.includes(normalizedLang) ? normalizedLang : fallback;
96
+ }
97
+
98
+ /**
99
+ * Resolves a language name to the preferred value used by DOM Studio controls.
100
+ *
101
+ * @param {string} lang Public or normalized language key.
102
+ * @returns {string} Supported DOM Studio language option value.
103
+ */
104
+ export function codeLanguageOptionValue(lang) {
105
+ const normalizedLang = normalizeCodeLanguage(lang, 'text');
106
+ const option = CODE_LANGUAGE_OPTIONS.find((candidate) => normalizeCodeLanguage(candidate.value) === normalizedLang);
107
+ return option?.value || 'txt';
108
+ }
109
+
110
+ /**
111
+ * Highlights source code with Shiki when available.
112
+ *
113
+ * @param {string} code Source code to highlight.
114
+ * @param {string} lang Source language key.
115
+ * @param {'inherit' | 'light' | 'dark'} themeMode Syntax theme mode.
116
+ * @returns {Promise<string|null>} Highlighted HTML, or null for caller fallback.
117
+ */
118
+ export async function highlight(code, lang = 'vue', themeMode = 'inherit') {
119
+ if (!code) return null;
120
+ const h = await getHighlighter();
121
+ if (!h) return null;
122
+ try {
123
+ const resolvedLang = normalizeCodeLanguage(lang);
124
+ await ensureLanguage(h, resolvedLang);
125
+ const themeOptions = themeMode === 'light'
126
+ ? { theme: LIGHT }
127
+ : { themes: { light: LIGHT, dark: DARK } };
128
+ return h.codeToHtml(code, { lang: resolvedLang, ...themeOptions });
129
+ } catch {
130
+ return null;
131
+ }
132
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Resolve the safest target for Vue Teleport-based floating UI.
3
+ *
4
+ * Floating controls normally portal to the document body so they can escape
5
+ * local overflow. Modal dialogs and drawers make the rest of the document
6
+ * inert, so a body-level portal can be mistaken for a backdrop click. Targeting
7
+ * the nearest modal interaction tree keeps the floating panel inside the focus
8
+ * trap while preserving document-level fallback behavior.
9
+ *
10
+ * @param {Element|null|undefined} source Element mounted inside the floating control.
11
+ * @returns {Element|string} Teleport target element, or `'body'` before the DOM is available.
12
+ */
13
+ export function resolveFloatingTeleportTarget(source) {
14
+ const nearestDialog = typeof source?.closest === 'function'
15
+ ? source.closest('dom-dialog')
16
+ : null;
17
+ const nearestDrawer = typeof source?.closest === 'function'
18
+ ? source.closest('dom-drawer')
19
+ : null;
20
+ const drawerPanel = nearestDrawer?.querySelector?.('[data-panel]') || null;
21
+ const ownerDocument = source?.ownerDocument || (typeof document !== 'undefined' ? document : null);
22
+ return nearestDialog || drawerPanel || nearestDrawer || ownerDocument?.body || 'body';
23
+ }