@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,271 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, onMounted, ref, useId, watch } from 'vue';
|
|
3
|
+
|
|
4
|
+
defineOptions({
|
|
5
|
+
__doc: {
|
|
6
|
+
name: 'Dialog',
|
|
7
|
+
tag: '<DomDialog>',
|
|
8
|
+
description: 'A modal built on the native HTML <dialog> element — top-layer rendering, native stacking, scroll lock, and backdrop styling without z-index hacks.',
|
|
9
|
+
slots: [
|
|
10
|
+
{ name: 'trigger', description: 'Optional. Element that opens the dialog when clicked — omit when using v-model, a template ref, or commandfor on an external button.' },
|
|
11
|
+
{ name: '(default)', description: 'Dialog body — rendered inside the card below the title and description.' },
|
|
12
|
+
{ name: 'header', payload: '{ title, description, titleId, descriptionId }', description: 'Replace the default title and description block. Apply the provided IDs to custom heading and description elements to preserve the dialog accessibility relationships.' },
|
|
13
|
+
{ name: 'footer', description: 'Action buttons. Add `data-close` on a control to dismiss the dialog.' },
|
|
14
|
+
],
|
|
15
|
+
events: [
|
|
16
|
+
{ name: 'update:modelValue', payload: '(open: boolean)', description: 'Emitted when the dialog opens or closes.' },
|
|
17
|
+
{ name: 'before-open', description: 'Emitted immediately before the dialog opens; prepare draft data or item snapshots here.' },
|
|
18
|
+
{ name: 'close', description: 'Emitted when the dialog is dismissed.' },
|
|
19
|
+
{ name: 'close-complete', description: 'Emitted after the close transition completes; clear retained dialog data here.' },
|
|
20
|
+
],
|
|
21
|
+
keyboard: [
|
|
22
|
+
{ keys: 'Esc', action: 'Closes the topmost open dialog unless `static` is set.' },
|
|
23
|
+
{ keys: 'Tab / Shift+Tab', action: 'Focus stays within the dialog while open (native top-layer).' },
|
|
24
|
+
{ keys: 'Click backdrop', action: 'Dismisses the dialog unless `static` is set.' },
|
|
25
|
+
],
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
const dialogWidthClasses = {
|
|
30
|
+
sm: 'w-[min(calc(100vw-2rem),24rem)]',
|
|
31
|
+
md: 'w-[min(calc(100vw-2rem),28rem)]',
|
|
32
|
+
lg: 'w-[min(calc(100vw-2rem),44rem)]',
|
|
33
|
+
xl: 'w-[min(calc(100vw-2rem),56rem)]',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const props = defineProps({
|
|
37
|
+
modelValue: {
|
|
38
|
+
type: Boolean,
|
|
39
|
+
default: false,
|
|
40
|
+
_edit: { description: 'Whether the dialog is open.' },
|
|
41
|
+
},
|
|
42
|
+
title: {
|
|
43
|
+
type: String,
|
|
44
|
+
default: '',
|
|
45
|
+
_edit: { description: 'Heading rendered inside the dialog card.' },
|
|
46
|
+
},
|
|
47
|
+
description: {
|
|
48
|
+
type: String,
|
|
49
|
+
default: '',
|
|
50
|
+
_edit: { description: 'Supporting copy under the heading.' },
|
|
51
|
+
},
|
|
52
|
+
size: {
|
|
53
|
+
type: String,
|
|
54
|
+
default: 'md',
|
|
55
|
+
validator: (value) => ['sm', 'md', 'lg', 'xl'].includes(value),
|
|
56
|
+
_edit: {
|
|
57
|
+
options: ['sm', 'md', 'lg', 'xl'],
|
|
58
|
+
description: 'Width preset for the dialog card.',
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
width: {
|
|
62
|
+
type: [String, Number],
|
|
63
|
+
default: '',
|
|
64
|
+
_edit: { description: 'Optional CSS width. Overrides the size preset when set, for example 95vw.' },
|
|
65
|
+
},
|
|
66
|
+
height: {
|
|
67
|
+
type: [String, Number],
|
|
68
|
+
default: '',
|
|
69
|
+
_edit: { description: 'Optional CSS height. Use values like 95vh for near-full-page dialogs.' },
|
|
70
|
+
},
|
|
71
|
+
static: {
|
|
72
|
+
type: Boolean,
|
|
73
|
+
default: false,
|
|
74
|
+
_edit: { description: 'Modal cannot be dismissed by backdrop click or Esc — use footer actions with data-close or v-model.' },
|
|
75
|
+
},
|
|
76
|
+
backdrop: {
|
|
77
|
+
type: Boolean,
|
|
78
|
+
default: true,
|
|
79
|
+
_edit: { description: 'Show the dimmed, blurred page backdrop behind the dialog.' },
|
|
80
|
+
},
|
|
81
|
+
footer: {
|
|
82
|
+
type: Boolean,
|
|
83
|
+
default: true,
|
|
84
|
+
_edit: { description: 'Show the footer region and default Close action when no footer slot is provided.' },
|
|
85
|
+
},
|
|
86
|
+
});
|
|
87
|
+
const emit = defineEmits(['update:modelValue', 'before-open', 'close', 'close-complete']);
|
|
88
|
+
|
|
89
|
+
const root = ref(null);
|
|
90
|
+
const uniqueId = useId();
|
|
91
|
+
const titleId = computed(() => (props.title ? `dom-dialog-${uniqueId}-title` : ''));
|
|
92
|
+
const descriptionId = computed(() => (props.description ? `dom-dialog-${uniqueId}-description` : ''));
|
|
93
|
+
const dialogWidthClass = computed(() => dialogWidthClasses[props.size] || dialogWidthClasses.md);
|
|
94
|
+
const dialogPanelStyle = computed(() => {
|
|
95
|
+
const width = normalizeDialogDimension(props.width);
|
|
96
|
+
const height = normalizeDialogDimension(props.height);
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
width: width || undefined,
|
|
100
|
+
height: height || undefined,
|
|
101
|
+
maxHeight: height || undefined,
|
|
102
|
+
};
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Normalize a CSS dimension prop into an inline style value.
|
|
107
|
+
*
|
|
108
|
+
* @param {string|number} value CSS size string or numeric pixel value.
|
|
109
|
+
* @returns {string} CSS size value, with numbers converted to pixels.
|
|
110
|
+
*/
|
|
111
|
+
function normalizeDialogDimension(value) {
|
|
112
|
+
if (typeof value === 'number' && Number.isFinite(value)) return `${value}px`;
|
|
113
|
+
if (typeof value === 'string') return value.trim();
|
|
114
|
+
return '';
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Synchronize the headless dialog host with the controlled Vue model.
|
|
119
|
+
*
|
|
120
|
+
* @param {boolean} v Whether the dialog should be open.
|
|
121
|
+
* @returns {void}
|
|
122
|
+
*/
|
|
123
|
+
function setOpen(v) {
|
|
124
|
+
if (!root.value) return;
|
|
125
|
+
if (v) root.value.open();
|
|
126
|
+
else root.value.close();
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Forward only the root dialog's before-open event to Vue consumers.
|
|
131
|
+
*
|
|
132
|
+
* @param {CustomEvent} event Headless before-open event.
|
|
133
|
+
* @returns {void}
|
|
134
|
+
*/
|
|
135
|
+
function onHeadlessBeforeOpen(event) {
|
|
136
|
+
if (event.target !== root.value) return;
|
|
137
|
+
emit('before-open');
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Forward only the root dialog's open event to Vue consumers.
|
|
142
|
+
*
|
|
143
|
+
* @param {CustomEvent} event Headless open event.
|
|
144
|
+
* @returns {void}
|
|
145
|
+
*/
|
|
146
|
+
function onHeadlessOpen(event) {
|
|
147
|
+
if (event.target !== root.value) return;
|
|
148
|
+
emit('update:modelValue', true);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Forward only the root dialog's close event to Vue consumers.
|
|
153
|
+
*
|
|
154
|
+
* @param {CustomEvent} event Headless close event.
|
|
155
|
+
* @returns {void}
|
|
156
|
+
*/
|
|
157
|
+
function onHeadlessClose(event) {
|
|
158
|
+
if (event.target !== root.value) return;
|
|
159
|
+
emit('update:modelValue', false);
|
|
160
|
+
emit('close');
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Forward only the root dialog's close-complete event to Vue consumers.
|
|
165
|
+
*
|
|
166
|
+
* @param {CustomEvent} event Headless close-complete event.
|
|
167
|
+
* @returns {void}
|
|
168
|
+
*/
|
|
169
|
+
function onHeadlessCloseComplete(event) {
|
|
170
|
+
if (event.target !== root.value) return;
|
|
171
|
+
emit('close-complete');
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
onMounted(async () => {
|
|
175
|
+
await import('../../lib/headless/dialog.js');
|
|
176
|
+
root.value?.addEventListener('dom:before-open', onHeadlessBeforeOpen);
|
|
177
|
+
root.value?.addEventListener('dom:open', onHeadlessOpen);
|
|
178
|
+
root.value?.addEventListener('dom:close', onHeadlessClose);
|
|
179
|
+
root.value?.addEventListener('dom:close-complete', onHeadlessCloseComplete);
|
|
180
|
+
if (props.modelValue) setOpen(true);
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
watch(() => props.modelValue, setOpen);
|
|
184
|
+
|
|
185
|
+
defineExpose({
|
|
186
|
+
open: () => setOpen(true),
|
|
187
|
+
close: () => setOpen(false),
|
|
188
|
+
toggle: () => root.value?.toggle(),
|
|
189
|
+
get element() { return root.value; },
|
|
190
|
+
});
|
|
191
|
+
</script>
|
|
192
|
+
|
|
193
|
+
<template>
|
|
194
|
+
<!--
|
|
195
|
+
<dom-dialog> owns the native <dialog> internally (shadow DOM). We
|
|
196
|
+
just hand it a trigger slot and the dialog body — no <dialog> tag in
|
|
197
|
+
Vue templates.
|
|
198
|
+
-->
|
|
199
|
+
<dom-dialog
|
|
200
|
+
ref="root"
|
|
201
|
+
class="dom-dialog"
|
|
202
|
+
:static="static || null"
|
|
203
|
+
:no-backdrop="backdrop ? null : ''"
|
|
204
|
+
:aria-labelledby="titleId || null"
|
|
205
|
+
:aria-describedby="descriptionId || null"
|
|
206
|
+
>
|
|
207
|
+
<!-- Vue does not forward slot= onto component roots; anchor trigger for dom-dialog -->
|
|
208
|
+
<span slot="trigger" class="contents">
|
|
209
|
+
<slot name="trigger" />
|
|
210
|
+
</span>
|
|
211
|
+
<div
|
|
212
|
+
class="skin-overlay flex max-h-[calc(100dvh-2rem)] flex-col overflow-hidden rounded-2xl p-6 outline-none"
|
|
213
|
+
:class="dialogWidthClass"
|
|
214
|
+
:style="dialogPanelStyle"
|
|
215
|
+
>
|
|
216
|
+
<slot
|
|
217
|
+
name="header"
|
|
218
|
+
:title="title"
|
|
219
|
+
:description="description"
|
|
220
|
+
:title-id="titleId"
|
|
221
|
+
:description-id="descriptionId"
|
|
222
|
+
>
|
|
223
|
+
<div v-if="title || description" class="shrink-0 space-y-2">
|
|
224
|
+
<h2 v-if="title" :id="titleId" class="text-lg font-semibold tracking-tight">{{ title }}</h2>
|
|
225
|
+
<p v-if="description" :id="descriptionId" class="text-sm text-muted-fg">{{ description }}</p>
|
|
226
|
+
</div>
|
|
227
|
+
</slot>
|
|
228
|
+
<div class="-mx-1 -mb-1 min-h-0 overflow-y-auto px-1 pb-1" :class="($slots.header || title || description) ? 'mt-3 pt-1' : '-mt-1 pt-1'">
|
|
229
|
+
<slot />
|
|
230
|
+
</div>
|
|
231
|
+
<div v-if="footer || $slots.footer" class="mt-6 flex shrink-0 items-center justify-end gap-2">
|
|
232
|
+
<slot name="footer">
|
|
233
|
+
<button
|
|
234
|
+
data-close
|
|
235
|
+
type="button"
|
|
236
|
+
class="skin-action inline-flex h-9 items-center rounded-full px-4 text-sm font-medium hover:opacity-90"
|
|
237
|
+
>Close</button>
|
|
238
|
+
</slot>
|
|
239
|
+
</div>
|
|
240
|
+
</div>
|
|
241
|
+
</dom-dialog>
|
|
242
|
+
</template>
|
|
243
|
+
|
|
244
|
+
<style scoped>
|
|
245
|
+
.dom-dialog::part(dialog) {
|
|
246
|
+
opacity: 0;
|
|
247
|
+
transform: translateY(10px) scale(0.96);
|
|
248
|
+
transform-origin: center;
|
|
249
|
+
filter: blur(1px);
|
|
250
|
+
transition:
|
|
251
|
+
opacity 180ms ease,
|
|
252
|
+
transform 240ms cubic-bezier(0.16, 1, 0.3, 1),
|
|
253
|
+
filter 180ms ease,
|
|
254
|
+
overlay 240ms allow-discrete,
|
|
255
|
+
display 240ms allow-discrete;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
.dom-dialog[open]::part(dialog) {
|
|
259
|
+
opacity: 1;
|
|
260
|
+
transform: none;
|
|
261
|
+
filter: blur(0);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
@starting-style {
|
|
265
|
+
.dom-dialog[open]::part(dialog) {
|
|
266
|
+
opacity: 0;
|
|
267
|
+
transform: translateY(10px) scale(0.96);
|
|
268
|
+
filter: blur(1px);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
</style>
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { ref, watch } from 'vue';
|
|
3
|
+
import DomButton from '../button/DomButton.vue';
|
|
4
|
+
import DomForm from '../../forms/form/DomForm.vue';
|
|
5
|
+
|
|
6
|
+
defineOptions({
|
|
7
|
+
__doc: {
|
|
8
|
+
name: 'Dialog schema form',
|
|
9
|
+
tag: '<DomDialogSchemaForm>',
|
|
10
|
+
description: 'Internal renderer used by dialogForm(schema) to resolve a DomForm children schema from a programmatic dialog.',
|
|
11
|
+
events: [
|
|
12
|
+
{ name: 'resolve', payload: 'Record<string, unknown>', description: 'Emitted with validated form values.' },
|
|
13
|
+
{ name: 'dismiss', description: 'Emitted when the form is cancelled.' },
|
|
14
|
+
],
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const props = defineProps({
|
|
19
|
+
schema: {
|
|
20
|
+
type: Array,
|
|
21
|
+
default: () => [],
|
|
22
|
+
},
|
|
23
|
+
modelValue: {
|
|
24
|
+
type: Object,
|
|
25
|
+
default: () => ({}),
|
|
26
|
+
},
|
|
27
|
+
submitText: {
|
|
28
|
+
type: String,
|
|
29
|
+
default: 'Submit',
|
|
30
|
+
},
|
|
31
|
+
cancelText: {
|
|
32
|
+
type: String,
|
|
33
|
+
default: 'Cancel',
|
|
34
|
+
},
|
|
35
|
+
invalidMessage: {
|
|
36
|
+
type: String,
|
|
37
|
+
default: 'Complete the required fields before continuing.',
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
const emit = defineEmits(['resolve', 'dismiss']);
|
|
41
|
+
const values = ref({ ...props.modelValue });
|
|
42
|
+
const formMessage = ref('');
|
|
43
|
+
|
|
44
|
+
watch(() => props.modelValue, (nextValues) => {
|
|
45
|
+
values.value = { ...(nextValues || {}) };
|
|
46
|
+
}, { deep: true });
|
|
47
|
+
|
|
48
|
+
function submit({ values: submittedValues }) {
|
|
49
|
+
emit('resolve', submittedValues);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function invalid() {
|
|
53
|
+
formMessage.value = props.invalidMessage;
|
|
54
|
+
}
|
|
55
|
+
</script>
|
|
56
|
+
|
|
57
|
+
<template>
|
|
58
|
+
<DomForm
|
|
59
|
+
v-model="values"
|
|
60
|
+
:children="schema"
|
|
61
|
+
class="space-y-4"
|
|
62
|
+
@change="formMessage = ''"
|
|
63
|
+
@invalid="invalid"
|
|
64
|
+
@submit="submit"
|
|
65
|
+
>
|
|
66
|
+
<p v-if="formMessage" class="text-xs text-destructive">{{ formMessage }}</p>
|
|
67
|
+
<div class="flex items-center justify-end gap-2">
|
|
68
|
+
<DomButton variant="secondary" type="button" @click="emit('dismiss')">{{ cancelText }}</DomButton>
|
|
69
|
+
<DomButton type="submit">{{ submitText }}</DomButton>
|
|
70
|
+
</div>
|
|
71
|
+
</DomForm>
|
|
72
|
+
</template>
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { onBeforeUnmount, ref, watch } from 'vue';
|
|
3
|
+
import DomButton from '../button/DomButton.vue';
|
|
4
|
+
import DomDialog from './DomDialog.vue';
|
|
5
|
+
import DomDialogSchemaForm from './DomDialogSchemaForm.vue';
|
|
6
|
+
|
|
7
|
+
const DIALOG_REMOVAL_DELAY_MS = 320;
|
|
8
|
+
|
|
9
|
+
defineOptions({
|
|
10
|
+
__doc: {
|
|
11
|
+
name: 'Dialog stack',
|
|
12
|
+
tag: '<DomDialogStack>',
|
|
13
|
+
description: 'Renderer for dialogs created with useDialogs(). Mount it once, then call the dialog API from script.',
|
|
14
|
+
slots: [
|
|
15
|
+
{ name: 'default', payload: '{ dialog, resolve, dismiss }', description: 'Custom renderer for a programmatic dialog body.' },
|
|
16
|
+
{ name: 'footer', payload: '{ dialog, resolve, dismiss }', description: 'Custom renderer for programmatic dialog actions.' },
|
|
17
|
+
],
|
|
18
|
+
events: [
|
|
19
|
+
{ name: 'resolve', payload: '({ id, value })', description: 'Emitted when a dialog resolves.' },
|
|
20
|
+
{ name: 'dismiss', payload: '({ id, value })', description: 'Emitted when a dialog is dismissed.' },
|
|
21
|
+
{ name: 'action', payload: '({ id, dialog, ...payload })', description: 'Emitted by custom dialog bodies for app-specific actions.' },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
const props = defineProps({
|
|
27
|
+
dialogs: {
|
|
28
|
+
type: Array,
|
|
29
|
+
default: () => [],
|
|
30
|
+
_edit: {
|
|
31
|
+
component: 'DomJsonListInput',
|
|
32
|
+
description: 'Visible programmatic dialogs from useDialogs().',
|
|
33
|
+
props: {
|
|
34
|
+
addLabel: '+ Add dialog',
|
|
35
|
+
compact: true,
|
|
36
|
+
schema: [
|
|
37
|
+
{ key: 'id', label: 'ID', placeholder: 'dialog-id', default: (index) => `dialog-${index + 1}` },
|
|
38
|
+
{ key: 'title', label: 'Title', placeholder: 'Delete project?' },
|
|
39
|
+
{ key: 'description', label: 'Description', placeholder: 'This action cannot be undone.' },
|
|
40
|
+
{ key: 'message', label: 'Message', placeholder: 'Are you sure?' },
|
|
41
|
+
{ key: 'type', label: 'Type', placeholder: 'dialog | confirm', default: 'dialog' },
|
|
42
|
+
],
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
});
|
|
47
|
+
const emit = defineEmits(['resolve', 'dismiss', 'action']);
|
|
48
|
+
const renderedDialogs = ref([]);
|
|
49
|
+
const removalTimers = new Map();
|
|
50
|
+
|
|
51
|
+
watch(
|
|
52
|
+
() => props.dialogs,
|
|
53
|
+
(dialogs) => syncRenderedDialogs(dialogs),
|
|
54
|
+
{ immediate: true, deep: true },
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
onBeforeUnmount(() => {
|
|
58
|
+
if (typeof window !== 'undefined') {
|
|
59
|
+
for (const timer of removalTimers.values()) window.clearTimeout(timer);
|
|
60
|
+
}
|
|
61
|
+
removalTimers.clear();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Resolve a dialog, notify the owner immediately, and let the mounted item animate out.
|
|
66
|
+
*
|
|
67
|
+
* @param {{ id: string, dialog: Record<string, unknown> }} item Rendered stack item.
|
|
68
|
+
* @param {unknown} [value] Value passed to the dialog resolver.
|
|
69
|
+
* @returns {void}
|
|
70
|
+
*/
|
|
71
|
+
function resolve(item, value = true) {
|
|
72
|
+
if (item.closing) return;
|
|
73
|
+
emit('resolve', { id: item.id, value });
|
|
74
|
+
closeDialogItem(item.id);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Dismiss a dialog, notify the owner immediately, and let the mounted item animate out.
|
|
79
|
+
*
|
|
80
|
+
* @param {{ id: string, dialog: Record<string, unknown> }} item Rendered stack item.
|
|
81
|
+
* @param {unknown} [value] Value passed to the dialog resolver.
|
|
82
|
+
* @returns {void}
|
|
83
|
+
*/
|
|
84
|
+
function dismiss(item, value = false) {
|
|
85
|
+
if (item.closing) return;
|
|
86
|
+
emit('dismiss', { id: item.id, value });
|
|
87
|
+
closeDialogItem(item.id);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Route custom body actions through the standard resolve and dismiss lifecycle.
|
|
92
|
+
*
|
|
93
|
+
* @param {{ id: string, dialog: Record<string, unknown> }} item Rendered stack item.
|
|
94
|
+
* @param {unknown} payload Custom action payload from a dialog body.
|
|
95
|
+
* @returns {void}
|
|
96
|
+
*/
|
|
97
|
+
function onDialogAction(item, payload) {
|
|
98
|
+
const detail = {
|
|
99
|
+
id: item.id,
|
|
100
|
+
dialog: item.dialog,
|
|
101
|
+
...(payload && typeof payload === 'object' ? payload : { action: payload }),
|
|
102
|
+
};
|
|
103
|
+
if (detail.action === 'resolve') resolve(item, detail.value);
|
|
104
|
+
else if (detail.action === 'dismiss') dismiss(item, detail.value);
|
|
105
|
+
emit('action', detail);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Keep the local rendered list aligned with caller-owned dialogs without skipping leave effects.
|
|
110
|
+
*
|
|
111
|
+
* @param {Array<Record<string, unknown> & { id: string }>} dialogs Caller-owned dialog records.
|
|
112
|
+
* @returns {void}
|
|
113
|
+
*/
|
|
114
|
+
function syncRenderedDialogs(dialogs) {
|
|
115
|
+
const incomingIds = new Set();
|
|
116
|
+
|
|
117
|
+
for (const dialog of dialogs) {
|
|
118
|
+
incomingIds.add(dialog.id);
|
|
119
|
+
const item = renderedDialogs.value.find((entry) => entry.id === dialog.id);
|
|
120
|
+
|
|
121
|
+
if (item) {
|
|
122
|
+
item.dialog = dialog;
|
|
123
|
+
if (item.closing) reopenDialogItem(item);
|
|
124
|
+
} else {
|
|
125
|
+
renderedDialogs.value.push({
|
|
126
|
+
id: dialog.id,
|
|
127
|
+
dialog,
|
|
128
|
+
open: true,
|
|
129
|
+
closing: false,
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
for (const item of renderedDialogs.value) {
|
|
135
|
+
if (!incomingIds.has(item.id)) closeDialogItem(item.id);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Re-open an item if the owner restores the same dialog id before removal completes.
|
|
141
|
+
*
|
|
142
|
+
* @param {{ id: string, open: boolean, closing: boolean }} item Rendered stack item.
|
|
143
|
+
* @returns {void}
|
|
144
|
+
*/
|
|
145
|
+
function reopenDialogItem(item) {
|
|
146
|
+
clearRemovalTimer(item.id);
|
|
147
|
+
item.open = true;
|
|
148
|
+
item.closing = false;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Start the close transition for a rendered dialog and schedule its final removal.
|
|
153
|
+
*
|
|
154
|
+
* @param {string} id Dialog id to close.
|
|
155
|
+
* @returns {void}
|
|
156
|
+
*/
|
|
157
|
+
function closeDialogItem(id) {
|
|
158
|
+
const item = renderedDialogs.value.find((entry) => entry.id === id);
|
|
159
|
+
if (!item || item.closing) return;
|
|
160
|
+
item.closing = true;
|
|
161
|
+
item.open = false;
|
|
162
|
+
scheduleDialogRemoval(id);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Remove a dialog after the component's close transition has had time to finish.
|
|
167
|
+
*
|
|
168
|
+
* @param {string} id Dialog id to remove.
|
|
169
|
+
* @returns {void}
|
|
170
|
+
*/
|
|
171
|
+
function scheduleDialogRemoval(id) {
|
|
172
|
+
clearRemovalTimer(id);
|
|
173
|
+
if (typeof window === 'undefined') {
|
|
174
|
+
removeDialogItem(id);
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
removalTimers.set(id, window.setTimeout(() => removeDialogItem(id), DIALOG_REMOVAL_DELAY_MS));
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Cancel a pending removal timer for a rendered dialog.
|
|
182
|
+
*
|
|
183
|
+
* @param {string} id Dialog id associated with the timer.
|
|
184
|
+
* @returns {void}
|
|
185
|
+
*/
|
|
186
|
+
function clearRemovalTimer(id) {
|
|
187
|
+
const timer = removalTimers.get(id);
|
|
188
|
+
if (!timer || typeof window === 'undefined') return;
|
|
189
|
+
window.clearTimeout(timer);
|
|
190
|
+
removalTimers.delete(id);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Remove a dialog from the local rendered list once its leave transition is complete.
|
|
195
|
+
*
|
|
196
|
+
* @param {string} id Dialog id to remove.
|
|
197
|
+
* @returns {void}
|
|
198
|
+
*/
|
|
199
|
+
function removeDialogItem(id) {
|
|
200
|
+
clearRemovalTimer(id);
|
|
201
|
+
renderedDialogs.value = renderedDialogs.value.filter((item) => item.id !== id);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/**
|
|
205
|
+
* Convert a native dialog close into a dismiss event unless this stack already initiated it.
|
|
206
|
+
*
|
|
207
|
+
* @param {{ id: string, closing: boolean }} item Rendered stack item.
|
|
208
|
+
* @returns {void}
|
|
209
|
+
*/
|
|
210
|
+
function onDialogClose(item) {
|
|
211
|
+
if (item.closing) return;
|
|
212
|
+
dismiss(item);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* Resolve the button variant for a confirmation action.
|
|
217
|
+
*
|
|
218
|
+
* @param {Record<string, unknown>} dialog Dialog options.
|
|
219
|
+
* @returns {string}
|
|
220
|
+
*/
|
|
221
|
+
function confirmVariant(dialog) {
|
|
222
|
+
return dialog.tone === 'danger' ? 'danger' : 'primary';
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Resolve the custom component body, if one was supplied.
|
|
227
|
+
*
|
|
228
|
+
* @param {Record<string, unknown>} dialog Dialog options.
|
|
229
|
+
* @returns {unknown}
|
|
230
|
+
*/
|
|
231
|
+
function dialogComponent(dialog) {
|
|
232
|
+
return dialog.component || null;
|
|
233
|
+
}
|
|
234
|
+
</script>
|
|
235
|
+
|
|
236
|
+
<template>
|
|
237
|
+
<DomDialog
|
|
238
|
+
v-for="item in renderedDialogs"
|
|
239
|
+
:key="item.id"
|
|
240
|
+
:model-value="item.open"
|
|
241
|
+
:title="item.dialog.title || ''"
|
|
242
|
+
:description="item.dialog.description || ''"
|
|
243
|
+
:static="item.dialog.static || null"
|
|
244
|
+
:backdrop="item.dialog.backdrop !== false"
|
|
245
|
+
:footer="item.dialog.footer !== false"
|
|
246
|
+
@close="onDialogClose(item)"
|
|
247
|
+
@close-complete="removeDialogItem(item.id)"
|
|
248
|
+
>
|
|
249
|
+
<slot :dialog="item.dialog" :resolve="(value = true) => resolve(item, value)" :dismiss="(value = false) => dismiss(item, value)">
|
|
250
|
+
<component
|
|
251
|
+
:is="dialogComponent(item.dialog)"
|
|
252
|
+
v-if="dialogComponent(item.dialog)"
|
|
253
|
+
v-bind="item.dialog.props || {}"
|
|
254
|
+
@action="onDialogAction(item, $event)"
|
|
255
|
+
@resolve="resolve(item, $event)"
|
|
256
|
+
@dismiss="dismiss(item, $event)"
|
|
257
|
+
@close="dismiss(item)"
|
|
258
|
+
/>
|
|
259
|
+
<DomDialogSchemaForm
|
|
260
|
+
v-else-if="item.dialog.formSchema"
|
|
261
|
+
:schema="item.dialog.formSchema"
|
|
262
|
+
:model-value="item.dialog.formValues || {}"
|
|
263
|
+
:submit-text="item.dialog.confirmText || 'Submit'"
|
|
264
|
+
:cancel-text="item.dialog.cancelText || 'Cancel'"
|
|
265
|
+
:invalid-message="item.dialog.invalidMessage || undefined"
|
|
266
|
+
@resolve="resolve(item, $event)"
|
|
267
|
+
@dismiss="dismiss(item, $event)"
|
|
268
|
+
/>
|
|
269
|
+
<p v-else-if="item.dialog.message" class="text-sm text-muted-fg">
|
|
270
|
+
{{ item.dialog.message }}
|
|
271
|
+
</p>
|
|
272
|
+
</slot>
|
|
273
|
+
|
|
274
|
+
<template v-if="item.dialog.footer !== false" #footer>
|
|
275
|
+
<slot name="footer" :dialog="item.dialog" :resolve="(value = true) => resolve(item, value)" :dismiss="(value = false) => dismiss(item, value)">
|
|
276
|
+
<DomButton
|
|
277
|
+
v-if="item.dialog.showCancel || item.dialog.type === 'confirm'"
|
|
278
|
+
variant="secondary"
|
|
279
|
+
@click="dismiss(item)"
|
|
280
|
+
>
|
|
281
|
+
{{ item.dialog.cancelText || 'Cancel' }}
|
|
282
|
+
</DomButton>
|
|
283
|
+
<DomButton :variant="confirmVariant(item.dialog)" @click="resolve(item)">
|
|
284
|
+
{{ item.dialog.confirmText || 'OK' }}
|
|
285
|
+
</DomButton>
|
|
286
|
+
</slot>
|
|
287
|
+
</template>
|
|
288
|
+
</DomDialog>
|
|
289
|
+
</template>
|