@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.
- package/LICENSE.md +109 -0
- package/README.md +44 -0
- package/SKILL.md +83 -0
- package/package.json +73 -0
- package/public/llms.txt +57 -0
- package/public/skills.sh +11 -0
- package/src/pages/_layout/componentCatalog.generated.js +4514 -0
- package/src/pages/_layout/componentInspector.js +60 -0
- package/src/pages/_layout/componentManager.js +192 -0
- package/src/pages/_layout/componentRuntimeRecords.js +49 -0
- package/src/pages/_layout/inspector/componentRegistry.js +639 -0
- package/src/pages/_layout/inspector/serialize.js +134 -0
- package/src/pages/_layout/inspector/useInspector.js +293 -0
- package/src/pages/components/accordion/DomAccordion.vue +39 -0
- package/src/pages/components/breadcrumbs/DomBreadcrumbs.vue +347 -0
- package/src/pages/components/button/DomButton.vue +132 -0
- package/src/pages/components/command-palette/DomCommandPalette.vue +482 -0
- package/src/pages/components/context-menu/DomContextMenu.vue +178 -0
- package/src/pages/components/dialog/DomDialog.vue +271 -0
- package/src/pages/components/dialog/DomDialogSchemaForm.vue +72 -0
- package/src/pages/components/dialog/DomDialogStack.vue +289 -0
- package/src/pages/components/dialog/useDialogs.js +253 -0
- package/src/pages/components/drawer/DomDrawer.vue +159 -0
- package/src/pages/components/dropdown/DomDropdown.vue +261 -0
- package/src/pages/components/icon-button/DomIconButton.vue +155 -0
- package/src/pages/components/lightbox/DomLightbox.vue +1561 -0
- package/src/pages/components/menu/DomMenu.vue +359 -0
- package/src/pages/components/menu/DomMenuItem.vue +233 -0
- package/src/pages/components/menu/DomMenuLabel.vue +19 -0
- package/src/pages/components/menu/DomMenuSeparator.vue +14 -0
- package/src/pages/components/menu/menuContext.js +11 -0
- package/src/pages/components/menu/menuPlacement.js +26 -0
- package/src/pages/components/menu/submenu.js +58 -0
- package/src/pages/components/pagination/DomPagination.vue +265 -0
- package/src/pages/components/playground/DomRenderer.vue +48 -0
- package/src/pages/components/popover/DomPopover.vue +244 -0
- package/src/pages/components/profile-menu/DomProfileMenu.vue +408 -0
- package/src/pages/components/profile-menu/DomProfileMenuItemContent.vue +45 -0
- package/src/pages/components/scroll-menu/DomScrollMenu.vue +430 -0
- package/src/pages/components/scroll-menu/DomScrollMenuItem.vue +212 -0
- package/src/pages/components/scroll-menu/scrollMenuContext.js +22 -0
- package/src/pages/components/splitter-panel/DomSplitterPanel.vue +283 -0
- package/src/pages/components/stepper/DomStepper.vue +386 -0
- package/src/pages/components/tabs/DomTabs.vue +149 -0
- package/src/pages/components/theme-switcher/DomThemeSwitcher.vue +226 -0
- package/src/pages/components/toast/DomToastItem.vue +52 -0
- package/src/pages/components/toast/DomToastStack.vue +768 -0
- package/src/pages/components/toast/useToasts.js +237 -0
- package/src/pages/components/toolbar/DomToolbar.vue +158 -0
- package/src/pages/components/tooltip/DomTooltip.vue +69 -0
- package/src/pages/components/tooltip/vDomTooltip.js +106 -0
- package/src/pages/components/tree-view/DomTreeView.vue +961 -0
- package/src/pages/components/tree-view/treeSchemas.js +129 -0
- package/src/pages/components/tree-view/treeUtils.js +228 -0
- package/src/pages/data/_shared/calculateVirtualWindow.js +101 -0
- package/src/pages/data/_shared/resourceRecord.js +29 -0
- package/src/pages/data/activity-timeline/DomActivityTimeline.vue +417 -0
- package/src/pages/data/agent-chat/DomAgentChatShell.vue +565 -0
- package/src/pages/data/agent-chat/DomAgentConversation.vue +737 -0
- package/src/pages/data/agent-chat/DomAgentConversationList.vue +319 -0
- package/src/pages/data/agent-chat/examples/agentChatMockApi.js +620 -0
- package/src/pages/data/agent-chat/examples/useAgentChatDemoStore.js +730 -0
- package/src/pages/data/data-grid/DataGridVirtualRows.vue +220 -0
- package/src/pages/data/data-grid/DomDataGrid.vue +1643 -0
- package/src/pages/data/document-editor/DomDocumentEditor.vue +3030 -0
- package/src/pages/data/document-editor/documentEditorCodeBlock.js +282 -0
- package/src/pages/data/document-editor/examples/documentEditorMockApi.js +272 -0
- package/src/pages/data/document-editor/examples/useDocumentEditorDemoStore.js +284 -0
- package/src/pages/data/filter-bar/DomFilterBar.vue +533 -0
- package/src/pages/data/kanban/DomKanban.vue +1703 -0
- package/src/pages/data/kanban/kanbanApi.js +80 -0
- package/src/pages/data/media-browser/DomMediaBrowser.vue +2344 -0
- package/src/pages/data/media-browser/examples/mediaBrowserMockApi.js +653 -0
- package/src/pages/data/media-browser/examples/useMediaBrowserDemoStore.js +1157 -0
- package/src/pages/data/month-calendar/DomMonthCalendar.vue +1718 -0
- package/src/pages/data/week-planner/DomWeekPlanner.vue +2308 -0
- package/src/pages/dev/code-block/DomCodeBlock.vue +263 -0
- package/src/pages/dev/json-viewer/DomJsonViewer.vue +962 -0
- package/src/pages/forms/_shared/DomFieldLoadingSpinner.vue +12 -0
- package/src/pages/forms/_shared/options.js +61 -0
- package/src/pages/forms/_shared/useOptionMenu.js +59 -0
- package/src/pages/forms/autocomplete/DomAutocomplete.vue +180 -0
- package/src/pages/forms/calendar/DomCalendar.vue +442 -0
- package/src/pages/forms/checkbox/DomCheckbox.vue +69 -0
- package/src/pages/forms/class-toggle-input/DomClassToggleInput.vue +316 -0
- package/src/pages/forms/code-input/DomCodeInput.vue +202 -0
- package/src/pages/forms/color-input/DomColorInput.vue +118 -0
- package/src/pages/forms/combobox/DomCombobox.vue +237 -0
- package/src/pages/forms/confirmation-code-input/DomConfirmationCodeInput.vue +345 -0
- package/src/pages/forms/date-picker/DomDatePicker.vue +299 -0
- package/src/pages/forms/date-time-picker/DomDateTimePicker.vue +630 -0
- package/src/pages/forms/email-input/DomEmailInput.vue +55 -0
- package/src/pages/forms/field/DomField.vue +267 -0
- package/src/pages/forms/field/fieldLayout.js +1 -0
- package/src/pages/forms/field/fieldProps.js +114 -0
- package/src/pages/forms/field/layouts/DomGridFieldLayout.vue +38 -0
- package/src/pages/forms/field/useField.js +343 -0
- package/src/pages/forms/field/validators.js +364 -0
- package/src/pages/forms/file-upload/DomFileUpload.vue +361 -0
- package/src/pages/forms/form/DomForm.vue +985 -0
- package/src/pages/forms/form/adapters/fieldBuilder.js +45 -0
- package/src/pages/forms/form/adapters/jsonSchema.js +264 -0
- package/src/pages/forms/form/adapters/shared.js +53 -0
- package/src/pages/forms/form/adapters/zod.js +235 -0
- package/src/pages/forms/form/formApi.js +38 -0
- package/src/pages/forms/form/formDefinition.js +392 -0
- package/src/pages/forms/form/schemaAdapter.js +54 -0
- package/src/pages/forms/icon-selector/DomIconSelector.vue +460 -0
- package/src/pages/forms/icon-selector/iconSelectorItems.js +490 -0
- package/src/pages/forms/input-group/DomInputGroup.vue +232 -0
- package/src/pages/forms/json-input/DomJsonInput.vue +227 -0
- package/src/pages/forms/json-list-input/DomJsonListInput.vue +206 -0
- package/src/pages/forms/listbox/DomListbox.vue +105 -0
- package/src/pages/forms/money-input/DomMoneyInput.vue +147 -0
- package/src/pages/forms/native-select/DomNativeSelect.vue +67 -0
- package/src/pages/forms/number-input/DomNumberInput.vue +47 -0
- package/src/pages/forms/password-input/DomPasswordInput.vue +218 -0
- package/src/pages/forms/password-input/pwnedPasswords.js +47 -0
- package/src/pages/forms/password-strength/DomPasswordStrength.vue +128 -0
- package/src/pages/forms/password-strength/passwordStrength.js +92 -0
- package/src/pages/forms/position-input/DomPositionInput.vue +83 -0
- package/src/pages/forms/radio-group/DomRadioGroup.vue +102 -0
- package/src/pages/forms/range-input/DomRangeInput.vue +296 -0
- package/src/pages/forms/select/DomSelect.vue +479 -0
- package/src/pages/forms/select-input/DomSelectInput.vue +155 -0
- package/src/pages/forms/tag-combobox/DomTagCombobox.vue +358 -0
- package/src/pages/forms/text-input/DomTextInput.vue +48 -0
- package/src/pages/forms/textarea-composer/DomTextareaComposer.vue +234 -0
- package/src/pages/forms/textarea-input/DomTextareaInput.vue +86 -0
- package/src/pages/forms/textarea-input/useTextareaAutosize.js +147 -0
- package/src/pages/forms/time-selector/DomTimeSelector.vue +389 -0
- package/src/pages/forms/time-selector/timeUtils.js +194 -0
- package/src/pages/forms/toggle/DomToggle.vue +158 -0
- package/src/pages/forms/toggle-button/DomToggleButton.vue +88 -0
- package/src/pages/forms/toggle-button-group/DomToggleButtonGroup.vue +340 -0
- package/src/pages/forms/url-input/DomUrlInput.vue +55 -0
- package/src/pages/forms/wysiwyg/DomWysiwyg.vue +204 -0
- package/src/pages/lib/css/color.js +245 -0
- package/src/pages/lib/headless/accordion.d.ts +7 -0
- package/src/pages/lib/headless/accordion.js +246 -0
- package/src/pages/lib/headless/autocomplete.d.ts +4 -0
- package/src/pages/lib/headless/autocomplete.js +33 -0
- package/src/pages/lib/headless/base.d.ts +25 -0
- package/src/pages/lib/headless/base.js +127 -0
- package/src/pages/lib/headless/checkbox.d.ts +19 -0
- package/src/pages/lib/headless/checkbox.js +99 -0
- package/src/pages/lib/headless/combobox.d.ts +7 -0
- package/src/pages/lib/headless/combobox.js +488 -0
- package/src/pages/lib/headless/dialog.d.ts +23 -0
- package/src/pages/lib/headless/dialog.js +384 -0
- package/src/pages/lib/headless/drawer.d.ts +21 -0
- package/src/pages/lib/headless/drawer.js +682 -0
- package/src/pages/lib/headless/dropdown.d.ts +19 -0
- package/src/pages/lib/headless/dropdown.js +412 -0
- package/src/pages/lib/headless/floating-effect.js +118 -0
- package/src/pages/lib/headless/floating.d.ts +34 -0
- package/src/pages/lib/headless/floating.js +382 -0
- package/src/pages/lib/headless/index.d.ts +19 -0
- package/src/pages/lib/headless/index.js +19 -0
- package/src/pages/lib/headless/listbox.d.ts +19 -0
- package/src/pages/lib/headless/listbox.js +126 -0
- package/src/pages/lib/headless/menu.d.ts +18 -0
- package/src/pages/lib/headless/menu.js +117 -0
- package/src/pages/lib/headless/popover-panel.d.ts +18 -0
- package/src/pages/lib/headless/popover-panel.js +309 -0
- package/src/pages/lib/headless/popover.d.ts +19 -0
- package/src/pages/lib/headless/popover.js +391 -0
- package/src/pages/lib/headless/radio-group.d.ts +19 -0
- package/src/pages/lib/headless/radio-group.js +100 -0
- package/src/pages/lib/headless/tabs.d.ts +18 -0
- package/src/pages/lib/headless/tabs.js +119 -0
- package/src/pages/lib/headless/toast.d.ts +13 -0
- package/src/pages/lib/headless/toast.js +65 -0
- package/src/pages/lib/headless/toggle.d.ts +19 -0
- package/src/pages/lib/headless/toggle.js +162 -0
- package/src/pages/lib/headless/tooltip-controller.d.ts +22 -0
- package/src/pages/lib/headless/tooltip-controller.js +549 -0
- package/src/pages/lib/headless/tooltip.d.ts +19 -0
- package/src/pages/lib/headless/tooltip.js +153 -0
- package/src/pages/lib/headless/transitions.d.ts +5 -0
- package/src/pages/lib/headless/transitions.js +89 -0
- package/src/pages/lib/vue/_internal/DomLiquidGlassSurface.vue +310 -0
- package/src/pages/lib/vue/_internal/liquidGlass.js +112 -0
- package/src/pages/lib/vue/_internal/liquidGlassConfig.js +10 -0
- package/src/pages/lib/vue/codeHighlighter.js +132 -0
- package/src/pages/lib/vue/floatingTeleportTarget.js +23 -0
- package/src/pages/lib/vue/index.d.ts +6806 -0
- package/src/pages/lib/vue/index.js +218 -0
- package/src/pages/lib/vue/useTheme.js +116 -0
- package/src/pages/mobile/action-sheet/DomActionSheet.vue +186 -0
- package/src/pages/mobile/bottom-nav/DomAppBottomNav.vue +94 -0
- package/src/pages/mobile/list-item/DomAppListItem.vue +90 -0
- package/src/pages/mobile/safe-area/DomSafeArea.vue +39 -0
- package/src/pages/mobile/shell/DomAppShell.vue +51 -0
- package/src/pages/mobile/stack/AppStackScreen.vue +63 -0
- package/src/pages/mobile/stack/DomAppStack.vue +711 -0
- package/src/pages/mobile/top-bar/DomAppTopBar.vue +67 -0
- package/src/pages/visual/alert/DomAlert.vue +363 -0
- package/src/pages/visual/avatar/DomAvatar.vue +110 -0
- package/src/pages/visual/avatar-stack/DomAvatarStack.vue +89 -0
- package/src/pages/visual/badge/DomBadge.vue +105 -0
- package/src/pages/visual/card/DomCard.vue +65 -0
- package/src/pages/visual/card-rail/DomCardRail.vue +99 -0
- package/src/pages/visual/empty-state/DomEmptyState.vue +88 -0
- package/src/pages/visual/progress/DomProgress.vue +212 -0
- package/src/pages/visual/progress/DomRadialProgress.vue +186 -0
- package/src/pages/visual/progress/DomSegmentedProgress.vue +350 -0
- package/src/pages/visual/skeleton/DomSkeleton.vue +174 -0
- package/src/pages/visual/spinner/DomSpinner.vue +122 -0
- package/src/pages/visual/status-pill/DomStatusPill.vue +75 -0
- package/src/pages/visual/text-diff/DomTextDiff.vue +577 -0
- package/src/pages/visual/text-diff/textDiff.js +300 -0
- package/src/style.css +446 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { DomElementBase, createRoving, defineDomElement } from './base.js';
|
|
2
|
+
|
|
3
|
+
const itemSelector = [
|
|
4
|
+
'[role="menuitem"]',
|
|
5
|
+
'[role="menuitemcheckbox"]',
|
|
6
|
+
'[role="menuitemradio"]',
|
|
7
|
+
].join(',');
|
|
8
|
+
|
|
9
|
+
function isDisabled(el) {
|
|
10
|
+
return el.hasAttribute('disabled') || el.getAttribute('aria-disabled') === 'true';
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class DomMenuElement extends DomElementBase {
|
|
14
|
+
static get observedAttributes() { return ['orientation']; }
|
|
15
|
+
|
|
16
|
+
static __doc = {
|
|
17
|
+
name: 'dom-menu',
|
|
18
|
+
description: 'A roving-focus action menu for command lists, static menus, and menu panels.',
|
|
19
|
+
slots: [{ name: '(default)', description: 'Menu items with role="menuitem", role="menuitemcheckbox", or role="menuitemradio".' }],
|
|
20
|
+
attributes: [
|
|
21
|
+
{ name: 'orientation', type: "'vertical' | 'horizontal'", description: 'Arrow-key direction. Defaults to vertical.' },
|
|
22
|
+
],
|
|
23
|
+
events: [
|
|
24
|
+
{ name: 'dom:select', payload: '{ value, item }', description: 'Fired when an item is selected.' },
|
|
25
|
+
{ name: 'dom:change', payload: '{ value, checked, item }', description: 'Fired when a checkbox or radio menu item changes.' },
|
|
26
|
+
],
|
|
27
|
+
keyboard: [
|
|
28
|
+
{ keys: '↑ / ↓', action: 'Move active item.' },
|
|
29
|
+
{ keys: 'Home / End', action: 'Jump to first / last item.' },
|
|
30
|
+
{ keys: 'Enter / Space', action: 'Select active item.' },
|
|
31
|
+
],
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
constructor() {
|
|
35
|
+
super();
|
|
36
|
+
this._roving = null;
|
|
37
|
+
this._onKey = this._onKey.bind(this);
|
|
38
|
+
this._onClick = this._onClick.bind(this);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
connectedCallback() {
|
|
42
|
+
this.setAttribute('role', this.getAttribute('role') || 'menu');
|
|
43
|
+
this._refresh();
|
|
44
|
+
this.on(this, 'keydown', this._onKey);
|
|
45
|
+
this.on(this, 'click', this._onClick);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
attributeChangedCallback() {
|
|
49
|
+
this._refresh();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_refresh() {
|
|
53
|
+
const items = this.items;
|
|
54
|
+
const orientation = this.getAttribute('orientation') || 'vertical';
|
|
55
|
+
this.setAttribute('aria-orientation', orientation);
|
|
56
|
+
items.forEach((item, index) => { item.tabIndex = index === 0 ? 0 : -1; });
|
|
57
|
+
this._roving = createRoving({ items, orientation, loop: true });
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
get items() {
|
|
61
|
+
return Array.from(this.querySelectorAll(itemSelector))
|
|
62
|
+
.filter((item) => item.closest('dom-menu') === this)
|
|
63
|
+
.filter((item) => !isDisabled(item));
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
_select(item, event) {
|
|
67
|
+
const role = item.getAttribute('role');
|
|
68
|
+
const value = item.dataset.value ?? item.textContent.trim();
|
|
69
|
+
|
|
70
|
+
if (item.getAttribute('aria-haspopup') === 'menu') {
|
|
71
|
+
item.setAttribute('aria-expanded', 'true');
|
|
72
|
+
this.emit('dom:submenu', { value, item, event });
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
if (role === 'menuitemcheckbox') {
|
|
77
|
+
const checked = item.getAttribute('aria-checked') !== 'true';
|
|
78
|
+
item.setAttribute('aria-checked', String(checked));
|
|
79
|
+
this.emit('dom:change', { value, checked, item, event });
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (role === 'menuitemradio') {
|
|
83
|
+
for (const radio of this.querySelectorAll('[role="menuitemradio"]')) {
|
|
84
|
+
radio.setAttribute('aria-checked', String(radio === item));
|
|
85
|
+
}
|
|
86
|
+
this.emit('dom:change', { value, checked: true, item, event });
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
this.emit('dom:select', { value, item, event });
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
_onClick(event) {
|
|
93
|
+
if (event.target.closest('[data-dom-menu-accessory]')) return;
|
|
94
|
+
const item = event.target.closest(itemSelector);
|
|
95
|
+
if (!item || !this.contains(item)) return;
|
|
96
|
+
if (item.closest('dom-menu') !== this) return;
|
|
97
|
+
if (isDisabled(item)) {
|
|
98
|
+
event.preventDefault();
|
|
99
|
+
event.stopPropagation();
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
this._select(item, event);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
_onKey(event) {
|
|
106
|
+
this._roving?.onKey(event);
|
|
107
|
+
if (event.key !== 'Enter' && event.key !== ' ') return;
|
|
108
|
+
const item = document.activeElement?.closest?.(itemSelector);
|
|
109
|
+
if (!item || !this.contains(item) || isDisabled(item)) return;
|
|
110
|
+
if (document.activeElement?.closest?.('[data-dom-menu-accessory]')) return;
|
|
111
|
+
if (item.closest('dom-menu') !== this) return;
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
this._select(item, event);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
defineDomElement('dom-menu', DomMenuElement);
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/* eslint-disable */
|
|
2
|
+
// Generated by scripts/generate-types.mjs. Do not edit by hand.
|
|
3
|
+
import type { FloatingPositionOptions, FloatingPositionResult } from './floating.js';
|
|
4
|
+
export declare function preparePopoverPanel(panel: HTMLElement | null, trigger?: HTMLElement | null): void;
|
|
5
|
+
export declare function setPopoverOpen(panel: HTMLElement | null, open: boolean): void;
|
|
6
|
+
export declare function openPopoverPanel(panel: HTMLElement | null, trigger?: HTMLElement | null, options?: FloatingPositionOptions, afterPlace?: () => void): void;
|
|
7
|
+
export declare function closePopoverPanel(panel: HTMLElement | null, options?: { immediate?: boolean }): void;
|
|
8
|
+
export declare function isPopoverOpen(panel: HTMLElement | null): boolean;
|
|
9
|
+
export declare function positionPopoverPanel(panel: HTMLElement | null, trigger: HTMLElement | null, options?: FloatingPositionOptions & { animatePlacement?: boolean }): FloatingPositionResult | undefined;
|
|
10
|
+
export declare function attachPopoverReflow(panel: HTMLElement, trigger: HTMLElement, options?: FloatingPositionOptions): () => void;
|
|
11
|
+
export declare function readFloatingPositionOptions(host: HTMLElement, defaults?: FloatingPositionOptions): FloatingPositionOptions;
|
|
12
|
+
export declare function bindFloatingReflow(host: HTMLElement & { _reflowOff?: (() => void) | null }, panel: HTMLElement, trigger: HTMLElement, options?: FloatingPositionOptions): void;
|
|
13
|
+
export declare function unbindFloatingReflow(host: HTMLElement & { _reflowOff?: (() => void) | null }): void;
|
|
14
|
+
export declare function syncFloatingScrollLock(host: HTMLElement & Record<string, unknown>, panel: HTMLElement, next: boolean, stateKey?: string): void;
|
|
15
|
+
export declare function bindPopoverToggle(panel: HTMLElement, handler: (open: boolean) => void): () => void;
|
|
16
|
+
export declare function lockDocumentScroll(panel?: HTMLElement | null): void;
|
|
17
|
+
export declare function unlockDocumentScroll(panel?: HTMLElement | null): void;
|
|
18
|
+
export declare function ensurePopoverPanelStyles(): void;
|
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { isBrowser, uid } from './base.js';
|
|
2
|
+
import {
|
|
3
|
+
applyFloatingPosition,
|
|
4
|
+
autoUpdateFloating,
|
|
5
|
+
} from './floating.js';
|
|
6
|
+
import {
|
|
7
|
+
beginFloatingEffectEnter,
|
|
8
|
+
beginFloatingEffectLeave,
|
|
9
|
+
hasFloatingEffect,
|
|
10
|
+
prepareFloatingEffect,
|
|
11
|
+
resetFloatingEffectState,
|
|
12
|
+
} from './floating-effect.js';
|
|
13
|
+
|
|
14
|
+
/** Wire a panel for the native Popover API (top layer + light dismiss). */
|
|
15
|
+
export function preparePopoverPanel(panel, trigger) {
|
|
16
|
+
if (!panel) return;
|
|
17
|
+
const id = uid('pop');
|
|
18
|
+
panel.id = panel.id || `${id}-panel`;
|
|
19
|
+
if (!panel.hasAttribute('popover')) {
|
|
20
|
+
panel.setAttribute('popover', 'auto');
|
|
21
|
+
}
|
|
22
|
+
prepareFloatingEffect(panel);
|
|
23
|
+
trigger?.setAttribute('aria-controls', panel.id);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export function setPopoverOpen(panel, open) {
|
|
27
|
+
if (!panel) return;
|
|
28
|
+
if (open) openPopoverPanel(panel);
|
|
29
|
+
else closePopoverPanel(panel);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function sideOf(placement = 'bottom') {
|
|
33
|
+
return String(placement).split('-')[0] || 'bottom';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Show a popover panel and anchor it under `trigger` (re-run after top-layer promotion). */
|
|
37
|
+
export function openPopoverPanel(panel, trigger, options = {}, afterPlace) {
|
|
38
|
+
if (!panel) return;
|
|
39
|
+
delete panel.dataset.leaving;
|
|
40
|
+
delete panel.dataset.skipLeave;
|
|
41
|
+
panel.dataset.side = sideOf(options.placement);
|
|
42
|
+
panel.dataset.placement = options.placement || 'bottom';
|
|
43
|
+
const place = () => {
|
|
44
|
+
if (trigger) positionPopoverPanel(panel, trigger, { ...options, animatePlacement: true });
|
|
45
|
+
afterPlace?.();
|
|
46
|
+
};
|
|
47
|
+
const animated = beginFloatingEffectEnter(panel, {
|
|
48
|
+
beforeMeasure: () => {
|
|
49
|
+
if (!isPopoverOpen(panel)) panel.showPopover?.();
|
|
50
|
+
place();
|
|
51
|
+
},
|
|
52
|
+
beforeFrame: place,
|
|
53
|
+
delayFrames: 1,
|
|
54
|
+
shouldContinue: () => isPopoverOpen(panel),
|
|
55
|
+
});
|
|
56
|
+
if (!animated) {
|
|
57
|
+
if (!isPopoverOpen(panel)) panel.showPopover?.();
|
|
58
|
+
place();
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function finishClose(panel) {
|
|
63
|
+
panel.dataset.skipLeave = 'true';
|
|
64
|
+
if (isPopoverOpen(panel)) panel.hidePopover?.();
|
|
65
|
+
delete panel.dataset.skipLeave;
|
|
66
|
+
resetFloatingEffectState(panel);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function closePopoverPanel(panel, { immediate = false } = {}) {
|
|
70
|
+
if (!panel) return;
|
|
71
|
+
beginFloatingEffectLeave(panel, {
|
|
72
|
+
immediate: immediate || !isPopoverOpen(panel),
|
|
73
|
+
finish: () => finishClose(panel),
|
|
74
|
+
shouldContinue: () => isPopoverOpen(panel),
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function isPopoverOpen(panel) {
|
|
79
|
+
return !!panel?.matches?.(':popover-open');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** Anchor a top-layer popover panel under its trigger (fixed coords). */
|
|
83
|
+
export function positionPopoverPanel(panel, trigger, { align = 'left', offset = 8, placement = 'bottom', padding = 8, mode = 'viewport', flip = true, animatePlacement } = {}) {
|
|
84
|
+
if (!panel || !trigger || typeof window === 'undefined') return;
|
|
85
|
+
const result = applyFloatingPosition(trigger, panel, { align, offset, placement, padding, mode, flip });
|
|
86
|
+
if (result) {
|
|
87
|
+
const shouldAnimatePlacement = panel.dataset.entering || panel.dataset.leaving
|
|
88
|
+
? true
|
|
89
|
+
: animatePlacement ?? !result.flipped;
|
|
90
|
+
panel.toggleAttribute('data-placement-ready', true);
|
|
91
|
+
panel.toggleAttribute('data-animate-placement', shouldAnimatePlacement);
|
|
92
|
+
}
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** Keep the panel aligned while scrolling or resizing. Returns an unbind fn. */
|
|
97
|
+
export function attachPopoverReflow(panel, trigger, options = {}) {
|
|
98
|
+
const update = () => positionPopoverPanel(panel, trigger, options);
|
|
99
|
+
update();
|
|
100
|
+
return autoUpdateFloating(trigger, panel, update);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function readFloatingPositionOptions(host, defaults = {}) {
|
|
104
|
+
const {
|
|
105
|
+
align = 'left',
|
|
106
|
+
offset = 8,
|
|
107
|
+
placement = 'bottom',
|
|
108
|
+
padding = 8,
|
|
109
|
+
mode = 'viewport',
|
|
110
|
+
flip,
|
|
111
|
+
} = defaults;
|
|
112
|
+
const options = {
|
|
113
|
+
align: host.getAttribute('align') || align,
|
|
114
|
+
offset: Number(host.getAttribute('offset') || offset),
|
|
115
|
+
placement: host.getAttribute('placement') || placement,
|
|
116
|
+
padding: Number(host.getAttribute('collision-padding') || padding),
|
|
117
|
+
mode: host.getAttribute('floating-mode') || mode,
|
|
118
|
+
};
|
|
119
|
+
if (flip !== undefined) options.flip = host.getAttribute('flip') !== 'false';
|
|
120
|
+
return options;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function bindFloatingReflow(host, panel, trigger, options = {}) {
|
|
124
|
+
host._reflowOff?.();
|
|
125
|
+
host._reflowOff = attachPopoverReflow(panel, trigger, options);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export function unbindFloatingReflow(host) {
|
|
129
|
+
host._reflowOff?.();
|
|
130
|
+
host._reflowOff = null;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export function syncFloatingScrollLock(host, panel, next, stateKey = '_scrollLocked') {
|
|
134
|
+
if (next && host.hasAttribute('lock-scroll') && !host[stateKey]) {
|
|
135
|
+
lockDocumentScroll(panel);
|
|
136
|
+
host[stateKey] = true;
|
|
137
|
+
} else if ((!next || !host.hasAttribute('lock-scroll')) && host[stateKey]) {
|
|
138
|
+
unlockDocumentScroll(panel);
|
|
139
|
+
host[stateKey] = false;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** Sync host state when the browser opens/closes the popover. Returns an unbind fn. */
|
|
144
|
+
export function bindPopoverToggle(panel, handler) {
|
|
145
|
+
const onBeforeToggle = (e) => {
|
|
146
|
+
if (
|
|
147
|
+
e.newState === 'closed'
|
|
148
|
+
&& panel.dataset.skipLeave !== 'true'
|
|
149
|
+
&& hasFloatingEffect(panel)
|
|
150
|
+
&& isPopoverOpen(panel)
|
|
151
|
+
) {
|
|
152
|
+
e.preventDefault();
|
|
153
|
+
handler(false);
|
|
154
|
+
closePopoverPanel(panel);
|
|
155
|
+
}
|
|
156
|
+
};
|
|
157
|
+
const onToggle = (e) => handler(e.newState === 'open');
|
|
158
|
+
panel.addEventListener('beforetoggle', onBeforeToggle);
|
|
159
|
+
panel.addEventListener('toggle', onToggle);
|
|
160
|
+
return () => {
|
|
161
|
+
panel.removeEventListener('beforetoggle', onBeforeToggle);
|
|
162
|
+
panel.removeEventListener('toggle', onToggle);
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let scrollLockCount = 0;
|
|
167
|
+
let previousHtmlOverflow = '';
|
|
168
|
+
let previousHtmlOverscrollBehavior = '';
|
|
169
|
+
let previousBodyOverflow = '';
|
|
170
|
+
let previousBodyPaddingRight = '';
|
|
171
|
+
let previousBodyOverscrollBehavior = '';
|
|
172
|
+
let scrollLockOff = null;
|
|
173
|
+
let touchStartY = 0;
|
|
174
|
+
const scrollLockPanels = new Set();
|
|
175
|
+
|
|
176
|
+
function lockedPanelFor(target) {
|
|
177
|
+
for (const panel of scrollLockPanels) {
|
|
178
|
+
if (panel?.isConnected && panel.contains(target)) return panel;
|
|
179
|
+
}
|
|
180
|
+
return null;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function closestScrollable(target, panel) {
|
|
184
|
+
let node = target instanceof Element ? target : target?.parentElement;
|
|
185
|
+
while (node && node !== document.body) {
|
|
186
|
+
if (node instanceof Element) {
|
|
187
|
+
const style = window.getComputedStyle(node);
|
|
188
|
+
const overflow = `${style.overflowY} ${style.overflow}`;
|
|
189
|
+
if (/(auto|scroll|overlay)/.test(overflow) && node.scrollHeight > node.clientHeight) {
|
|
190
|
+
return node;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (node === panel) break;
|
|
194
|
+
node = node.parentElement;
|
|
195
|
+
}
|
|
196
|
+
return null;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
function canScrollElement(element, deltaY) {
|
|
200
|
+
if (!element || !deltaY) return false;
|
|
201
|
+
if (deltaY < 0) return element.scrollTop > 0;
|
|
202
|
+
return element.scrollTop + element.clientHeight < element.scrollHeight - 1;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function canScrollInsideLockedPanel(target, deltaY) {
|
|
206
|
+
const panel = lockedPanelFor(target);
|
|
207
|
+
if (!panel) return false;
|
|
208
|
+
return canScrollElement(closestScrollable(target, panel), deltaY);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function bindScrollLockEvents() {
|
|
212
|
+
const onWheel = (event) => {
|
|
213
|
+
if (canScrollInsideLockedPanel(event.target, event.deltaY)) return;
|
|
214
|
+
event.preventDefault();
|
|
215
|
+
};
|
|
216
|
+
const onTouchStart = (event) => {
|
|
217
|
+
touchStartY = event.touches?.[0]?.clientY ?? 0;
|
|
218
|
+
};
|
|
219
|
+
const onTouchMove = (event) => {
|
|
220
|
+
const nextY = event.touches?.[0]?.clientY ?? touchStartY;
|
|
221
|
+
const deltaY = touchStartY - nextY;
|
|
222
|
+
if (canScrollInsideLockedPanel(event.target, deltaY)) {
|
|
223
|
+
touchStartY = nextY;
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
event.preventDefault();
|
|
227
|
+
};
|
|
228
|
+
const onKeydown = (event) => {
|
|
229
|
+
if (lockedPanelFor(event.target)) return;
|
|
230
|
+
if (!['ArrowDown', 'ArrowUp', 'End', 'Home', 'PageDown', 'PageUp', ' '].includes(event.key)) return;
|
|
231
|
+
event.preventDefault();
|
|
232
|
+
};
|
|
233
|
+
document.addEventListener('wheel', onWheel, { capture: true, passive: false });
|
|
234
|
+
document.addEventListener('touchstart', onTouchStart, { capture: true, passive: true });
|
|
235
|
+
document.addEventListener('touchmove', onTouchMove, { capture: true, passive: false });
|
|
236
|
+
document.addEventListener('keydown', onKeydown, true);
|
|
237
|
+
return () => {
|
|
238
|
+
document.removeEventListener('wheel', onWheel, true);
|
|
239
|
+
document.removeEventListener('touchstart', onTouchStart, true);
|
|
240
|
+
document.removeEventListener('touchmove', onTouchMove, true);
|
|
241
|
+
document.removeEventListener('keydown', onKeydown, true);
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function lockDocumentScroll(panel) {
|
|
246
|
+
if (!isBrowser) return;
|
|
247
|
+
if (panel) scrollLockPanels.add(panel);
|
|
248
|
+
scrollLockCount += 1;
|
|
249
|
+
if (scrollLockCount > 1) return;
|
|
250
|
+
previousHtmlOverflow = document.documentElement.style.overflow;
|
|
251
|
+
previousHtmlOverscrollBehavior = document.documentElement.style.overscrollBehavior;
|
|
252
|
+
previousBodyOverflow = document.body.style.overflow;
|
|
253
|
+
previousBodyPaddingRight = document.body.style.paddingRight;
|
|
254
|
+
previousBodyOverscrollBehavior = document.body.style.overscrollBehavior;
|
|
255
|
+
const scrollbarWidth = window.innerWidth - document.documentElement.clientWidth;
|
|
256
|
+
document.documentElement.style.overflow = 'hidden';
|
|
257
|
+
document.documentElement.style.overscrollBehavior = 'contain';
|
|
258
|
+
document.body.style.overflow = 'hidden';
|
|
259
|
+
document.body.style.overscrollBehavior = 'contain';
|
|
260
|
+
if (scrollbarWidth > 0) {
|
|
261
|
+
document.body.style.paddingRight = `${scrollbarWidth}px`;
|
|
262
|
+
}
|
|
263
|
+
scrollLockOff = bindScrollLockEvents();
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export function unlockDocumentScroll(panel) {
|
|
267
|
+
if (!isBrowser || scrollLockCount === 0) return;
|
|
268
|
+
if (panel) scrollLockPanels.delete(panel);
|
|
269
|
+
scrollLockCount -= 1;
|
|
270
|
+
if (scrollLockCount > 0) return;
|
|
271
|
+
scrollLockPanels.clear();
|
|
272
|
+
scrollLockOff?.();
|
|
273
|
+
scrollLockOff = null;
|
|
274
|
+
document.documentElement.style.overflow = previousHtmlOverflow;
|
|
275
|
+
document.documentElement.style.overscrollBehavior = previousHtmlOverscrollBehavior;
|
|
276
|
+
document.body.style.overflow = previousBodyOverflow;
|
|
277
|
+
document.body.style.paddingRight = previousBodyPaddingRight;
|
|
278
|
+
document.body.style.overscrollBehavior = previousBodyOverscrollBehavior;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const STYLE_ID = 'dom-popover-panel-styles';
|
|
282
|
+
|
|
283
|
+
/** Hide closed popover panels and neutralize native popover chrome. */
|
|
284
|
+
export function ensurePopoverPanelStyles() {
|
|
285
|
+
if (!isBrowser || document.getElementById(STYLE_ID)) return;
|
|
286
|
+
const style = document.createElement('style');
|
|
287
|
+
style.id = STYLE_ID;
|
|
288
|
+
style.textContent = `
|
|
289
|
+
@layer base {
|
|
290
|
+
:where([popover].dom-popover-panel, [popover].dom-dropdown-menu) {
|
|
291
|
+
background: transparent;
|
|
292
|
+
border: 0;
|
|
293
|
+
box-shadow: none;
|
|
294
|
+
color: inherit;
|
|
295
|
+
margin: 0;
|
|
296
|
+
overflow: visible;
|
|
297
|
+
padding: 0;
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
[popover].dom-popover-panel:not(:popover-open),
|
|
302
|
+
[popover].dom-dropdown-menu:not(:popover-open) {
|
|
303
|
+
display: none !important;
|
|
304
|
+
}
|
|
305
|
+
`;
|
|
306
|
+
document.head.appendChild(style);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
ensurePopoverPanelStyles();
|
|
@@ -0,0 +1,19 @@
|
|
|
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 DomPopoverElement extends DomElementBase {
|
|
5
|
+
static readonly observedAttributes?: Array<string>;
|
|
6
|
+
static readonly __doc?: HeadlessElementDoc;
|
|
7
|
+
open: boolean;
|
|
8
|
+
toggle(): void;
|
|
9
|
+
}
|
|
10
|
+
export interface HeadlessElementDoc {
|
|
11
|
+
name?: string;
|
|
12
|
+
description?: string;
|
|
13
|
+
slots?: Array<Record<string, unknown>>;
|
|
14
|
+
attributes?: Array<Record<string, unknown>>;
|
|
15
|
+
events?: Array<Record<string, unknown>>;
|
|
16
|
+
keyboard?: Array<Record<string, unknown>>;
|
|
17
|
+
example?: string;
|
|
18
|
+
[key: string]: unknown;
|
|
19
|
+
}
|