@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,58 @@
|
|
|
1
|
+
export const SUBMENU_CLOSE_DELAY = 220;
|
|
2
|
+
export const SUBMENU_OVERLAP = 8;
|
|
3
|
+
|
|
4
|
+
export function submenuOptions() {
|
|
5
|
+
return {
|
|
6
|
+
placement: 'right',
|
|
7
|
+
align: 'start',
|
|
8
|
+
offset: -SUBMENU_OVERLAP,
|
|
9
|
+
padding: 8,
|
|
10
|
+
mode: 'viewport',
|
|
11
|
+
flip: true,
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function isSubmenuSafeTarget(target) {
|
|
16
|
+
if (!(target instanceof Element)) return false;
|
|
17
|
+
return Boolean(target.closest('[data-dom-menu-submenu-panel]'));
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function isLeavingSubmenu(event, trigger, panel) {
|
|
21
|
+
return !(
|
|
22
|
+
panel?.contains(event.relatedTarget)
|
|
23
|
+
|| trigger?.contains(event.relatedTarget)
|
|
24
|
+
|| isSubmenuSafeTarget(event.relatedTarget)
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function focusFirstMenuItem(panel) {
|
|
29
|
+
panel?.querySelector('[role="menuitem"]')?.focus();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isSubmenuOpenKey(event) {
|
|
33
|
+
return event.key === 'ArrowRight' || event.key === 'Enter' || event.key === ' ';
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function isSubmenuCloseKey(event) {
|
|
37
|
+
return event.key === 'ArrowLeft' || event.key === 'Escape';
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function createSubmenuCloseTimer(close, delay = SUBMENU_CLOSE_DELAY) {
|
|
41
|
+
let timer = null;
|
|
42
|
+
|
|
43
|
+
function cancel() {
|
|
44
|
+
if (!timer) return;
|
|
45
|
+
clearTimeout(timer);
|
|
46
|
+
timer = null;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function schedule(...args) {
|
|
50
|
+
cancel();
|
|
51
|
+
timer = setTimeout(() => close(...args), delay);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return {
|
|
55
|
+
cancel,
|
|
56
|
+
schedule,
|
|
57
|
+
};
|
|
58
|
+
}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed } from 'vue';
|
|
3
|
+
import DomDropdown from '../dropdown/DomDropdown.vue';
|
|
4
|
+
|
|
5
|
+
defineOptions({
|
|
6
|
+
__doc: {
|
|
7
|
+
name: 'Pagination',
|
|
8
|
+
tag: '<DomPagination>',
|
|
9
|
+
description: 'A controlled page navigator for data grids, audit logs, search results, and resource lists.',
|
|
10
|
+
icon: 'M7 8l-4 4 4 4M17 8l4 4-4 4M10 12h4',
|
|
11
|
+
events: [
|
|
12
|
+
{ name: 'update:page', payload: 'number', description: 'Fired when the selected page changes.' },
|
|
13
|
+
{ name: 'page-change', payload: '{ page, previousPage, pageCount }', description: 'Fired with page transition metadata.' },
|
|
14
|
+
],
|
|
15
|
+
playground: {
|
|
16
|
+
initial: { page: 4, pageSize: 25, total: 286 },
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
const props = defineProps({
|
|
22
|
+
page: {
|
|
23
|
+
type: Number,
|
|
24
|
+
default: 1,
|
|
25
|
+
_edit: { description: 'Current 1-based page.' },
|
|
26
|
+
},
|
|
27
|
+
pageSize: {
|
|
28
|
+
type: Number,
|
|
29
|
+
default: 25,
|
|
30
|
+
_edit: { description: 'Rows or items per page.' },
|
|
31
|
+
},
|
|
32
|
+
total: {
|
|
33
|
+
type: Number,
|
|
34
|
+
default: 0,
|
|
35
|
+
_edit: { description: 'Total available items.' },
|
|
36
|
+
},
|
|
37
|
+
pageCount: {
|
|
38
|
+
type: Number,
|
|
39
|
+
default: null,
|
|
40
|
+
_edit: { description: 'Explicit total page count. Overrides total/pageSize when provided.' },
|
|
41
|
+
},
|
|
42
|
+
siblingCount: {
|
|
43
|
+
type: Number,
|
|
44
|
+
default: 1,
|
|
45
|
+
_edit: { description: 'Pages to show around the current page.' },
|
|
46
|
+
},
|
|
47
|
+
boundaryCount: {
|
|
48
|
+
type: Number,
|
|
49
|
+
default: 1,
|
|
50
|
+
_edit: { description: 'Pages to show at the beginning and end.' },
|
|
51
|
+
},
|
|
52
|
+
disabled: {
|
|
53
|
+
type: Boolean,
|
|
54
|
+
default: false,
|
|
55
|
+
_edit: { description: 'Disable pagination controls.' },
|
|
56
|
+
},
|
|
57
|
+
compact: {
|
|
58
|
+
type: Boolean,
|
|
59
|
+
default: false,
|
|
60
|
+
_edit: { description: 'Hide first and last buttons.' },
|
|
61
|
+
},
|
|
62
|
+
label: {
|
|
63
|
+
type: String,
|
|
64
|
+
default: 'Pagination',
|
|
65
|
+
_edit: { description: 'Navigation landmark label.' },
|
|
66
|
+
},
|
|
67
|
+
activeStyle: {
|
|
68
|
+
type: String,
|
|
69
|
+
default: 'secondary',
|
|
70
|
+
_edit: {
|
|
71
|
+
options: ['secondary', 'primary'],
|
|
72
|
+
description: 'Visual style for the current page. Use primary to restore the stronger selected treatment.',
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
const emit = defineEmits(['update:page', 'page-change']);
|
|
78
|
+
|
|
79
|
+
const resolvedPageCount = computed(() => {
|
|
80
|
+
const explicit = Number(props.pageCount);
|
|
81
|
+
if (Number.isFinite(explicit) && explicit > 0) return Math.floor(explicit);
|
|
82
|
+
|
|
83
|
+
const pageSize = Number(props.pageSize);
|
|
84
|
+
const total = Number(props.total);
|
|
85
|
+
if (!Number.isFinite(pageSize) || pageSize <= 0 || !Number.isFinite(total) || total <= 0) return 1;
|
|
86
|
+
return Math.max(1, Math.ceil(total / pageSize));
|
|
87
|
+
});
|
|
88
|
+
const currentPage = computed(() => clampPage(props.page));
|
|
89
|
+
const pageItems = computed(() => visiblePageItems(currentPage.value, resolvedPageCount.value));
|
|
90
|
+
const canGoPrevious = computed(() => !props.disabled && currentPage.value > 1);
|
|
91
|
+
const canGoNext = computed(() => !props.disabled && currentPage.value < resolvedPageCount.value);
|
|
92
|
+
const itemRangeLabel = computed(() => {
|
|
93
|
+
const total = Number(props.total);
|
|
94
|
+
const pageSize = Number(props.pageSize);
|
|
95
|
+
if (!Number.isFinite(total) || total <= 0 || !Number.isFinite(pageSize) || pageSize <= 0) {
|
|
96
|
+
return `${resolvedPageCount.value} page${resolvedPageCount.value === 1 ? '' : 's'}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const start = (currentPage.value - 1) * pageSize + 1;
|
|
100
|
+
const end = Math.min(total, currentPage.value * pageSize);
|
|
101
|
+
return `${start}-${end} of ${total}`;
|
|
102
|
+
});
|
|
103
|
+
const controls = computed(() => {
|
|
104
|
+
const items = [];
|
|
105
|
+
if (!props.compact) {
|
|
106
|
+
items.push({ type: 'action', key: 'first', label: 'First page', disabled: !canGoPrevious.value, target: 1, icon: 'first' });
|
|
107
|
+
}
|
|
108
|
+
items.push({ type: 'action', key: 'previous', label: 'Previous page', disabled: !canGoPrevious.value, target: currentPage.value - 1, icon: 'previous' });
|
|
109
|
+
for (const item of pageItems.value) items.push(item);
|
|
110
|
+
items.push({ type: 'action', key: 'next', label: 'Next page', disabled: !canGoNext.value, target: currentPage.value + 1, icon: 'next' });
|
|
111
|
+
if (!props.compact) {
|
|
112
|
+
items.push({ type: 'action', key: 'last', label: 'Last page', disabled: !canGoNext.value, target: resolvedPageCount.value, icon: 'last' });
|
|
113
|
+
}
|
|
114
|
+
return items;
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
function clampPage(page) {
|
|
118
|
+
const value = Math.floor(Number(page));
|
|
119
|
+
if (!Number.isFinite(value)) return 1;
|
|
120
|
+
return Math.min(resolvedPageCount.value, Math.max(1, value));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function visiblePageItems(page, count) {
|
|
124
|
+
const pages = new Set();
|
|
125
|
+
const boundary = Math.max(0, Math.floor(Number(props.boundaryCount) || 0));
|
|
126
|
+
const siblings = Math.max(0, Math.floor(Number(props.siblingCount) || 0));
|
|
127
|
+
|
|
128
|
+
for (let index = 1; index <= Math.min(boundary, count); index += 1) pages.add(index);
|
|
129
|
+
for (let index = Math.max(1, count - boundary + 1); index <= count; index += 1) pages.add(index);
|
|
130
|
+
for (let index = Math.max(1, page - siblings); index <= Math.min(count, page + siblings); index += 1) pages.add(index);
|
|
131
|
+
|
|
132
|
+
const sorted = [...pages].sort((a, b) => a - b);
|
|
133
|
+
const items = [];
|
|
134
|
+
let previous = 0;
|
|
135
|
+
|
|
136
|
+
for (const item of sorted) {
|
|
137
|
+
if (previous && item - previous > 1) {
|
|
138
|
+
const hiddenPages = range(previous + 1, item - 1);
|
|
139
|
+
if (hiddenPages.length === 1) {
|
|
140
|
+
items.push({ type: 'page', page: hiddenPages[0], key: `page-${hiddenPages[0]}` });
|
|
141
|
+
} else {
|
|
142
|
+
items.push({ type: 'ellipsis', key: `ellipsis-${previous}-${item}`, pages: hiddenPages });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
items.push({ type: 'page', page: item, key: `page-${item}` });
|
|
146
|
+
previous = item;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return items;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function range(start, end) {
|
|
153
|
+
const pages = [];
|
|
154
|
+
for (let page = start; page <= end; page += 1) pages.push(page);
|
|
155
|
+
return pages;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function goTo(page) {
|
|
159
|
+
const nextPage = clampPage(page);
|
|
160
|
+
if (props.disabled || nextPage === currentPage.value) return;
|
|
161
|
+
|
|
162
|
+
emit('update:page', nextPage);
|
|
163
|
+
emit('page-change', {
|
|
164
|
+
page: nextPage,
|
|
165
|
+
previousPage: currentPage.value,
|
|
166
|
+
pageCount: resolvedPageCount.value,
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function onHiddenPageSelect(value) {
|
|
171
|
+
const page = Number(value);
|
|
172
|
+
if (Number.isFinite(page)) goTo(page);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function hiddenPageOptions(item) {
|
|
176
|
+
return (item.pages || []).map((page) => ({
|
|
177
|
+
label: `Page ${page}`,
|
|
178
|
+
value: String(page),
|
|
179
|
+
}));
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function hiddenPagesLabel(item) {
|
|
183
|
+
const pages = item.pages || [];
|
|
184
|
+
if (!pages.length) return 'Hidden pages';
|
|
185
|
+
return `Pages ${pages[0]} to ${pages[pages.length - 1]}`;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function activePageClasses() {
|
|
189
|
+
const styles = {
|
|
190
|
+
primary: 'z-20 border-primary bg-primary text-primary-fg shadow-sm',
|
|
191
|
+
secondary: 'z-20 border-border bg-secondary bg-[linear-gradient(rgb(0_0_0/6%)_0_0)] text-secondary-fg shadow-[inset_0_2px_4px_rgba(0,0,0,0.28)]',
|
|
192
|
+
};
|
|
193
|
+
return styles[props.activeStyle] || styles.secondary;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function buttonClasses(active = false) {
|
|
197
|
+
return [
|
|
198
|
+
'relative grid size-9 place-items-center border text-sm font-medium transition hover:z-30 focus:z-40 focus:outline-none focus-visible:ring-2 focus-visible:ring-ring/60 disabled:cursor-not-allowed disabled:opacity-45',
|
|
199
|
+
active
|
|
200
|
+
? activePageClasses()
|
|
201
|
+
: 'border-border bg-canvas text-canvas-fg hover:border-primary/40 hover:bg-secondary',
|
|
202
|
+
];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function controlClasses(item, index) {
|
|
206
|
+
return [
|
|
207
|
+
buttonClasses(item.type === 'page' && item.page === currentPage.value),
|
|
208
|
+
index === 0 ? 'ml-0' : '-ml-px',
|
|
209
|
+
index === 0 && 'rounded-l-md',
|
|
210
|
+
index === controls.value.length - 1 && 'rounded-r-md',
|
|
211
|
+
];
|
|
212
|
+
}
|
|
213
|
+
</script>
|
|
214
|
+
|
|
215
|
+
<template>
|
|
216
|
+
<nav class="flex flex-col gap-3 text-sm sm:flex-row sm:items-center sm:justify-between" :aria-label="label">
|
|
217
|
+
<p class="text-muted-fg">{{ itemRangeLabel }}</p>
|
|
218
|
+
<div class="inline-flex items-center rounded-md shadow-sm">
|
|
219
|
+
<template v-for="(item, index) in controls" :key="item.key">
|
|
220
|
+
<DomDropdown
|
|
221
|
+
v-if="item.type === 'ellipsis'"
|
|
222
|
+
class="!contents"
|
|
223
|
+
align="left"
|
|
224
|
+
placement="bottom"
|
|
225
|
+
width="min-w-28"
|
|
226
|
+
:items="hiddenPageOptions(item)"
|
|
227
|
+
:label="hiddenPagesLabel(item)"
|
|
228
|
+
:focus-value="String(item.pages?.[0] || '')"
|
|
229
|
+
:disabled="disabled || !item.pages?.length"
|
|
230
|
+
:trigger-class="controlClasses(item, index)"
|
|
231
|
+
:show-indicator="false"
|
|
232
|
+
@select="onHiddenPageSelect"
|
|
233
|
+
>
|
|
234
|
+
<template #trigger>
|
|
235
|
+
<span class="sr-only">{{ hiddenPagesLabel(item) }}</span>
|
|
236
|
+
<span aria-hidden="true">...</span>
|
|
237
|
+
</template>
|
|
238
|
+
</DomDropdown>
|
|
239
|
+
<button
|
|
240
|
+
v-else
|
|
241
|
+
type="button"
|
|
242
|
+
:class="controlClasses(item, index)"
|
|
243
|
+
:disabled="item.disabled || disabled"
|
|
244
|
+
:aria-current="item.type === 'page' && item.page === currentPage ? 'page' : undefined"
|
|
245
|
+
:aria-label="item.label || `Page ${item.page}`"
|
|
246
|
+
@click="goTo(item.target ?? item.page)"
|
|
247
|
+
>
|
|
248
|
+
<template v-if="item.type === 'page'">{{ item.page }}</template>
|
|
249
|
+
<svg v-else-if="item.icon === 'first'" viewBox="0 0 24 24" class="size-4" fill="none" aria-hidden="true">
|
|
250
|
+
<path d="M11 7l-5 5 5 5M18 7l-5 5 5 5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
|
|
251
|
+
</svg>
|
|
252
|
+
<svg v-else-if="item.icon === 'previous'" viewBox="0 0 24 24" class="size-4" fill="none" aria-hidden="true">
|
|
253
|
+
<path d="M15 6l-6 6 6 6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
|
|
254
|
+
</svg>
|
|
255
|
+
<svg v-else-if="item.icon === 'next'" viewBox="0 0 24 24" class="size-4" fill="none" aria-hidden="true">
|
|
256
|
+
<path d="M9 6l6 6-6 6" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
|
|
257
|
+
</svg>
|
|
258
|
+
<svg v-else viewBox="0 0 24 24" class="size-4" fill="none" aria-hidden="true">
|
|
259
|
+
<path d="M13 7l5 5-5 5M6 7l5 5-5 5" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" />
|
|
260
|
+
</svg>
|
|
261
|
+
</button>
|
|
262
|
+
</template>
|
|
263
|
+
</div>
|
|
264
|
+
</nav>
|
|
265
|
+
</template>
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
import { computed, defineComponent, h } from 'vue';
|
|
3
|
+
import { deserializeTree } from '../../_layout/inspector/serialize.js';
|
|
4
|
+
|
|
5
|
+
const isTextNode = (node) => node && node.type === 'text';
|
|
6
|
+
|
|
7
|
+
// Recursive renderer for the spec tree.
|
|
8
|
+
const RenderNode = defineComponent({
|
|
9
|
+
name: 'RenderNode',
|
|
10
|
+
props: { node: { type: Object, required: true } },
|
|
11
|
+
setup(props) {
|
|
12
|
+
return () => {
|
|
13
|
+
const n = props.node;
|
|
14
|
+
if (isTextNode(n)) return n.text;
|
|
15
|
+
const tag = n.component || 'div';
|
|
16
|
+
const children = (n.children || []).map((c) =>
|
|
17
|
+
isTextNode(c) ? c.text : h(RenderNode, { node: c, key: c.id }),
|
|
18
|
+
);
|
|
19
|
+
return h(tag, { ...(n.props || {}) }, children.length ? children : undefined);
|
|
20
|
+
};
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
// Rehydrate a saved spec into a live tree of components. Use this to render
|
|
25
|
+
// content stored in a database, fetched from an API, or generated by the
|
|
26
|
+
// studio export.
|
|
27
|
+
//
|
|
28
|
+
// <DomRenderer :spec="loadedFromDb" />
|
|
29
|
+
export default defineComponent({
|
|
30
|
+
name: 'DomRenderer',
|
|
31
|
+
__doc: {
|
|
32
|
+
hidden: true,
|
|
33
|
+
studio: { hidden: true },
|
|
34
|
+
},
|
|
35
|
+
props: { spec: { type: Object, required: true } },
|
|
36
|
+
setup(props) {
|
|
37
|
+
const tree = computed(() => {
|
|
38
|
+
try { return deserializeTree(props.spec); } catch { return null; }
|
|
39
|
+
});
|
|
40
|
+
return () =>
|
|
41
|
+
tree.value
|
|
42
|
+
? h(RenderNode, { node: tree.value })
|
|
43
|
+
: h('div', {
|
|
44
|
+
class: 'rounded-xl border border-dashed border-border bg-secondary/40 p-4 text-xs text-muted-fg',
|
|
45
|
+
}, 'Unable to render the provided spec.');
|
|
46
|
+
},
|
|
47
|
+
});
|
|
48
|
+
</script>
|
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
export const popoverProps = {
|
|
3
|
+
position: {
|
|
4
|
+
type: String,
|
|
5
|
+
default: 'bottom-start',
|
|
6
|
+
_edit: {
|
|
7
|
+
component: 'DomPositionInput',
|
|
8
|
+
props: {
|
|
9
|
+
label: 'Position',
|
|
10
|
+
},
|
|
11
|
+
description: 'Preferred side and alignment around the trigger.',
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
offset: {
|
|
15
|
+
type: Number,
|
|
16
|
+
default: 8,
|
|
17
|
+
_edit: { description: 'Gap in pixels between the trigger and the popover.' },
|
|
18
|
+
},
|
|
19
|
+
collisionPadding: {
|
|
20
|
+
type: Number,
|
|
21
|
+
default: 8,
|
|
22
|
+
_edit: { description: 'Viewport padding used when the panel flips or shifts.' },
|
|
23
|
+
},
|
|
24
|
+
floatingMode: {
|
|
25
|
+
type: String,
|
|
26
|
+
default: 'viewport',
|
|
27
|
+
_edit: { options: ['viewport', 'anchor'], description: 'viewport keeps the panel inside the browser; anchor keeps it attached while scrolling.' },
|
|
28
|
+
},
|
|
29
|
+
flip: {
|
|
30
|
+
type: Boolean,
|
|
31
|
+
default: true,
|
|
32
|
+
_edit: { description: 'Allow the popover to flip to the opposite side when it would collide with the viewport.' },
|
|
33
|
+
},
|
|
34
|
+
lockScroll: {
|
|
35
|
+
type: Boolean,
|
|
36
|
+
default: false,
|
|
37
|
+
_edit: { description: 'Lock browser scrolling while the popover is open.' },
|
|
38
|
+
},
|
|
39
|
+
trigger: {
|
|
40
|
+
type: String,
|
|
41
|
+
default: 'click',
|
|
42
|
+
_edit: { options: ['click', 'hover', 'hover-click'], description: 'How the trigger opens the popover. hover-click opens on hover and pins open on click.' },
|
|
43
|
+
},
|
|
44
|
+
hoverCloseDelay: {
|
|
45
|
+
type: Number,
|
|
46
|
+
default: 140,
|
|
47
|
+
_edit: { description: 'Delay in milliseconds before a hover-triggered popover closes after pointer or focus leaves.' },
|
|
48
|
+
},
|
|
49
|
+
arrow: {
|
|
50
|
+
type: Boolean,
|
|
51
|
+
default: true,
|
|
52
|
+
_edit: { description: 'Show a small arrow pointing back to the trigger.' },
|
|
53
|
+
},
|
|
54
|
+
width: {
|
|
55
|
+
type: String,
|
|
56
|
+
default: 'min-w-[14rem] max-w-[20rem]',
|
|
57
|
+
_edit: { description: 'Tailwind width utility(ies) for the panel.' },
|
|
58
|
+
},
|
|
59
|
+
padding: {
|
|
60
|
+
type: String,
|
|
61
|
+
default: 'p-4',
|
|
62
|
+
_edit: { description: 'Tailwind padding utility for the panel.' },
|
|
63
|
+
},
|
|
64
|
+
label: {
|
|
65
|
+
type: String,
|
|
66
|
+
default: 'More',
|
|
67
|
+
_edit: { description: 'Trigger button label (use the #trigger slot for richer content).' },
|
|
68
|
+
},
|
|
69
|
+
triggerId: {
|
|
70
|
+
type: String,
|
|
71
|
+
default: '',
|
|
72
|
+
_edit: { description: 'ID of an external trigger button. When set, DomPopover will not render its own trigger.' },
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
</script>
|
|
76
|
+
|
|
77
|
+
<script setup>
|
|
78
|
+
import { computed, onMounted, ref, useSlots } from 'vue';
|
|
79
|
+
|
|
80
|
+
defineOptions({
|
|
81
|
+
__doc: {
|
|
82
|
+
name: 'Popover',
|
|
83
|
+
tag: '<DomPopover>',
|
|
84
|
+
description: 'A floating panel anchored to a trigger. Built on the HTML Popover API so it sits in the top layer — no parent overflow, transform, or z-index can clip it.',
|
|
85
|
+
slots: [
|
|
86
|
+
{ name: 'trigger', description: 'Replaces the trigger button. Omit when using triggerId.' },
|
|
87
|
+
{ name: '(default)', payload: '{ open, close, toggle }', description: 'Popover body. Receives helpers so content can close itself without storing a component ref.' },
|
|
88
|
+
],
|
|
89
|
+
keyboard: [
|
|
90
|
+
{ keys: 'Click trigger', action: 'Toggles the popover.' },
|
|
91
|
+
{ keys: 'Focus trigger', action: 'Opens the popover when trigger is hover.' },
|
|
92
|
+
{ keys: 'Click outside', action: 'Light-dismiss via the native popover API.' },
|
|
93
|
+
{ keys: 'Esc', action: 'Closes the popover.' },
|
|
94
|
+
],
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const props = defineProps(popoverProps);
|
|
99
|
+
const emit = defineEmits(['open', 'close']);
|
|
100
|
+
|
|
101
|
+
const root = ref(null);
|
|
102
|
+
const slots = useSlots();
|
|
103
|
+
const hasTriggerSlot = computed(() => !!slots.trigger);
|
|
104
|
+
const positionMap = {
|
|
105
|
+
'start-bottom': { placement: 'left', align: 'end' },
|
|
106
|
+
start: { placement: 'left', align: 'center' },
|
|
107
|
+
'start-top': { placement: 'left', align: 'start' },
|
|
108
|
+
'top-start': { placement: 'top', align: 'start' },
|
|
109
|
+
top: { placement: 'top', align: 'center' },
|
|
110
|
+
'top-end': { placement: 'top', align: 'end' },
|
|
111
|
+
'end-top': { placement: 'right', align: 'start' },
|
|
112
|
+
end: { placement: 'right', align: 'center' },
|
|
113
|
+
'end-bottom': { placement: 'right', align: 'end' },
|
|
114
|
+
'bottom-end': { placement: 'bottom', align: 'end' },
|
|
115
|
+
bottom: { placement: 'bottom', align: 'center' },
|
|
116
|
+
'bottom-start': { placement: 'bottom', align: 'start' },
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const resolvedPosition = computed(() => {
|
|
120
|
+
return positionMap[props.position] || positionMap['bottom-start'];
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
function open() {
|
|
124
|
+
if (root.value) root.value.open = true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function close() {
|
|
128
|
+
if (root.value) root.value.open = false;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function toggle() {
|
|
132
|
+
root.value?.toggle?.();
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
defineExpose({
|
|
136
|
+
open,
|
|
137
|
+
close,
|
|
138
|
+
toggle,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
onMounted(async () => {
|
|
142
|
+
await import('../../lib/headless/popover.js');
|
|
143
|
+
root.value?.addEventListener('dom:open', () => emit('open'));
|
|
144
|
+
root.value?.addEventListener('dom:close', () => emit('close'));
|
|
145
|
+
});
|
|
146
|
+
</script>
|
|
147
|
+
|
|
148
|
+
<template>
|
|
149
|
+
<dom-popover
|
|
150
|
+
ref="root"
|
|
151
|
+
:align="resolvedPosition.align"
|
|
152
|
+
:offset="offset"
|
|
153
|
+
:placement="resolvedPosition.placement"
|
|
154
|
+
:collision-padding="collisionPadding"
|
|
155
|
+
:floating-mode="floatingMode"
|
|
156
|
+
:flip="flip ? null : 'false'"
|
|
157
|
+
:lock-scroll="lockScroll ? '' : null"
|
|
158
|
+
:trigger="trigger"
|
|
159
|
+
:hover-close-delay="hoverCloseDelay"
|
|
160
|
+
:data-trigger-id="triggerId || null"
|
|
161
|
+
:class="triggerId ? 'absolute' : 'relative inline-block'"
|
|
162
|
+
>
|
|
163
|
+
<span v-if="!triggerId && hasTriggerSlot" slot="trigger" data-trigger class="inline-block align-baseline"><slot name="trigger" /></span>
|
|
164
|
+
<button
|
|
165
|
+
v-else-if="!triggerId"
|
|
166
|
+
slot="trigger"
|
|
167
|
+
type="button"
|
|
168
|
+
class="skin-control inline-flex h-10 items-center gap-2 rounded-full px-4 text-sm font-medium transition hover:brightness-[0.98] focus:outline-none focus-visible:ring-2 focus-visible:ring-ring/60"
|
|
169
|
+
>
|
|
170
|
+
{{ label }}
|
|
171
|
+
</button>
|
|
172
|
+
<div
|
|
173
|
+
slot="panel"
|
|
174
|
+
class="dom-popover-panel dom-floating-transition outline-none rounded-lg"
|
|
175
|
+
:class="width"
|
|
176
|
+
>
|
|
177
|
+
<div
|
|
178
|
+
class="dom-popover-card skin-popover relative rounded-lg text-left text-sm"
|
|
179
|
+
:class="padding"
|
|
180
|
+
>
|
|
181
|
+
<svg v-if="arrow" class="dom-popover-arrow" aria-hidden="true" viewBox="0 0 16 8">
|
|
182
|
+
<path class="dom-popover-arrow-fill" d="M1 8L8 1L15 8Z" />
|
|
183
|
+
<path class="dom-popover-arrow-stroke" d="M1 8L8 1L15 8" />
|
|
184
|
+
</svg>
|
|
185
|
+
<div class="dom-popover-content relative z-10 whitespace-normal text-left">
|
|
186
|
+
<slot :open="open" :close="close" :toggle="toggle" />
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
</dom-popover>
|
|
191
|
+
</template>
|
|
192
|
+
|
|
193
|
+
<style>
|
|
194
|
+
.dom-popover-arrow {
|
|
195
|
+
position: absolute;
|
|
196
|
+
z-index: 0;
|
|
197
|
+
display: block;
|
|
198
|
+
width: 16px;
|
|
199
|
+
height: 8px;
|
|
200
|
+
overflow: visible;
|
|
201
|
+
fill: var(--skin-bg, var(--canvas));
|
|
202
|
+
stroke: var(--skin-border, var(--border));
|
|
203
|
+
stroke-width: 1;
|
|
204
|
+
stroke-linejoin: round;
|
|
205
|
+
pointer-events: none;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.dom-popover-arrow-fill {
|
|
209
|
+
fill: var(--skin-bg, var(--canvas));
|
|
210
|
+
stroke: none;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
.dom-popover-arrow-stroke {
|
|
214
|
+
fill: none;
|
|
215
|
+
stroke: var(--skin-border, var(--border));
|
|
216
|
+
stroke-width: 1;
|
|
217
|
+
stroke-linecap: round;
|
|
218
|
+
stroke-linejoin: round;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.dom-floating-transition[data-side="top"] > .dom-popover-card > .dom-popover-arrow {
|
|
222
|
+
bottom: -8px;
|
|
223
|
+
left: var(--dom-popover-arrow-x);
|
|
224
|
+
transform: translateX(-50%) rotate(180deg);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.dom-floating-transition[data-side="bottom"] > .dom-popover-card > .dom-popover-arrow {
|
|
228
|
+
top: -8px;
|
|
229
|
+
left: var(--dom-popover-arrow-x);
|
|
230
|
+
transform: translateX(-50%);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.dom-floating-transition[data-side="left"] > .dom-popover-card > .dom-popover-arrow {
|
|
234
|
+
right: -12px;
|
|
235
|
+
top: var(--dom-popover-arrow-y);
|
|
236
|
+
transform: translateY(-50%) rotate(90deg);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
.dom-floating-transition[data-side="right"] > .dom-popover-card > .dom-popover-arrow {
|
|
240
|
+
left: -12px;
|
|
241
|
+
top: var(--dom-popover-arrow-y);
|
|
242
|
+
transform: translateY(-50%) rotate(-90deg);
|
|
243
|
+
}
|
|
244
|
+
</style>
|