@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,384 @@
1
+ import { DomElementBase, defineDomElement, isBrowser } from './base.js';
2
+
3
+ const CLOSE_COMPLETE_BUFFER_MS = 40;
4
+
5
+ // <dom-dialog id="my-dialog">
6
+ // <!-- trigger slot optional — omit when opening via commandfor or .open() -->
7
+ // <h2>Title</h2>
8
+ // <p>Body content.</p>
9
+ // <button data-close>Close</button>
10
+ // </dom-dialog>
11
+ //
12
+ // <button command="open" commandfor="my-dialog">Open</button>
13
+ //
14
+ // const dlg = document.getElementById('my-dialog');
15
+ // dlg.open(); dlg.close(); dlg.toggle();
16
+
17
+ let TEMPLATE = null;
18
+
19
+ /**
20
+ * Resolve the shared shadow DOM template for dialog elements.
21
+ *
22
+ * @returns {HTMLTemplateElement|null} Cached dialog template, or null outside the browser.
23
+ */
24
+ function getTemplate() {
25
+ if (TEMPLATE || !isBrowser) return TEMPLATE;
26
+ TEMPLATE = document.createElement('template');
27
+ TEMPLATE.innerHTML = `
28
+ <style>
29
+ :host { display: contents; }
30
+ dialog {
31
+ box-sizing: border-box;
32
+ border: 0;
33
+ margin: auto;
34
+ background: transparent;
35
+ color: inherit;
36
+ outline: none;
37
+ max-width: 100vw;
38
+ max-height: 100vh;
39
+ max-height: 100dvh;
40
+ overscroll-behavior: contain;
41
+ overflow: auto;
42
+ padding: min(1rem, 4vw);
43
+ }
44
+ dialog:focus,
45
+ dialog:focus-visible {
46
+ outline: none;
47
+ }
48
+ dialog::backdrop {
49
+ background: rgba(0, 0, 0, 0.5);
50
+ backdrop-filter: blur(4px);
51
+ -webkit-backdrop-filter: blur(4px);
52
+ opacity: 0;
53
+ transition:
54
+ opacity 140ms ease,
55
+ overlay 140ms ease allow-discrete,
56
+ display 140ms ease allow-discrete;
57
+ }
58
+ dialog[open]::backdrop { opacity: 1; }
59
+ :host([no-backdrop]) dialog::backdrop {
60
+ background: transparent;
61
+ backdrop-filter: none;
62
+ -webkit-backdrop-filter: none;
63
+ }
64
+ @starting-style {
65
+ dialog[open]::backdrop { opacity: 0; }
66
+ }
67
+ </style>
68
+ <slot name="trigger"></slot>
69
+ <dialog part="dialog"><slot></slot></dialog>
70
+ `;
71
+ return TEMPLATE;
72
+ }
73
+
74
+ const OPEN_COMMANDS = new Set(['open', 'show-modal']);
75
+
76
+ /**
77
+ * Parse a CSS time value into milliseconds.
78
+ *
79
+ * @param {string} value CSS time token such as `120ms` or `0.2s`.
80
+ * @returns {number} Milliseconds represented by the token.
81
+ */
82
+ function parseCssTime(value) {
83
+ const time = String(value || '').trim();
84
+ if (!time) return 0;
85
+ const numeric = Number.parseFloat(time);
86
+ if (!Number.isFinite(numeric)) return 0;
87
+ if (time.endsWith('ms')) return numeric;
88
+ if (time.endsWith('s')) return numeric * 1000;
89
+ return numeric;
90
+ }
91
+
92
+ /**
93
+ * Parse a comma-separated CSS time list into millisecond values.
94
+ *
95
+ * @param {string} value CSS transition duration or delay list.
96
+ * @returns {Array<number>} Parsed millisecond values.
97
+ */
98
+ function parseCssTimeList(value) {
99
+ return String(value || '')
100
+ .split(',')
101
+ .map((entry) => parseCssTime(entry));
102
+ }
103
+
104
+ /**
105
+ * Read an indexed CSS time, repeating the final value as CSS transitions do.
106
+ *
107
+ * @param {Array<number>} values Parsed CSS time values.
108
+ * @param {number} index Desired transition index.
109
+ * @returns {number} Millisecond value for the index.
110
+ */
111
+ function cssTimeAt(values, index) {
112
+ if (!values.length) return 0;
113
+ return values[index] ?? values[values.length - 1] ?? 0;
114
+ }
115
+
116
+ /**
117
+ * Calculate the longest transition duration plus delay for an element.
118
+ *
119
+ * @param {Element|null} element Element whose computed transition timing should be measured.
120
+ * @returns {number} Longest transition time in milliseconds.
121
+ */
122
+ function transitionTotalMs(element) {
123
+ if (!element || !isBrowser) return 0;
124
+ const style = window.getComputedStyle(element);
125
+ const durations = parseCssTimeList(style.transitionDuration);
126
+ const delays = parseCssTimeList(style.transitionDelay);
127
+ const count = Math.max(durations.length, delays.length);
128
+ let longest = 0;
129
+
130
+ for (let index = 0; index < count; index += 1) {
131
+ longest = Math.max(longest, cssTimeAt(durations, index) + cssTimeAt(delays, index));
132
+ }
133
+
134
+ return longest;
135
+ }
136
+
137
+ export class DomDialogElement extends DomElementBase {
138
+ static get observedAttributes() { return ['open', 'aria-labelledby', 'aria-describedby']; }
139
+
140
+ static __doc = {
141
+ name: 'dom-dialog',
142
+ description: 'Self-contained wrapper around the native HTML <dialog>. Top-layer rendering, nested stacking, ::backdrop and Esc-to-close are all native. Trigger slot is optional — open via commandfor, the open attribute, or .open() / .close() / .toggle().',
143
+ slots: [
144
+ { name: 'trigger', description: 'Optional. Element that opens the dialog when clicked. Omit when using commandfor or programmatic control.' },
145
+ { name: '(default)', description: 'Dialog body. Anything that is not the trigger goes here.' },
146
+ ],
147
+ attributes: [
148
+ { name: 'id', type: 'string', description: 'Host id — used by commandfor on external buttons (e.g. commandfor="my-dialog").' },
149
+ { name: 'open', type: 'boolean', description: 'Reflects open state. Setting it shows the dialog (showModal), removing it closes it.' },
150
+ { name: 'static', type: 'boolean', description: 'Modal cannot be dismissed by backdrop click or Esc — only data-close, .close(), or removing open.' },
151
+ { name: 'no-backdrop', type: 'boolean', description: 'Hides the visual backdrop while keeping native modal top-layer behaviour.' },
152
+ ],
153
+ events: [
154
+ { name: 'dom:before-open', description: 'Fired immediately before the dialog opens so callers can prepare render state.' },
155
+ { name: 'dom:open', description: 'Fired when the dialog opens.' },
156
+ { name: 'dom:close', description: 'Fired when the dialog closes (Esc, .close(), backdrop, or data-close).' },
157
+ { name: 'dom:close-complete', description: 'Fired after the close transition completes so callers can release retained data.' },
158
+ ],
159
+ keyboard: [
160
+ { keys: 'Esc', action: 'Closes the topmost open dialog unless `static` is set.' },
161
+ { keys: 'Tab / Shift+Tab', action: 'Focus stays within the dialog while open (native top-layer).' },
162
+ { keys: 'Click backdrop', action: 'Dismisses the dialog (disable with `static`).' },
163
+ ],
164
+ example: `<dom-dialog id="confirm-dialog">
165
+ <h2>Delete project?</h2>
166
+ <p>This cannot be undone.</p>
167
+ <button data-close>Cancel</button>
168
+ </dom-dialog>
169
+
170
+ <button type="button" command="open" commandfor="confirm-dialog">Delete</button>
171
+
172
+ <script type="module">
173
+ import '@getdom/studio/headless/dialog.js';
174
+ const dlg = document.getElementById('confirm-dialog');
175
+ dlg.addEventListener('dom:close', () => console.log('closed'));
176
+ <\/script>`,
177
+ };
178
+
179
+ constructor() {
180
+ super();
181
+ if (!isBrowser) return;
182
+ if (!this.shadowRoot) {
183
+ const shadow = this.attachShadow({ mode: 'open' });
184
+ shadow.appendChild(getTemplate().content.cloneNode(true));
185
+ }
186
+ this._dialog = this.shadowRoot.querySelector('dialog');
187
+ this._syncing = false;
188
+ this._closeCompleteRun = 0;
189
+ this._closeCompleteTimer = null;
190
+ }
191
+
192
+ connectedCallback() {
193
+ this._trigger = this.querySelector('[slot="trigger"]');
194
+ if (this._trigger) {
195
+ this._trigger.setAttribute('aria-haspopup', 'dialog');
196
+ this._trigger.setAttribute('aria-expanded', String(this.isOpen));
197
+ this.on(this._trigger, 'click', () => this._setOpen(true));
198
+ }
199
+
200
+ this.addEventListener('command', (e) => {
201
+ if (e.target !== this) return;
202
+ if (OPEN_COMMANDS.has(e.command)) this._setOpen(true);
203
+ else if (e.command === 'close') this._setOpen(false);
204
+ });
205
+
206
+ this._wireCommandForButtons();
207
+
208
+ this.on(this, 'click', (ev) => {
209
+ const closer = ev.target.closest?.('[data-close]');
210
+ if (closer && this.contains(closer)) this._setOpen(false);
211
+ });
212
+
213
+ this.on(this._dialog, 'click', (e) => {
214
+ if (this.hasAttribute('static')) return;
215
+ if (e.target === this._dialog) this._setOpen(false);
216
+ });
217
+
218
+ this.on(this._dialog, 'cancel', (e) => {
219
+ if (this.hasAttribute('static')) e.preventDefault();
220
+ });
221
+ this.on(this._dialog, 'keydown', (e) => {
222
+ if (this.hasAttribute('static') && e.key === 'Escape') e.preventDefault();
223
+ });
224
+
225
+ this.on(this._dialog, 'close', () => this._onNativeClose());
226
+ this._syncAria();
227
+
228
+ if (this.boolAttr('open')) this._setOpen(true);
229
+ }
230
+
231
+ /**
232
+ * Release listeners and close-complete timers when the element disconnects.
233
+ *
234
+ * @returns {void}
235
+ */
236
+ disconnectedCallback() {
237
+ super.disconnectedCallback();
238
+ this._cancelCloseCompleteWait();
239
+ }
240
+
241
+ /**
242
+ * Synchronize ARIA references from the host to the internal native dialog.
243
+ *
244
+ * @returns {void}
245
+ */
246
+ _syncAria() {
247
+ for (const name of ['aria-labelledby', 'aria-describedby']) {
248
+ const value = this.getAttribute(name);
249
+ if (value) this._dialog.setAttribute(name, value);
250
+ else this._dialog.removeAttribute(name);
251
+ }
252
+ }
253
+
254
+ /** Fallback when Invoker Commands are unavailable — wire [commandfor] buttons by id. */
255
+ _wireCommandForButtons() {
256
+ const id = this.id;
257
+ if (!id) return;
258
+ for (const btn of document.querySelectorAll(`[commandfor="${id}"]`)) {
259
+ this.on(btn, 'click', () => {
260
+ const cmd = btn.getAttribute('command');
261
+ if (OPEN_COMMANDS.has(cmd)) this._setOpen(true);
262
+ else if (cmd === 'close') this._setOpen(false);
263
+ });
264
+ }
265
+ }
266
+
267
+ /**
268
+ * Handle the native close event and start close-complete tracking.
269
+ *
270
+ * @returns {void}
271
+ */
272
+ _onNativeClose() {
273
+ this._syncAttr(false);
274
+ this._trigger?.setAttribute('aria-expanded', 'false');
275
+ this.emit('dom:close');
276
+ this._waitForCloseComplete();
277
+ }
278
+
279
+ /**
280
+ * Reflect open state changes without recursively re-entering attribute watchers.
281
+ *
282
+ * @param {boolean} open Whether the host should carry the open attribute.
283
+ * @returns {void}
284
+ */
285
+ _syncAttr(open) {
286
+ if (this._syncing) return;
287
+ this._syncing = true;
288
+ try {
289
+ if (open) this.setAttribute('open', '');
290
+ else this.removeAttribute('open');
291
+ } finally {
292
+ this._syncing = false;
293
+ }
294
+ }
295
+
296
+ /**
297
+ * Open or close the internal native dialog.
298
+ *
299
+ * @param {boolean} next Desired open state.
300
+ * @returns {void}
301
+ */
302
+ _setOpen(next) {
303
+ if (!this._dialog || this._syncing) return;
304
+ const isOpen = this._dialog.open;
305
+ if (next === isOpen) {
306
+ if (this.hasAttribute('open') !== next) this._syncAttr(next);
307
+ return;
308
+ }
309
+ if (next) {
310
+ this._cancelCloseCompleteWait();
311
+ this.emit('dom:before-open');
312
+ this._dialog.showModal();
313
+ if (!this.querySelector('[autofocus]')) {
314
+ try {
315
+ this._dialog.focus({ preventScroll: true });
316
+ } catch {
317
+ this._dialog.focus();
318
+ }
319
+ }
320
+ this._dialog.scrollTop = 0;
321
+ this._syncAttr(true);
322
+ this._trigger?.setAttribute('aria-expanded', 'true');
323
+ this.emit('dom:open');
324
+ } else {
325
+ this._dialog.close();
326
+ }
327
+ }
328
+
329
+ /**
330
+ * React to controlled attribute changes.
331
+ *
332
+ * @param {string} name Changed attribute name.
333
+ * @param {string|null} _oldValue Previous attribute value.
334
+ * @param {string|null} newValue New attribute value.
335
+ * @returns {void}
336
+ */
337
+ attributeChangedCallback(name, _oldValue, newValue) {
338
+ if (!this.isConnected || !this._dialog || this._syncing) return;
339
+ if (name === 'open') this._setOpen(newValue !== null);
340
+ else this._syncAria();
341
+ }
342
+
343
+ /**
344
+ * Schedule a close-complete event after the active leave transition timing.
345
+ *
346
+ * @returns {void}
347
+ */
348
+ _waitForCloseComplete() {
349
+ this._cancelCloseCompleteWait();
350
+ const run = ++this._closeCompleteRun;
351
+ const delay = transitionTotalMs(this._dialog) + CLOSE_COMPLETE_BUFFER_MS;
352
+ this._closeCompleteTimer = window.setTimeout(() => {
353
+ this._closeCompleteTimer = null;
354
+ if (run !== this._closeCompleteRun || this.isOpen) return;
355
+ this.emit('dom:close-complete');
356
+ }, delay);
357
+ }
358
+
359
+ /**
360
+ * Cancel a pending close-complete event when the dialog reopens or unmounts.
361
+ *
362
+ * @returns {void}
363
+ */
364
+ _cancelCloseCompleteWait() {
365
+ this._closeCompleteRun += 1;
366
+ if (!this._closeCompleteTimer) return;
367
+ window.clearTimeout(this._closeCompleteTimer);
368
+ this._closeCompleteTimer = null;
369
+ }
370
+
371
+ /** Whether the modal is open (reads the internal &lt;dialog&gt;). */
372
+ get isOpen() { return !!this._dialog?.open; }
373
+ set isOpen(v) { this._setOpen(!!v); }
374
+
375
+ open() { this._setOpen(true); }
376
+ close() { this._setOpen(false); }
377
+ toggle() { this._setOpen(!this.isOpen); }
378
+
379
+ // Aliases
380
+ show() { this.open(); }
381
+ hide() { this.close(); }
382
+ }
383
+
384
+ defineDomElement('dom-dialog', DomDialogElement);
@@ -0,0 +1,21 @@
1
+ /* eslint-disable */
2
+ // Generated by scripts/generate-types.mjs. Do not edit by hand.
3
+ import { DomElementBase } from './base.js';
4
+ export declare class DomDrawerElement extends DomElementBase {
5
+ static readonly observedAttributes?: Array<string>;
6
+ static readonly __doc?: HeadlessElementDoc;
7
+ open: boolean;
8
+ show(): void;
9
+ hide(): void;
10
+ toggle(): void;
11
+ }
12
+ export interface HeadlessElementDoc {
13
+ name?: string;
14
+ description?: string;
15
+ slots?: Array<Record<string, unknown>>;
16
+ attributes?: Array<Record<string, unknown>>;
17
+ events?: Array<Record<string, unknown>>;
18
+ keyboard?: Array<Record<string, unknown>>;
19
+ example?: string;
20
+ [key: string]: unknown;
21
+ }