@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,768 @@
|
|
|
1
|
+
<script setup>
|
|
2
|
+
import { computed, onBeforeUnmount, onMounted, ref, watch } from 'vue';
|
|
3
|
+
import DomToastItem from './DomToastItem.vue';
|
|
4
|
+
|
|
5
|
+
defineOptions({
|
|
6
|
+
__doc: {
|
|
7
|
+
name: 'Toast stack',
|
|
8
|
+
tag: '<DomToastStack>',
|
|
9
|
+
description: 'A polite live-region stack for transient notifications.',
|
|
10
|
+
slots: [
|
|
11
|
+
{ name: 'default', payload: '{ toast, dismiss }', description: 'Custom toast content rendered inside the card. Use this for avatars, actions, or richer markup.' },
|
|
12
|
+
],
|
|
13
|
+
events: [
|
|
14
|
+
{ name: 'dismiss', payload: '(id: string)', description: 'Emitted when a toast is dismissed.' },
|
|
15
|
+
{ name: 'action', payload: '({ id, toast, ...payload })', description: 'Emitted by toast template content, useful for buttons and menus inside a toast.' },
|
|
16
|
+
],
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const props = defineProps({
|
|
21
|
+
toasts: {
|
|
22
|
+
type: Array,
|
|
23
|
+
default: () => [],
|
|
24
|
+
_edit: {
|
|
25
|
+
component: 'DomJsonListInput',
|
|
26
|
+
description: 'Visible toasts. Set duration to 0 for a toast that requires manual dismissal.',
|
|
27
|
+
props: {
|
|
28
|
+
addLabel: '+ Add toast',
|
|
29
|
+
compact: true,
|
|
30
|
+
schema: [
|
|
31
|
+
{ key: 'id', label: 'ID', placeholder: 'toast-id', default: (index) => `toast-${index + 1}` },
|
|
32
|
+
{ key: 'title', label: 'Title', placeholder: 'Successfully saved' },
|
|
33
|
+
{ key: 'description', label: 'Description', placeholder: 'Your changes have been stored.' },
|
|
34
|
+
{ key: 'tone', label: 'Tone', placeholder: 'default | success | danger | warning', default: 'default' },
|
|
35
|
+
{ key: 'duration', label: 'Duration', type: 'number', default: 3600 },
|
|
36
|
+
],
|
|
37
|
+
},
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
position: {
|
|
41
|
+
type: String,
|
|
42
|
+
default: 'top-right',
|
|
43
|
+
_edit: { options: ['top-right', 'top-center', 'top-left', 'bottom-right', 'bottom-center', 'bottom-left'], description: 'Viewport placement.' },
|
|
44
|
+
},
|
|
45
|
+
layout: {
|
|
46
|
+
type: String,
|
|
47
|
+
default: 'stack',
|
|
48
|
+
_edit: { options: ['stack', 'rack'], description: 'Visual layout. Use rack to place the newest toast in front while older toasts recede behind it.' },
|
|
49
|
+
},
|
|
50
|
+
duration: {
|
|
51
|
+
type: Number,
|
|
52
|
+
default: 3600,
|
|
53
|
+
_edit: { description: 'Default auto-dismiss delay in milliseconds. Set to 0 to keep toasts until manually dismissed.' },
|
|
54
|
+
},
|
|
55
|
+
autoDismiss: {
|
|
56
|
+
type: Boolean,
|
|
57
|
+
default: true,
|
|
58
|
+
_edit: { description: 'Automatically emit dismiss after each toast duration.' },
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
const emit = defineEmits(['dismiss', 'action']);
|
|
62
|
+
const renderedToasts = ref([]);
|
|
63
|
+
const rackExpanded = ref(false);
|
|
64
|
+
const rackPointerInside = ref(false);
|
|
65
|
+
const rackFocusInside = ref(false);
|
|
66
|
+
const isMounted = ref(false);
|
|
67
|
+
const timers = new Map();
|
|
68
|
+
const motionTimers = new Map();
|
|
69
|
+
const rackMotionTimers = new Map();
|
|
70
|
+
let rackCollapseTimer;
|
|
71
|
+
const isRackLayout = computed(isRackLayoutSelected);
|
|
72
|
+
const isBottomPosition = computed(isBottomPlacement);
|
|
73
|
+
const displayedToasts = computed(buildDisplayedToasts);
|
|
74
|
+
|
|
75
|
+
onMounted(registerToastRegion);
|
|
76
|
+
watch(getPropToasts, syncTimers, { immediate: true, deep: true });
|
|
77
|
+
watch(getPropToasts, syncRendered, { immediate: true, deep: true });
|
|
78
|
+
onBeforeUnmount(clearToastTimers);
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Registers the headless toast region custom element after the client app mounts.
|
|
82
|
+
*
|
|
83
|
+
* @returns {Promise<void>}
|
|
84
|
+
*/
|
|
85
|
+
async function registerToastRegion() {
|
|
86
|
+
isMounted.value = true;
|
|
87
|
+
await import('../../lib/headless/toast.js');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Clears any pending auto-dismiss and motion timers owned by this stack.
|
|
92
|
+
*
|
|
93
|
+
* @returns {void}
|
|
94
|
+
*/
|
|
95
|
+
function clearToastTimers() {
|
|
96
|
+
for (const timer of timers.values()) clearClientTimeout(timer);
|
|
97
|
+
for (const timer of motionTimers.values()) clearClientTimeout(timer);
|
|
98
|
+
for (const timer of rackMotionTimers.values()) clearClientTimeout(timer);
|
|
99
|
+
clearRackCollapseTimer();
|
|
100
|
+
timers.clear();
|
|
101
|
+
motionTimers.clear();
|
|
102
|
+
rackMotionTimers.clear();
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Reads the controlled toast list for watchers without creating inline watch callbacks.
|
|
107
|
+
*
|
|
108
|
+
* @returns {Array}
|
|
109
|
+
*/
|
|
110
|
+
function getPropToasts() {
|
|
111
|
+
return props.toasts;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Reports whether the rack visual treatment is currently selected.
|
|
116
|
+
*
|
|
117
|
+
* @returns {boolean}
|
|
118
|
+
*/
|
|
119
|
+
function isRackLayoutSelected() {
|
|
120
|
+
return props.layout === 'rack';
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Reports whether the current viewport position anchors the toast stack to the bottom edge.
|
|
125
|
+
*
|
|
126
|
+
* @returns {boolean}
|
|
127
|
+
*/
|
|
128
|
+
function isBottomPlacement() {
|
|
129
|
+
return props.position.startsWith('bottom');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Builds the render order and rack depth metadata for the current layout.
|
|
134
|
+
*
|
|
135
|
+
* @returns {Array<{toast: object, rackIndex: number, isRackFront: boolean}>}
|
|
136
|
+
*/
|
|
137
|
+
function buildDisplayedToasts() {
|
|
138
|
+
const toasts = isRackLayout.value ? [...renderedToasts.value].reverse() : renderedToasts.value;
|
|
139
|
+
return toasts.map((toast, index) => ({
|
|
140
|
+
toast,
|
|
141
|
+
rackIndex: isRackLayout.value ? index : 0,
|
|
142
|
+
isRackFront: !isRackLayout.value || index === 0,
|
|
143
|
+
}));
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Emits a dismiss event for a toast id and clears its pending auto-dismiss timer.
|
|
148
|
+
*
|
|
149
|
+
* @param {string} id Toast id to dismiss.
|
|
150
|
+
* @returns {void}
|
|
151
|
+
*/
|
|
152
|
+
function dismiss(id) {
|
|
153
|
+
clearClientTimeout(timers.get(id));
|
|
154
|
+
timers.delete(id);
|
|
155
|
+
holdRackOpenAfterDismiss();
|
|
156
|
+
emit('dismiss', id);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Synchronizes auto-dismiss timers with the controlled toast array.
|
|
161
|
+
*
|
|
162
|
+
* Auto-dismiss timers live in the stack so callers only need to update the
|
|
163
|
+
* controlled toast array when dismiss is emitted.
|
|
164
|
+
*
|
|
165
|
+
* @param {Array} toasts Controlled toast records.
|
|
166
|
+
* @returns {void}
|
|
167
|
+
*/
|
|
168
|
+
function syncTimers(toasts) {
|
|
169
|
+
const ids = new Set(toasts.map((toast) => toast.id));
|
|
170
|
+
for (const [id, timer] of timers) {
|
|
171
|
+
if (!ids.has(id)) {
|
|
172
|
+
clearClientTimeout(timer);
|
|
173
|
+
timers.delete(id);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (!props.autoDismiss || typeof window === 'undefined') return;
|
|
178
|
+
for (const toast of toasts) {
|
|
179
|
+
if (!toast.id || timers.has(toast.id)) continue;
|
|
180
|
+
const delay = Number(toast.duration ?? props.duration);
|
|
181
|
+
if (delay <= 0) continue;
|
|
182
|
+
timers.set(toast.id, window.setTimeout(() => dismiss(toast.id), delay));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Synchronizes animated toast rows with the controlled toast array.
|
|
188
|
+
*
|
|
189
|
+
* Leaving toasts stay in a local render list long enough for opacity and
|
|
190
|
+
* height transitions to finish before removal from the DOM.
|
|
191
|
+
*
|
|
192
|
+
* @param {Array} toasts Controlled toast records.
|
|
193
|
+
* @returns {void}
|
|
194
|
+
*/
|
|
195
|
+
function syncRendered(toasts) {
|
|
196
|
+
const previousRackFrontId = getRackFrontId();
|
|
197
|
+
const nextIds = new Set(toasts.map((toast) => toast.id));
|
|
198
|
+
const current = new Map(renderedToasts.value.map((toast) => [toast.id, toast]));
|
|
199
|
+
|
|
200
|
+
for (const toast of toasts) {
|
|
201
|
+
const existing = current.get(toast.id);
|
|
202
|
+
if (existing) {
|
|
203
|
+
clearClientTimeout(motionTimers.get(toast.id));
|
|
204
|
+
motionTimers.delete(toast.id);
|
|
205
|
+
Object.assign(existing, toast, {
|
|
206
|
+
expanded: true,
|
|
207
|
+
rackMotion: existing.rackMotion,
|
|
208
|
+
motion: existing.motion === 'leaving' ? 'entering' : existing.motion,
|
|
209
|
+
});
|
|
210
|
+
if (existing.motion === 'entering') settleToast(existing);
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const shouldAnimate = isMounted.value && typeof window !== 'undefined';
|
|
215
|
+
const next = {
|
|
216
|
+
...toast,
|
|
217
|
+
expanded: true,
|
|
218
|
+
rackMotion: 'idle',
|
|
219
|
+
motion: shouldAnimate ? 'entering' : 'open',
|
|
220
|
+
};
|
|
221
|
+
renderedToasts.value.push(next);
|
|
222
|
+
if (shouldAnimate) settleToast(renderedToasts.value[renderedToasts.value.length - 1]);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
for (const toast of renderedToasts.value) {
|
|
226
|
+
if (nextIds.has(toast.id) || toast.motion === 'leaving') continue;
|
|
227
|
+
toast.motion = 'leaving';
|
|
228
|
+
toast.expanded = false;
|
|
229
|
+
clearClientTimeout(motionTimers.get(toast.id));
|
|
230
|
+
if (typeof window === 'undefined') {
|
|
231
|
+
renderedToasts.value = renderedToasts.value.filter((item) => item.id !== toast.id);
|
|
232
|
+
continue;
|
|
233
|
+
}
|
|
234
|
+
motionTimers.set(toast.id, window.setTimeout(() => {
|
|
235
|
+
renderedToasts.value = renderedToasts.value.filter((item) => item.id !== toast.id);
|
|
236
|
+
motionTimers.delete(toast.id);
|
|
237
|
+
}, 300));
|
|
238
|
+
}
|
|
239
|
+
syncRackRestackMotion(previousRackFrontId);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Reads the current front toast id for collapsed rack transition bookkeeping.
|
|
244
|
+
*
|
|
245
|
+
* @returns {string|undefined}
|
|
246
|
+
*/
|
|
247
|
+
function getRackFrontId() {
|
|
248
|
+
if (!isRackLayout.value || !renderedToasts.value.length) return undefined;
|
|
249
|
+
return renderedToasts.value[renderedToasts.value.length - 1]?.id;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/**
|
|
253
|
+
* Marks the displaced rack front so it animates into the receded card position.
|
|
254
|
+
*
|
|
255
|
+
* @param {string|undefined} previousRackFrontId Toast id that was front before the render sync.
|
|
256
|
+
* @returns {void}
|
|
257
|
+
*/
|
|
258
|
+
function syncRackRestackMotion(previousRackFrontId) {
|
|
259
|
+
const nextRackFrontId = getRackFrontId();
|
|
260
|
+
if (!previousRackFrontId || !nextRackFrontId || previousRackFrontId === nextRackFrontId) return;
|
|
261
|
+
if (!isRackLayout.value || rackExpanded.value || typeof window === 'undefined') return;
|
|
262
|
+
const toast = renderedToasts.value.find((item) => item.id === previousRackFrontId);
|
|
263
|
+
if (!toast || toast.motion === 'leaving') return;
|
|
264
|
+
clearClientTimeout(rackMotionTimers.get(toast.id));
|
|
265
|
+
toast.rackMotion = 'receding';
|
|
266
|
+
rackMotionTimers.set(toast.id, window.setTimeout(() => {
|
|
267
|
+
if (toast.rackMotion === 'receding') toast.rackMotion = 'idle';
|
|
268
|
+
rackMotionTimers.delete(toast.id);
|
|
269
|
+
}, 260));
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Moves an entering toast into its open motion state after its entrance settles.
|
|
274
|
+
*
|
|
275
|
+
* @param {object} toast Rendered toast record.
|
|
276
|
+
* @returns {void}
|
|
277
|
+
*/
|
|
278
|
+
function settleToast(toast) {
|
|
279
|
+
requestClientFrame(() => {
|
|
280
|
+
toast.expanded = true;
|
|
281
|
+
if (typeof window === 'undefined') {
|
|
282
|
+
toast.motion = 'open';
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
motionTimers.set(toast.id, window.setTimeout(() => {
|
|
286
|
+
if (toast.motion === 'entering') toast.motion = 'open';
|
|
287
|
+
motionTimers.delete(toast.id);
|
|
288
|
+
}, 320));
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Clears a browser timeout when code is running on the client.
|
|
294
|
+
*
|
|
295
|
+
* @param {number|undefined} timer Timeout id to clear.
|
|
296
|
+
* @returns {void}
|
|
297
|
+
*/
|
|
298
|
+
function clearClientTimeout(timer) {
|
|
299
|
+
if (typeof window !== 'undefined') window.clearTimeout(timer);
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Runs a callback on the next animation frame when the browser is available.
|
|
304
|
+
*
|
|
305
|
+
* @param {Function} callback Work to run after layout has a chance to update.
|
|
306
|
+
* @returns {void}
|
|
307
|
+
*/
|
|
308
|
+
function requestClientFrame(callback) {
|
|
309
|
+
if (typeof window === 'undefined') {
|
|
310
|
+
callback();
|
|
311
|
+
return;
|
|
312
|
+
}
|
|
313
|
+
window.requestAnimationFrame(callback);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Resolves the semantic accent border class for a toast tone.
|
|
318
|
+
*
|
|
319
|
+
* @param {string} tone Toast tone.
|
|
320
|
+
* @returns {string}
|
|
321
|
+
*/
|
|
322
|
+
function toneClass(tone) {
|
|
323
|
+
return {
|
|
324
|
+
default: 'border-border',
|
|
325
|
+
success: 'border-success/35',
|
|
326
|
+
danger: 'border-destructive/35',
|
|
327
|
+
warning: 'border-warning/35',
|
|
328
|
+
}[tone] || 'border-border';
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Resolves the component used for a toast record.
|
|
333
|
+
*
|
|
334
|
+
* @param {object} toast Toast record.
|
|
335
|
+
* @returns {object}
|
|
336
|
+
*/
|
|
337
|
+
function toastComponent(toast) {
|
|
338
|
+
return toast.component || DomToastItem;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* Builds props for the resolved toast component.
|
|
343
|
+
*
|
|
344
|
+
* @param {object} toast Toast record.
|
|
345
|
+
* @returns {object}
|
|
346
|
+
*/
|
|
347
|
+
function toastProps(toast) {
|
|
348
|
+
if (toast.component) return toast.props || {};
|
|
349
|
+
return {
|
|
350
|
+
title: toast.title,
|
|
351
|
+
description: toast.description,
|
|
352
|
+
tone: toast.tone,
|
|
353
|
+
dismissible: toast.dismissible !== false,
|
|
354
|
+
};
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Builds rack CSS custom properties for a displayed toast.
|
|
359
|
+
*
|
|
360
|
+
* @param {number} rackIndex Visual depth from the front toast.
|
|
361
|
+
* @returns {object|undefined}
|
|
362
|
+
*/
|
|
363
|
+
function toastShellStyle(rackIndex) {
|
|
364
|
+
if (!isRackLayout.value) return undefined;
|
|
365
|
+
const visibleIndex = Math.min(rackIndex, 3);
|
|
366
|
+
const direction = isBottomPosition.value ? -1 : 1;
|
|
367
|
+
const offset = visibleIndex * direction * 10;
|
|
368
|
+
const collapseOffset = offset < 0 ? `calc(-100% - ${Math.abs(offset)}px)` : `calc(-100% + ${offset}px)`;
|
|
369
|
+
return {
|
|
370
|
+
'--toast-rack-collapse-y': rackIndex === 0 ? '0px' : collapseOffset,
|
|
371
|
+
'--toast-rack-depth-offset': `${offset}px`,
|
|
372
|
+
'--toast-rack-scale': `${Math.max(0.86, 1 - (visibleIndex * 0.045))}`,
|
|
373
|
+
'--toast-rack-opacity': `${rackIndex > 3 ? 0 : Math.max(0.58, 1 - (visibleIndex * 0.16))}`,
|
|
374
|
+
'--toast-rack-z': `${100 - rackIndex}`,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
/**
|
|
379
|
+
* Reports whether a rack toast should be hidden from interaction while collapsed.
|
|
380
|
+
*
|
|
381
|
+
* @param {boolean} isRackFront Whether the toast is the front rack card.
|
|
382
|
+
* @returns {boolean}
|
|
383
|
+
*/
|
|
384
|
+
function isCollapsedRackItem(isRackFront) {
|
|
385
|
+
return isRackLayout.value && !rackExpanded.value && !isRackFront;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* Handles pointer entry so the rack can stay expanded while the user works in it.
|
|
390
|
+
*
|
|
391
|
+
* @returns {void}
|
|
392
|
+
*/
|
|
393
|
+
function handleRackPointerEnter() {
|
|
394
|
+
rackPointerInside.value = true;
|
|
395
|
+
expandRack();
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
/**
|
|
399
|
+
* Handles pointer exit by allowing the rack to settle before collapsing.
|
|
400
|
+
*
|
|
401
|
+
* @returns {void}
|
|
402
|
+
*/
|
|
403
|
+
function handleRackPointerLeave() {
|
|
404
|
+
rackPointerInside.value = false;
|
|
405
|
+
scheduleRackCollapse();
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
/**
|
|
409
|
+
* Handles focus entry so keyboard users can inspect and dismiss older rack items.
|
|
410
|
+
*
|
|
411
|
+
* @returns {void}
|
|
412
|
+
*/
|
|
413
|
+
function handleRackFocusIn() {
|
|
414
|
+
rackFocusInside.value = true;
|
|
415
|
+
expandRack();
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* Expands the rack so older toasts can be inspected and dismissed.
|
|
420
|
+
*
|
|
421
|
+
* @returns {void}
|
|
422
|
+
*/
|
|
423
|
+
function expandRack() {
|
|
424
|
+
clearRackCollapseTimer();
|
|
425
|
+
setRackExpanded(true);
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
/**
|
|
429
|
+
* Collapses the rack after keyboard focus leaves the toast region.
|
|
430
|
+
*
|
|
431
|
+
* @param {FocusEvent} event Focusout event from the toast region.
|
|
432
|
+
* @returns {void}
|
|
433
|
+
*/
|
|
434
|
+
function handleRackFocusOut(event) {
|
|
435
|
+
if (event.currentTarget?.contains(event.relatedTarget)) return;
|
|
436
|
+
rackFocusInside.value = false;
|
|
437
|
+
if (rackPointerInside.value) return;
|
|
438
|
+
scheduleRackCollapse(420);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
/**
|
|
442
|
+
* Updates the rack expansion state only when rack layout is active.
|
|
443
|
+
*
|
|
444
|
+
* @param {boolean} expanded Whether the rack should be expanded.
|
|
445
|
+
* @returns {void}
|
|
446
|
+
*/
|
|
447
|
+
function setRackExpanded(expanded) {
|
|
448
|
+
rackExpanded.value = isRackLayout.value ? expanded : false;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Keeps the rack open briefly after a dismiss action changes focus and layout.
|
|
453
|
+
*
|
|
454
|
+
* @returns {void}
|
|
455
|
+
*/
|
|
456
|
+
function holdRackOpenAfterDismiss() {
|
|
457
|
+
if (!isRackLayout.value || !rackExpanded.value) return;
|
|
458
|
+
clearRackCollapseTimer();
|
|
459
|
+
scheduleRackCollapse(520);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Schedules rack collapse after any close or hover-leave motion has time to read cleanly.
|
|
464
|
+
*
|
|
465
|
+
* @param {number} delay Collapse delay in milliseconds.
|
|
466
|
+
* @returns {void}
|
|
467
|
+
*/
|
|
468
|
+
function scheduleRackCollapse(delay = 180) {
|
|
469
|
+
clearRackCollapseTimer();
|
|
470
|
+
if (!isRackLayout.value) {
|
|
471
|
+
setRackExpanded(false);
|
|
472
|
+
return;
|
|
473
|
+
}
|
|
474
|
+
if (typeof window === 'undefined') {
|
|
475
|
+
setRackExpanded(false);
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
rackCollapseTimer = window.setTimeout(() => {
|
|
479
|
+
rackCollapseTimer = undefined;
|
|
480
|
+
if (rackPointerInside.value || rackFocusInside.value) return;
|
|
481
|
+
setRackExpanded(false);
|
|
482
|
+
}, delay);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
/**
|
|
486
|
+
* Clears the pending rack-collapse timeout.
|
|
487
|
+
*
|
|
488
|
+
* @returns {void}
|
|
489
|
+
*/
|
|
490
|
+
function clearRackCollapseTimer() {
|
|
491
|
+
clearClientTimeout(rackCollapseTimer);
|
|
492
|
+
rackCollapseTimer = undefined;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
/**
|
|
496
|
+
* Handles action payloads emitted by custom toast template components.
|
|
497
|
+
*
|
|
498
|
+
* Template components can emit action payloads for app-specific work, or emit
|
|
499
|
+
* dismiss when they want the stack to remove their own toast.
|
|
500
|
+
*
|
|
501
|
+
* @param {object} toast Toast record that emitted the action.
|
|
502
|
+
* @param {object|string} payload Action payload from the toast content.
|
|
503
|
+
* @returns {void}
|
|
504
|
+
*/
|
|
505
|
+
function onToastAction(toast, payload) {
|
|
506
|
+
const detail = {
|
|
507
|
+
id: toast.id,
|
|
508
|
+
toast,
|
|
509
|
+
...(payload && typeof payload === 'object' ? payload : { action: payload }),
|
|
510
|
+
};
|
|
511
|
+
if (detail.action === 'dismiss') dismiss(toast.id);
|
|
512
|
+
emit('action', detail);
|
|
513
|
+
}
|
|
514
|
+
</script>
|
|
515
|
+
|
|
516
|
+
<template>
|
|
517
|
+
<dom-toast-region
|
|
518
|
+
class="toast-region fixed z-[80] w-[min(92vw,22rem)]"
|
|
519
|
+
:class="[
|
|
520
|
+
{
|
|
521
|
+
'top-4 right-4': position === 'top-right',
|
|
522
|
+
'top-4 left-1/2 -translate-x-1/2': position === 'top-center',
|
|
523
|
+
'top-4 left-4': position === 'top-left',
|
|
524
|
+
'bottom-4 right-4': position === 'bottom-right',
|
|
525
|
+
'bottom-4 left-1/2 -translate-x-1/2': position === 'bottom-center',
|
|
526
|
+
'bottom-4 left-4': position === 'bottom-left',
|
|
527
|
+
'toast-region-bottom': isBottomPosition,
|
|
528
|
+
},
|
|
529
|
+
]"
|
|
530
|
+
@mouseenter="handleRackPointerEnter"
|
|
531
|
+
@mouseleave="handleRackPointerLeave"
|
|
532
|
+
@focusin="handleRackFocusIn"
|
|
533
|
+
@focusout="handleRackFocusOut"
|
|
534
|
+
>
|
|
535
|
+
<div
|
|
536
|
+
class="toast-list"
|
|
537
|
+
:class="{
|
|
538
|
+
'toast-list-stack': !isRackLayout,
|
|
539
|
+
'toast-list-rack': isRackLayout,
|
|
540
|
+
'toast-list-rack-expanded': isRackLayout && rackExpanded,
|
|
541
|
+
}"
|
|
542
|
+
>
|
|
543
|
+
<div
|
|
544
|
+
v-for="{ toast, rackIndex, isRackFront } in displayedToasts"
|
|
545
|
+
:key="toast.id"
|
|
546
|
+
class="toast-shell"
|
|
547
|
+
:class="[
|
|
548
|
+
toast.expanded && 'toast-shell-open',
|
|
549
|
+
toast.motion === 'leaving' && 'toast-leaving',
|
|
550
|
+
toast.rackMotion === 'receding' && 'toast-rack-receding',
|
|
551
|
+
]"
|
|
552
|
+
:style="toastShellStyle(rackIndex)"
|
|
553
|
+
:data-rack-front="isRackFront ? 'true' : undefined"
|
|
554
|
+
:inert="isCollapsedRackItem(isRackFront)"
|
|
555
|
+
:aria-hidden="isCollapsedRackItem(isRackFront) ? 'true' : undefined"
|
|
556
|
+
>
|
|
557
|
+
<div class="toast-shell-clip">
|
|
558
|
+
<div class="toast-shell-inner">
|
|
559
|
+
<div
|
|
560
|
+
:role="toast.tone === 'danger' ? 'alert' : 'status'"
|
|
561
|
+
class="toast-card skin-popover overflow-hidden rounded-2xl p-4"
|
|
562
|
+
:class="[
|
|
563
|
+
toneClass(toast.tone),
|
|
564
|
+
toast.motion === 'entering' && 'toast-entering',
|
|
565
|
+
toast.motion === 'leaving' && 'toast-card-leaving',
|
|
566
|
+
]"
|
|
567
|
+
>
|
|
568
|
+
<slot :toast="toast" :dismiss="dismiss">
|
|
569
|
+
<component
|
|
570
|
+
:is="toastComponent(toast)"
|
|
571
|
+
v-bind="toastProps(toast)"
|
|
572
|
+
@action="onToastAction(toast, $event)"
|
|
573
|
+
@dismiss="dismiss(toast.id)"
|
|
574
|
+
/>
|
|
575
|
+
</slot>
|
|
576
|
+
</div>
|
|
577
|
+
</div>
|
|
578
|
+
</div>
|
|
579
|
+
</div>
|
|
580
|
+
</div>
|
|
581
|
+
</dom-toast-region>
|
|
582
|
+
</template>
|
|
583
|
+
|
|
584
|
+
<style scoped>
|
|
585
|
+
.toast-region {
|
|
586
|
+
--toast-enter-y: -12px;
|
|
587
|
+
--toast-leave-y: -8px;
|
|
588
|
+
--toast-rack-enter-y: -18px;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.toast-region-bottom {
|
|
592
|
+
--toast-enter-y: 12px;
|
|
593
|
+
--toast-leave-y: 8px;
|
|
594
|
+
--toast-rack-enter-y: 18px;
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
.toast-list {
|
|
598
|
+
display: grid;
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
.toast-list-rack {
|
|
602
|
+
align-items: start;
|
|
603
|
+
isolation: isolate;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
.toast-shell {
|
|
607
|
+
display: grid;
|
|
608
|
+
grid-template-rows: 0fr;
|
|
609
|
+
padding-bottom: 0;
|
|
610
|
+
position: relative;
|
|
611
|
+
transition:
|
|
612
|
+
grid-template-rows 300ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
613
|
+
padding-bottom 300ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
.toast-list-rack .toast-shell {
|
|
617
|
+
z-index: var(--toast-rack-z, 1);
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
.toast-list-rack .toast-shell-inner {
|
|
621
|
+
opacity: 1;
|
|
622
|
+
transform: translateY(0) scale(1);
|
|
623
|
+
transform-origin: top center;
|
|
624
|
+
transition:
|
|
625
|
+
transform 300ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
626
|
+
opacity 220ms ease;
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
.toast-region-bottom .toast-list-rack .toast-shell-inner {
|
|
630
|
+
transform-origin: bottom center;
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
.toast-shell-open {
|
|
634
|
+
grid-template-rows: 1fr;
|
|
635
|
+
padding-bottom: 0.5rem;
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
.toast-shell:last-child {
|
|
639
|
+
padding-bottom: 0;
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
.toast-shell-clip {
|
|
643
|
+
min-height: 0;
|
|
644
|
+
overflow: visible;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
.toast-list-rack:not(.toast-list-rack-expanded) .toast-shell {
|
|
648
|
+
pointer-events: none;
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
.toast-list-rack:not(.toast-list-rack-expanded) .toast-shell-open {
|
|
652
|
+
padding-bottom: 0;
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
.toast-list-rack:not(.toast-list-rack-expanded) .toast-shell:not([data-rack-front='true']) {
|
|
656
|
+
grid-template-rows: 0fr;
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
.toast-list-rack:not(.toast-list-rack-expanded) .toast-rack-receding {
|
|
660
|
+
transition: none;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
.toast-list-rack:not(.toast-list-rack-expanded) .toast-shell-inner {
|
|
664
|
+
opacity: var(--toast-rack-opacity, 1);
|
|
665
|
+
transform: translateY(var(--toast-rack-collapse-y, 0)) scale(var(--toast-rack-scale, 1));
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
.toast-list-rack:not(.toast-list-rack-expanded) .toast-rack-receding .toast-shell-inner {
|
|
669
|
+
animation: rack-toast-recede 240ms cubic-bezier(0.22, 1, 0.36, 1) both;
|
|
670
|
+
}
|
|
671
|
+
|
|
672
|
+
.toast-list-rack:not(.toast-list-rack-expanded) .toast-shell[data-rack-front='true'] {
|
|
673
|
+
pointer-events: auto;
|
|
674
|
+
}
|
|
675
|
+
|
|
676
|
+
.toast-list-rack:not(.toast-list-rack-expanded) .toast-shell:not([data-rack-front='true']) .toast-card {
|
|
677
|
+
box-shadow: var(--shadow-md);
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
.toast-list-rack-expanded .toast-shell {
|
|
681
|
+
pointer-events: auto;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
.toast-list-rack-expanded .toast-shell-inner {
|
|
685
|
+
opacity: 1;
|
|
686
|
+
transform: translateY(0) scale(1);
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
.toast-entering {
|
|
690
|
+
animation: toast-in 280ms both;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
.toast-list-rack:not(.toast-list-rack-expanded) .toast-shell[data-rack-front='true'] .toast-entering {
|
|
694
|
+
animation: rack-toast-in 320ms cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
.toast-card-leaving {
|
|
698
|
+
animation: toast-out 260ms both;
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
@keyframes toast-in {
|
|
702
|
+
0% {
|
|
703
|
+
opacity: 0;
|
|
704
|
+
transform: translateY(var(--toast-enter-y)) scale(0.965);
|
|
705
|
+
}
|
|
706
|
+
68% {
|
|
707
|
+
opacity: 1;
|
|
708
|
+
transform: translateY(1px) scale(1.012);
|
|
709
|
+
}
|
|
710
|
+
100% {
|
|
711
|
+
opacity: 1;
|
|
712
|
+
transform: translateY(0) scale(1);
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
@keyframes toast-out {
|
|
717
|
+
0% {
|
|
718
|
+
opacity: 1;
|
|
719
|
+
transform: translateY(0) scale(1);
|
|
720
|
+
}
|
|
721
|
+
100% {
|
|
722
|
+
opacity: 0;
|
|
723
|
+
transform: translateY(var(--toast-leave-y)) scale(0.985);
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
@keyframes rack-toast-in {
|
|
728
|
+
0% {
|
|
729
|
+
opacity: 0;
|
|
730
|
+
transform: translateY(var(--toast-rack-enter-y, var(--toast-enter-y))) scale(0.94);
|
|
731
|
+
}
|
|
732
|
+
58% {
|
|
733
|
+
opacity: 1;
|
|
734
|
+
transform: translateY(1px) scale(1.018);
|
|
735
|
+
}
|
|
736
|
+
100% {
|
|
737
|
+
opacity: 1;
|
|
738
|
+
transform: translateY(0) scale(1);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
@keyframes rack-toast-recede {
|
|
743
|
+
0% {
|
|
744
|
+
opacity: 1;
|
|
745
|
+
transform: translateY(calc(var(--toast-rack-collapse-y, 0px) - var(--toast-rack-depth-offset, 0px))) scale(1);
|
|
746
|
+
}
|
|
747
|
+
100% {
|
|
748
|
+
opacity: var(--toast-rack-opacity, 1);
|
|
749
|
+
transform: translateY(var(--toast-rack-collapse-y, 0px)) scale(var(--toast-rack-scale, 1));
|
|
750
|
+
}
|
|
751
|
+
}
|
|
752
|
+
|
|
753
|
+
@media (prefers-reduced-motion: reduce) {
|
|
754
|
+
.toast-entering,
|
|
755
|
+
.toast-card-leaving,
|
|
756
|
+
.toast-rack-receding .toast-shell-inner {
|
|
757
|
+
animation-duration: 1ms;
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
.toast-shell {
|
|
761
|
+
transition-duration: 1ms;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
.toast-list-rack .toast-shell-inner {
|
|
765
|
+
transition-duration: 1ms;
|
|
766
|
+
}
|
|
767
|
+
}
|
|
768
|
+
</style>
|