@chromvoid/uikit 0.1.0 → 0.2.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 +19 -6
- package/README.md +1 -0
- package/dist/components/cv-accordion-item.d.ts +1 -1
- package/dist/components/cv-accordion.d.ts +1 -1
- package/dist/components/cv-accordion.js +2 -1
- package/dist/components/cv-alert-dialog.d.ts +1 -1
- package/dist/components/cv-alert-dialog.js +17 -2
- package/dist/components/cv-alert.d.ts +1 -1
- package/dist/components/cv-alert.js +2 -1
- package/dist/components/cv-badge.d.ts +1 -1
- package/dist/components/cv-badge.js +2 -1
- package/dist/components/cv-bottom-sheet.d.ts +127 -0
- package/dist/components/cv-bottom-sheet.js +513 -0
- package/dist/components/cv-breadcrumb-item.d.ts +1 -1
- package/dist/components/cv-breadcrumb-item.js +1 -1
- package/dist/components/cv-breadcrumb.d.ts +1 -1
- package/dist/components/cv-breadcrumb.js +2 -1
- package/dist/components/cv-button.d.ts +23 -1
- package/dist/components/cv-button.js +194 -37
- package/dist/components/cv-callout.d.ts +8 -1
- package/dist/components/cv-callout.js +18 -1
- package/dist/components/cv-card.d.ts +1 -1
- package/dist/components/cv-card.js +2 -2
- package/dist/components/cv-carousel-slide.d.ts +1 -1
- package/dist/components/cv-carousel.d.ts +1 -1
- package/dist/components/cv-carousel.js +2 -1
- package/dist/components/cv-checkbox.d.ts +1 -1
- package/dist/components/cv-combobox-group.d.ts +1 -1
- package/dist/components/cv-combobox-option.d.ts +1 -1
- package/dist/components/cv-combobox-option.js +2 -2
- package/dist/components/cv-combobox.d.ts +3 -1
- package/dist/components/cv-combobox.js +49 -8
- package/dist/components/cv-command-item.d.ts +1 -1
- package/dist/components/cv-command-item.js +2 -2
- package/dist/components/cv-command-palette.d.ts +1 -1
- package/dist/components/cv-command-palette.js +21 -1
- package/dist/components/cv-context-menu.d.ts +1 -1
- package/dist/components/cv-context-menu.js +2 -1
- package/dist/components/cv-copy-button.d.ts +37 -9
- package/dist/components/cv-copy-button.js +129 -41
- package/dist/components/cv-date-picker.d.ts +1 -1
- package/dist/components/cv-date-picker.js +20 -1
- package/dist/components/cv-dialog.d.ts +44 -2
- package/dist/components/cv-dialog.js +686 -74
- package/dist/components/cv-disclosure.d.ts +1 -1
- package/dist/components/cv-disclosure.js +2 -1
- package/dist/components/cv-drawer.d.ts +29 -1
- package/dist/components/cv-drawer.js +229 -4
- package/dist/components/cv-feed-article.d.ts +1 -1
- package/dist/components/cv-feed-article.js +2 -1
- package/dist/components/cv-feed.d.ts +1 -1
- package/dist/components/cv-feed.js +2 -1
- package/dist/components/cv-grid-cell.d.ts +1 -1
- package/dist/components/cv-grid-cell.js +3 -3
- package/dist/components/cv-grid-column.d.ts +1 -1
- package/dist/components/cv-grid-column.js +1 -1
- package/dist/components/cv-grid-row.d.ts +1 -1
- package/dist/components/cv-grid.d.ts +1 -1
- package/dist/components/cv-grid.js +2 -1
- package/dist/components/cv-guidance-anchor.d.ts +47 -0
- package/dist/components/cv-guidance-anchor.js +113 -0
- package/dist/components/cv-guidance-panel.d.ts +29 -0
- package/dist/components/cv-guidance-panel.js +245 -0
- package/dist/components/cv-icon.d.ts +2 -1
- package/dist/components/cv-icon.js +28 -3
- package/dist/components/cv-input.d.ts +7 -1
- package/dist/components/cv-input.js +33 -1
- package/dist/components/cv-landmark.d.ts +1 -1
- package/dist/components/cv-landmark.js +2 -1
- package/dist/components/cv-link.d.ts +1 -1
- package/dist/components/cv-link.js +2 -1
- package/dist/components/cv-listbox-group.d.ts +1 -1
- package/dist/components/cv-listbox.d.ts +1 -1
- package/dist/components/cv-listbox.js +2 -1
- package/dist/components/cv-menu-button.d.ts +24 -1
- package/dist/components/cv-menu-button.js +226 -18
- package/dist/components/cv-menu-group.d.ts +1 -1
- package/dist/components/cv-menu-item.d.ts +1 -1
- package/dist/components/cv-menu-item.js +6 -2
- package/dist/components/cv-menu.d.ts +1 -1
- package/dist/components/cv-menu.js +21 -1
- package/dist/components/cv-meter.d.ts +1 -1
- package/dist/components/cv-meter.js +6 -22
- package/dist/components/cv-number.d.ts +1 -1
- package/dist/components/cv-option.d.ts +1 -1
- package/dist/components/cv-option.js +3 -9
- package/dist/components/cv-popover-positioning.d.ts +22 -0
- package/dist/components/cv-popover-positioning.js +112 -0
- package/dist/components/cv-popover.d.ts +45 -8
- package/dist/components/cv-popover.js +395 -113
- package/dist/components/cv-progress-ring.d.ts +1 -1
- package/dist/components/cv-progress-ring.js +2 -1
- package/dist/components/cv-progress.d.ts +8 -1
- package/dist/components/cv-progress.js +41 -10
- package/dist/components/cv-radio-group.d.ts +1 -1
- package/dist/components/cv-radio.d.ts +1 -1
- package/dist/components/cv-radio.js +1 -1
- package/dist/components/cv-select-group.d.ts +1 -1
- package/dist/components/cv-select-option.d.ts +1 -1
- package/dist/components/cv-select-option.js +2 -2
- package/dist/components/cv-select.d.ts +1 -1
- package/dist/components/cv-select.js +28 -1
- package/dist/components/cv-sidebar-item.d.ts +1 -1
- package/dist/components/cv-sidebar.d.ts +1 -1
- package/dist/components/cv-sidebar.js +3 -2
- package/dist/components/cv-slider-multi-thumb.d.ts +1 -1
- package/dist/components/cv-slider-multi-thumb.js +2 -1
- package/dist/components/cv-slider.d.ts +17 -4
- package/dist/components/cv-slider.js +63 -21
- package/dist/components/cv-spinbutton.d.ts +1 -1
- package/dist/components/cv-spinner.d.ts +1 -1
- package/dist/components/cv-spinner.js +2 -1
- package/dist/components/cv-switch.d.ts +1 -1
- package/dist/components/cv-tab-panel.d.ts +1 -1
- package/dist/components/cv-tab.d.ts +1 -1
- package/dist/components/cv-table-cell.d.ts +1 -1
- package/dist/components/cv-table-cell.js +1 -1
- package/dist/components/cv-table-column.d.ts +1 -1
- package/dist/components/cv-table-column.js +1 -1
- package/dist/components/cv-table-row.d.ts +1 -1
- package/dist/components/cv-table-row.js +1 -4
- package/dist/components/cv-table.d.ts +1 -3
- package/dist/components/cv-table.js +4 -11
- package/dist/components/cv-tabs.d.ts +1 -1
- package/dist/components/cv-tabs.js +3 -2
- package/dist/components/cv-textarea.d.ts +11 -1
- package/dist/components/cv-textarea.js +33 -0
- package/dist/components/cv-toast-region.d.ts +1 -1
- package/dist/components/cv-toast-region.js +2 -1
- package/dist/components/cv-toast.d.ts +1 -1
- package/dist/components/cv-toast.js +20 -27
- package/dist/components/cv-toolbar-item.d.ts +1 -1
- package/dist/components/cv-toolbar-separator.d.ts +1 -1
- package/dist/components/cv-toolbar.d.ts +1 -1
- package/dist/components/cv-toolbar.js +2 -1
- package/dist/components/cv-tooltip.d.ts +1 -1
- package/dist/components/cv-tooltip.js +2 -1
- package/dist/components/cv-treegrid-cell.d.ts +1 -1
- package/dist/components/cv-treegrid-cell.js +1 -1
- package/dist/components/cv-treegrid-column.d.ts +1 -1
- package/dist/components/cv-treegrid-column.js +1 -1
- package/dist/components/cv-treegrid-row.d.ts +1 -1
- package/dist/components/cv-treegrid-row.js +1 -1
- package/dist/components/cv-treegrid.d.ts +1 -1
- package/dist/components/cv-treegrid.js +4 -3
- package/dist/components/cv-treeitem.d.ts +1 -1
- package/dist/components/cv-treeitem.js +2 -2
- package/dist/components/cv-treeview.d.ts +1 -1
- package/dist/components/cv-treeview.js +2 -1
- package/dist/components/cv-window-splitter.d.ts +1 -1
- package/dist/components/cv-window-splitter.js +2 -1
- package/dist/components/index.d.ts +7 -0
- package/dist/components/index.js +3 -0
- package/dist/dialog/create-dialog-controller.d.ts +12 -4
- package/dist/dialog/create-dialog-controller.js +84 -22
- package/dist/dialog/index.d.ts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/reatom-lit/ReatomLitElement.d.ts +6 -3
- package/dist/reatom-lit/ReatomLitElement.js +18 -8
- package/dist/reatom-lit/createAfterRenderScheduler.d.ts +10 -0
- package/dist/reatom-lit/createAfterRenderScheduler.js +33 -0
- package/dist/reatom-lit/index.d.ts +2 -0
- package/dist/reatom-lit/index.js +1 -0
- package/dist/reatom-lit/watch.d.ts +1 -1
- package/dist/reatom-lit/withReatomElement.js +16 -2
- package/dist/register.js +4 -1
- package/dist/styles/component-styles.js +4 -0
- package/dist/styles/uno-generated.d.ts +2 -0
- package/dist/styles/uno-generated.js +1 -0
- package/dist/styles/uno-utilities.d.ts +5 -0
- package/dist/styles/uno-utilities.js +7 -0
- package/dist/theme/cv-theme-provider.d.ts +1 -1
- package/dist/theme/cv-theme-provider.js +2 -2
- package/dist/theme/tokens.css +619 -162
- package/package.json +9 -5
- package/specs/components/bottom-sheet.md +93 -0
- package/specs/components/button.md +8 -0
- package/specs/components/callout.md +8 -0
- package/specs/components/copy-button.md +54 -17
- package/specs/components/dialog.md +72 -43
- package/specs/components/drawer.md +18 -13
- package/specs/components/guidance-anchor.md +64 -0
- package/specs/components/guidance-panel.md +92 -0
- package/specs/components/input.md +7 -0
- package/specs/components/menu.md +8 -0
- package/specs/components/option.md +9 -9
- package/specs/components/progress.md +11 -0
- package/specs/components/sidebar.md +12 -12
- package/specs/components/table.md +13 -13
- package/specs/components/theme.md +13 -13
- package/specs/components/treegrid.md +15 -15
- package/specs/components/treeview.md +10 -10
|
@@ -1,7 +1,55 @@
|
|
|
1
1
|
import { createDialog } from '@chromvoid/headless-ui/dialog';
|
|
2
|
-
import { css,
|
|
2
|
+
import { css, nothing } from 'lit';
|
|
3
|
+
import { html } from '../reatom-lit/index.js';
|
|
3
4
|
import { ReatomLitElement } from '../reatom-lit/ReatomLitElement.js';
|
|
4
5
|
let cvDialogNonce = 0;
|
|
6
|
+
let hasWarnedAboutTriggerSlot = false;
|
|
7
|
+
function getDeepActiveElement() {
|
|
8
|
+
let activeElement = document.activeElement;
|
|
9
|
+
while (activeElement instanceof HTMLElement &&
|
|
10
|
+
activeElement.shadowRoot?.activeElement instanceof HTMLElement) {
|
|
11
|
+
activeElement = activeElement.shadowRoot.activeElement;
|
|
12
|
+
}
|
|
13
|
+
return activeElement instanceof HTMLElement ? activeElement : null;
|
|
14
|
+
}
|
|
15
|
+
function getFocusRestoreTarget() {
|
|
16
|
+
const activeElement = getDeepActiveElement();
|
|
17
|
+
if (!activeElement || activeElement === document.body || activeElement === document.documentElement) {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
return activeElement;
|
|
21
|
+
}
|
|
22
|
+
function getIdSelector(id) {
|
|
23
|
+
return `[id="${id.replace(/["\\]/g, '\\$&')}"]`;
|
|
24
|
+
}
|
|
25
|
+
function findComposedElementById(root, id) {
|
|
26
|
+
const direct = root.querySelector?.(getIdSelector(id));
|
|
27
|
+
if (direct instanceof HTMLElement)
|
|
28
|
+
return direct;
|
|
29
|
+
const elements = Array.from(root.querySelectorAll?.('*') ?? []);
|
|
30
|
+
for (const element of elements) {
|
|
31
|
+
if (element.shadowRoot) {
|
|
32
|
+
const shadowMatch = findComposedElementById(element.shadowRoot, id);
|
|
33
|
+
if (shadowMatch)
|
|
34
|
+
return shadowMatch;
|
|
35
|
+
}
|
|
36
|
+
if (typeof HTMLSlotElement === 'undefined' || !(element instanceof HTMLSlotElement))
|
|
37
|
+
continue;
|
|
38
|
+
for (const assigned of element.assignedElements({ flatten: true })) {
|
|
39
|
+
if (assigned.id === id && assigned instanceof HTMLElement)
|
|
40
|
+
return assigned;
|
|
41
|
+
const assignedMatch = findComposedElementById(assigned, id);
|
|
42
|
+
if (assignedMatch)
|
|
43
|
+
return assignedMatch;
|
|
44
|
+
if (assigned.shadowRoot) {
|
|
45
|
+
const assignedShadowMatch = findComposedElementById(assigned.shadowRoot, id);
|
|
46
|
+
if (assignedShadowMatch)
|
|
47
|
+
return assignedShadowMatch;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
5
53
|
export class CVDialog extends ReatomLitElement {
|
|
6
54
|
static elementName = 'cv-dialog';
|
|
7
55
|
static get properties() {
|
|
@@ -10,7 +58,11 @@ export class CVDialog extends ReatomLitElement {
|
|
|
10
58
|
modal: { type: Boolean, reflect: true },
|
|
11
59
|
type: { type: String, reflect: true },
|
|
12
60
|
closeOnEscape: { type: Boolean, attribute: 'close-on-escape', reflect: true },
|
|
13
|
-
closeOnOutsidePointer: {
|
|
61
|
+
closeOnOutsidePointer: {
|
|
62
|
+
type: Boolean,
|
|
63
|
+
attribute: 'close-on-outside-pointer',
|
|
64
|
+
reflect: true,
|
|
65
|
+
},
|
|
14
66
|
closeOnOutsideFocus: { type: Boolean, attribute: 'close-on-outside-focus', reflect: true },
|
|
15
67
|
initialFocusId: { type: String, attribute: 'initial-focus-id' },
|
|
16
68
|
noHeader: { type: Boolean, attribute: 'no-header', reflect: true },
|
|
@@ -23,6 +75,15 @@ export class CVDialog extends ReatomLitElement {
|
|
|
23
75
|
previousBodyOverflow = '';
|
|
24
76
|
suppressLifecycleFromUpdate = false;
|
|
25
77
|
lifecycleToken = 0;
|
|
78
|
+
focusRestoreTarget = null;
|
|
79
|
+
portalVisible = false;
|
|
80
|
+
presenceState = 'closed';
|
|
81
|
+
presenceAnimationTimeout = 0;
|
|
82
|
+
pendingFocusRestoreTargetId = null;
|
|
83
|
+
shouldAnimatePresence = false;
|
|
84
|
+
suppressNextNativeCancel = false;
|
|
85
|
+
lastTouchClientY = 0;
|
|
86
|
+
handleDocumentFocusInBound = (event) => this.handleDocumentFocusIn(event);
|
|
26
87
|
constructor() {
|
|
27
88
|
super();
|
|
28
89
|
this.open = false;
|
|
@@ -35,11 +96,44 @@ export class CVDialog extends ReatomLitElement {
|
|
|
35
96
|
this.noHeader = false;
|
|
36
97
|
this.closable = true;
|
|
37
98
|
this.model = this.createModel();
|
|
99
|
+
this.portalVisible = this.open;
|
|
100
|
+
this.presenceState = this.open ? 'open' : 'closed';
|
|
38
101
|
}
|
|
39
102
|
static styles = [
|
|
40
103
|
css `
|
|
41
104
|
:host {
|
|
42
105
|
display: inline-block;
|
|
106
|
+
--cv-dialog-viewport-inset-top: var(--safe-area-top, env(safe-area-inset-top, 0px));
|
|
107
|
+
--cv-dialog-viewport-inset-right: var(--safe-area-right, env(safe-area-inset-right, 0px));
|
|
108
|
+
--cv-dialog-viewport-inset-bottom: calc(
|
|
109
|
+
var(--safe-area-bottom-active, var(--safe-area-bottom, env(safe-area-inset-bottom, 0px))) +
|
|
110
|
+
var(--visual-viewport-bottom-inset, 0px)
|
|
111
|
+
);
|
|
112
|
+
--cv-dialog-viewport-inset-left: var(--safe-area-left, env(safe-area-inset-left, 0px));
|
|
113
|
+
--cv-dialog-overlay-padding-block-start: calc(
|
|
114
|
+
var(--cv-dialog-padding-block, var(--cv-space-4, 16px)) + var(--cv-dialog-viewport-inset-top)
|
|
115
|
+
);
|
|
116
|
+
--cv-dialog-overlay-padding-inline-end: calc(
|
|
117
|
+
var(--cv-dialog-padding-inline, var(--cv-space-4, 16px)) + var(--cv-dialog-viewport-inset-right)
|
|
118
|
+
);
|
|
119
|
+
--cv-dialog-overlay-padding-block-end: calc(
|
|
120
|
+
var(--cv-dialog-padding-block, var(--cv-space-4, 16px)) + var(--cv-dialog-viewport-inset-bottom)
|
|
121
|
+
);
|
|
122
|
+
--cv-dialog-overlay-padding-inline-start: calc(
|
|
123
|
+
var(--cv-dialog-padding-inline, var(--cv-space-4, 16px)) + var(--cv-dialog-viewport-inset-left)
|
|
124
|
+
);
|
|
125
|
+
--cv-dialog-available-inline-size: calc(
|
|
126
|
+
100vw - var(--cv-dialog-overlay-padding-inline-start) - var(--cv-dialog-overlay-padding-inline-end)
|
|
127
|
+
);
|
|
128
|
+
--cv-dialog-available-block-size: calc(
|
|
129
|
+
100dvh - var(--cv-dialog-overlay-padding-block-start) - var(--cv-dialog-overlay-padding-block-end)
|
|
130
|
+
);
|
|
131
|
+
--cv-dialog-transition-duration: var(--cv-duration-fast, 120ms);
|
|
132
|
+
--cv-dialog-transition-easing-open: var(--cv-easing-decelerate, cubic-bezier(0, 0, 0.2, 1));
|
|
133
|
+
--cv-dialog-transition-easing-close: var(--cv-easing-accelerate, cubic-bezier(0.4, 0, 1, 1));
|
|
134
|
+
--cv-dialog-content-transition-property: opacity, transform;
|
|
135
|
+
--cv-dialog-content-closed-transform: translate3d(0, 8px, 0) scale(0.98);
|
|
136
|
+
--cv-dialog-content-open-transform: translate3d(0, 0, 0) scale(1);
|
|
43
137
|
}
|
|
44
138
|
|
|
45
139
|
[part='trigger'] {
|
|
@@ -60,25 +154,90 @@ export class CVDialog extends ReatomLitElement {
|
|
|
60
154
|
outline-offset: 1px;
|
|
61
155
|
}
|
|
62
156
|
|
|
157
|
+
.portal-shell {
|
|
158
|
+
position: fixed;
|
|
159
|
+
inset: 0;
|
|
160
|
+
margin: 0;
|
|
161
|
+
padding: 0;
|
|
162
|
+
border: none;
|
|
163
|
+
background: transparent;
|
|
164
|
+
color: inherit;
|
|
165
|
+
inline-size: 100vw;
|
|
166
|
+
max-inline-size: none;
|
|
167
|
+
block-size: 100dvh;
|
|
168
|
+
max-block-size: none;
|
|
169
|
+
overflow: visible;
|
|
170
|
+
transition:
|
|
171
|
+
display var(--cv-dialog-transition-duration) allow-discrete,
|
|
172
|
+
overlay var(--cv-dialog-transition-duration) allow-discrete;
|
|
173
|
+
transition-behavior: allow-discrete;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.portal-shell::backdrop {
|
|
177
|
+
background: transparent;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
.portal-shell[hidden] {
|
|
181
|
+
display: none;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
.popover-shell {
|
|
185
|
+
position: fixed;
|
|
186
|
+
inset: 0;
|
|
187
|
+
}
|
|
188
|
+
|
|
63
189
|
[part='overlay'] {
|
|
64
190
|
position: fixed;
|
|
65
191
|
inset: 0;
|
|
66
192
|
z-index: var(--cv-dialog-z-index, 40);
|
|
67
193
|
display: grid;
|
|
68
194
|
place-items: center;
|
|
69
|
-
background:
|
|
70
|
-
padding: var(--cv-
|
|
195
|
+
background: transparent;
|
|
196
|
+
padding-block: var(--cv-dialog-overlay-padding-block-start) var(--cv-dialog-overlay-padding-block-end);
|
|
197
|
+
padding-inline: var(--cv-dialog-overlay-padding-inline-start)
|
|
198
|
+
var(--cv-dialog-overlay-padding-inline-end);
|
|
199
|
+
overscroll-behavior: contain;
|
|
200
|
+
transition: display var(--cv-dialog-transition-duration) allow-discrete;
|
|
201
|
+
transition-behavior: allow-discrete;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
[part='overlay']::before {
|
|
205
|
+
content: '';
|
|
206
|
+
position: fixed;
|
|
207
|
+
inset: 0;
|
|
208
|
+
background: var(--cv-dialog-overlay-color, var(--cv-color-overlay));
|
|
209
|
+
opacity: 0;
|
|
210
|
+
transition: opacity var(--cv-dialog-transition-duration) var(--cv-dialog-transition-easing-open);
|
|
211
|
+
pointer-events: none;
|
|
71
212
|
}
|
|
72
213
|
|
|
73
214
|
[part='overlay'][hidden] {
|
|
74
215
|
display: none;
|
|
75
216
|
}
|
|
76
217
|
|
|
218
|
+
[part='overlay'][data-state='opening']::before,
|
|
219
|
+
[part='overlay'][data-state='open']::before {
|
|
220
|
+
opacity: 1;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
[part='overlay'][data-state='closing']::before {
|
|
224
|
+
opacity: 0;
|
|
225
|
+
transition-timing-function: var(--cv-dialog-transition-easing-close);
|
|
226
|
+
}
|
|
227
|
+
|
|
77
228
|
[part='content'] {
|
|
229
|
+
position: relative;
|
|
230
|
+
z-index: 1;
|
|
78
231
|
box-sizing: border-box;
|
|
79
|
-
inline-size: var(--cv-dialog-width, min(560px, calc(100vw - 32px)));
|
|
80
|
-
max-
|
|
232
|
+
inline-size: var(--cv-dialog-width, var(--cv-dialog-width-m, min(560px, calc(100vw - 32px))));
|
|
233
|
+
max-inline-size: min(var(--cv-dialog-width, 100%), var(--cv-dialog-available-inline-size));
|
|
234
|
+
max-block-size: min(
|
|
235
|
+
var(--cv-dialog-max-height, var(--cv-dialog-available-block-size)),
|
|
236
|
+
var(--cv-dialog-available-block-size)
|
|
237
|
+
);
|
|
81
238
|
overflow: auto;
|
|
239
|
+
overscroll-behavior: contain;
|
|
240
|
+
-webkit-overflow-scrolling: touch;
|
|
82
241
|
display: grid;
|
|
83
242
|
gap: var(--cv-space-3, 12px);
|
|
84
243
|
padding: var(--cv-space-4, 16px);
|
|
@@ -86,6 +245,24 @@ export class CVDialog extends ReatomLitElement {
|
|
|
86
245
|
border: 1px solid var(--cv-color-border, #2a3245);
|
|
87
246
|
background: var(--cv-color-surface-elevated, #1d2432);
|
|
88
247
|
color: var(--cv-color-text, #e8ecf6);
|
|
248
|
+
opacity: 0;
|
|
249
|
+
transform: var(--cv-dialog-content-closed-transform);
|
|
250
|
+
transition-property: var(--cv-dialog-content-transition-property);
|
|
251
|
+
transition-duration: var(--cv-dialog-transition-duration);
|
|
252
|
+
transition-timing-function: var(--cv-dialog-transition-easing-open);
|
|
253
|
+
will-change: opacity, transform;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
[part='content'][data-state='opening'],
|
|
257
|
+
[part='content'][data-state='open'] {
|
|
258
|
+
opacity: 1;
|
|
259
|
+
transform: var(--cv-dialog-content-open-transform);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
[part='content'][data-state='closing'] {
|
|
263
|
+
opacity: 0;
|
|
264
|
+
transform: var(--cv-dialog-content-closed-transform);
|
|
265
|
+
transition-timing-function: var(--cv-dialog-transition-easing-close);
|
|
89
266
|
}
|
|
90
267
|
|
|
91
268
|
[part='content']:focus-visible {
|
|
@@ -101,6 +278,12 @@ export class CVDialog extends ReatomLitElement {
|
|
|
101
278
|
align-items: start;
|
|
102
279
|
}
|
|
103
280
|
|
|
281
|
+
[part='header'],
|
|
282
|
+
[part='body'],
|
|
283
|
+
[part='footer'] {
|
|
284
|
+
min-inline-size: 0;
|
|
285
|
+
}
|
|
286
|
+
|
|
104
287
|
[part='title'] {
|
|
105
288
|
grid-column: 1;
|
|
106
289
|
margin: 0;
|
|
@@ -139,7 +322,7 @@ export class CVDialog extends ReatomLitElement {
|
|
|
139
322
|
|
|
140
323
|
[part='header-close']:hover {
|
|
141
324
|
color: var(--cv-color-text, #e8ecf6);
|
|
142
|
-
background:
|
|
325
|
+
background: var(--cv-color-surface-highlight);
|
|
143
326
|
}
|
|
144
327
|
|
|
145
328
|
[part='header-close']:focus-visible {
|
|
@@ -164,6 +347,31 @@ export class CVDialog extends ReatomLitElement {
|
|
|
164
347
|
gap: var(--cv-space-2, 8px);
|
|
165
348
|
justify-content: flex-end;
|
|
166
349
|
}
|
|
350
|
+
|
|
351
|
+
@starting-style {
|
|
352
|
+
[part='overlay'][data-state='opening']::before,
|
|
353
|
+
[part='overlay'][data-state='open']::before {
|
|
354
|
+
opacity: 0;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
[part='content'][data-state='opening'],
|
|
358
|
+
[part='content'][data-state='open'] {
|
|
359
|
+
opacity: 0;
|
|
360
|
+
transform: var(--cv-dialog-content-closed-transform);
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
@media (prefers-reduced-motion: reduce) {
|
|
365
|
+
:host {
|
|
366
|
+
--cv-dialog-transition-duration: 0ms;
|
|
367
|
+
--cv-dialog-content-closed-transform: none;
|
|
368
|
+
--cv-dialog-content-open-transform: none;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
[part='content'] {
|
|
372
|
+
transform: none;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
167
375
|
`,
|
|
168
376
|
];
|
|
169
377
|
static define() {
|
|
@@ -173,10 +381,13 @@ export class CVDialog extends ReatomLitElement {
|
|
|
173
381
|
}
|
|
174
382
|
connectedCallback() {
|
|
175
383
|
super.connectedCallback();
|
|
384
|
+
this.warnAboutDeprecatedTriggerSlot();
|
|
176
385
|
this.syncOutsideFocusListener();
|
|
177
386
|
this.syncScrollLock();
|
|
178
387
|
}
|
|
179
388
|
disconnectedCallback() {
|
|
389
|
+
this.clearPresenceAnimationQueue();
|
|
390
|
+
this.closeNativeShells();
|
|
180
391
|
super.disconnectedCallback();
|
|
181
392
|
this.syncOutsideFocusListener(true);
|
|
182
393
|
this.releaseScrollLock();
|
|
@@ -192,6 +403,9 @@ export class CVDialog extends ReatomLitElement {
|
|
|
192
403
|
const wasOpen = this.model.state.isOpen();
|
|
193
404
|
this.model = this.createModel(wasOpen);
|
|
194
405
|
}
|
|
406
|
+
if (changedProperties.has('open') && this.open && changedProperties.get('open') !== true) {
|
|
407
|
+
this.captureFocusRestoreTarget();
|
|
408
|
+
}
|
|
195
409
|
if (changedProperties.has('open') && this.model.state.isOpen() !== this.open) {
|
|
196
410
|
if (this.open) {
|
|
197
411
|
this.model.actions.open();
|
|
@@ -200,21 +414,57 @@ export class CVDialog extends ReatomLitElement {
|
|
|
200
414
|
this.model.actions.close();
|
|
201
415
|
}
|
|
202
416
|
}
|
|
417
|
+
if (changedProperties.has('open')) {
|
|
418
|
+
this.clearPresenceAnimationQueue();
|
|
419
|
+
if (this.open) {
|
|
420
|
+
this.portalVisible = true;
|
|
421
|
+
this.presenceState = this.hasUpdated ? 'opening' : 'open';
|
|
422
|
+
}
|
|
423
|
+
else {
|
|
424
|
+
this.presenceState = 'closing';
|
|
425
|
+
if (!this.hasUpdated) {
|
|
426
|
+
this.portalVisible = false;
|
|
427
|
+
this.presenceState = 'closed';
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
this.shouldAnimatePresence = this.hasUpdated;
|
|
431
|
+
}
|
|
203
432
|
}
|
|
204
433
|
updated(changedProperties) {
|
|
205
434
|
super.updated(changedProperties);
|
|
435
|
+
this.warnAboutDeprecatedTriggerSlot();
|
|
436
|
+
this.syncTopLayerVisibility();
|
|
206
437
|
this.syncOutsideFocusListener();
|
|
207
438
|
this.syncScrollLock();
|
|
439
|
+
this.syncRenderedPresenceState();
|
|
208
440
|
if (changedProperties.has('open')) {
|
|
209
441
|
const previousOpen = changedProperties.get('open');
|
|
442
|
+
const hasLogicalTransition = previousOpen !== undefined && previousOpen !== this.open;
|
|
210
443
|
if (this.suppressLifecycleFromUpdate) {
|
|
211
444
|
this.suppressLifecycleFromUpdate = false;
|
|
212
445
|
}
|
|
213
|
-
else if (
|
|
446
|
+
else if (hasLogicalTransition) {
|
|
214
447
|
this.dispatchLifecycleTransition(this.open);
|
|
215
448
|
}
|
|
449
|
+
if (hasLogicalTransition && previousOpen === true && this.open === false) {
|
|
450
|
+
this.queueFocusRestore(this.model.state.restoreTargetId());
|
|
451
|
+
}
|
|
452
|
+
if (hasLogicalTransition) {
|
|
453
|
+
if (this.shouldAnimatePresence) {
|
|
454
|
+
if (this.open) {
|
|
455
|
+
this.startOpenPresenceTransition();
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
this.startClosePresenceTransition();
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
else {
|
|
462
|
+
this.finishPresenceTransition(this.open);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
this.shouldAnimatePresence = false;
|
|
216
466
|
if (this.open) {
|
|
217
|
-
this.focusInitialTarget();
|
|
467
|
+
queueMicrotask(() => this.focusInitialTarget());
|
|
218
468
|
}
|
|
219
469
|
}
|
|
220
470
|
}
|
|
@@ -230,6 +480,112 @@ export class CVDialog extends ReatomLitElement {
|
|
|
230
480
|
initialFocusId: this.initialFocusId || undefined,
|
|
231
481
|
});
|
|
232
482
|
}
|
|
483
|
+
clearPresenceAnimationQueue() {
|
|
484
|
+
if (this.presenceAnimationTimeout) {
|
|
485
|
+
window.clearTimeout(this.presenceAnimationTimeout);
|
|
486
|
+
this.presenceAnimationTimeout = 0;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
startOpenPresenceTransition() {
|
|
490
|
+
if (!this.open)
|
|
491
|
+
return;
|
|
492
|
+
this.presenceState = 'open';
|
|
493
|
+
this.syncRenderedPresenceState();
|
|
494
|
+
const duration = this.getPresenceTransitionDuration();
|
|
495
|
+
if (duration === 0) {
|
|
496
|
+
this.finishPresenceTransition(true);
|
|
497
|
+
return;
|
|
498
|
+
}
|
|
499
|
+
const token = this.lifecycleToken;
|
|
500
|
+
this.presenceAnimationTimeout = window.setTimeout(() => {
|
|
501
|
+
this.presenceAnimationTimeout = 0;
|
|
502
|
+
if (!this.open || token !== this.lifecycleToken)
|
|
503
|
+
return;
|
|
504
|
+
this.finishPresenceTransition(true);
|
|
505
|
+
}, duration);
|
|
506
|
+
}
|
|
507
|
+
startClosePresenceTransition() {
|
|
508
|
+
const duration = this.getPresenceTransitionDuration();
|
|
509
|
+
if (duration === 0) {
|
|
510
|
+
this.finishPresenceTransition(false);
|
|
511
|
+
return;
|
|
512
|
+
}
|
|
513
|
+
const token = this.lifecycleToken;
|
|
514
|
+
this.presenceAnimationTimeout = window.setTimeout(() => {
|
|
515
|
+
this.presenceAnimationTimeout = 0;
|
|
516
|
+
if (this.open || token !== this.lifecycleToken)
|
|
517
|
+
return;
|
|
518
|
+
this.finishPresenceTransition(false);
|
|
519
|
+
}, duration);
|
|
520
|
+
}
|
|
521
|
+
finishPresenceTransition(open) {
|
|
522
|
+
if (this.open !== open)
|
|
523
|
+
return;
|
|
524
|
+
if (open) {
|
|
525
|
+
this.portalVisible = true;
|
|
526
|
+
this.presenceState = 'open';
|
|
527
|
+
this.syncRenderedPresenceState();
|
|
528
|
+
this.dispatchLifecycleEvent('cv-after-show');
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
this.portalVisible = false;
|
|
532
|
+
this.presenceState = 'closed';
|
|
533
|
+
this.syncTopLayerVisibility();
|
|
534
|
+
this.syncScrollLock();
|
|
535
|
+
this.syncRenderedPresenceState();
|
|
536
|
+
this.restoreQueuedFocus();
|
|
537
|
+
this.dispatchLifecycleEvent('cv-after-hide');
|
|
538
|
+
}
|
|
539
|
+
syncRenderedPresenceState() {
|
|
540
|
+
const shell = this.getCurrentPortalShell();
|
|
541
|
+
const overlay = this.getPortalOverlay();
|
|
542
|
+
const content = this.getContentElement();
|
|
543
|
+
if (shell) {
|
|
544
|
+
shell.dataset['state'] = this.presenceState;
|
|
545
|
+
shell.hidden = !this.portalVisible;
|
|
546
|
+
}
|
|
547
|
+
if (overlay) {
|
|
548
|
+
overlay.hidden = !this.portalVisible;
|
|
549
|
+
overlay.dataset['state'] = this.presenceState;
|
|
550
|
+
}
|
|
551
|
+
if (content) {
|
|
552
|
+
content.dataset['state'] = this.presenceState;
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
shouldLockScrollForPresence() {
|
|
556
|
+
return this.portalVisible && this.modal;
|
|
557
|
+
}
|
|
558
|
+
getPresenceTransitionDuration() {
|
|
559
|
+
return Math.max(this.readTransitionDuration(this.getPortalOverlay()), this.readTransitionDuration(this.getContentElement()));
|
|
560
|
+
}
|
|
561
|
+
readTransitionDuration(element) {
|
|
562
|
+
if (!element)
|
|
563
|
+
return 0;
|
|
564
|
+
const styles = getComputedStyle(element);
|
|
565
|
+
const durations = this.parseTimeValues(styles.transitionDuration);
|
|
566
|
+
const delays = this.parseTimeValues(styles.transitionDelay);
|
|
567
|
+
const transitionCount = Math.max(durations.length, delays.length);
|
|
568
|
+
let maxDuration = 0;
|
|
569
|
+
for (let index = 0; index < transitionCount; index += 1) {
|
|
570
|
+
const duration = durations[index] ?? durations[durations.length - 1] ?? 0;
|
|
571
|
+
const delay = delays[index] ?? delays[delays.length - 1] ?? 0;
|
|
572
|
+
maxDuration = Math.max(maxDuration, duration + delay);
|
|
573
|
+
}
|
|
574
|
+
return maxDuration;
|
|
575
|
+
}
|
|
576
|
+
parseTimeValues(value) {
|
|
577
|
+
return value
|
|
578
|
+
.split(',')
|
|
579
|
+
.map((entry) => entry.trim())
|
|
580
|
+
.filter(Boolean)
|
|
581
|
+
.map((entry) => {
|
|
582
|
+
if (entry.endsWith('ms'))
|
|
583
|
+
return Number.parseFloat(entry);
|
|
584
|
+
if (entry.endsWith('s'))
|
|
585
|
+
return Number.parseFloat(entry) * 1000;
|
|
586
|
+
return Number.parseFloat(entry) || 0;
|
|
587
|
+
});
|
|
588
|
+
}
|
|
233
589
|
captureState() {
|
|
234
590
|
return {
|
|
235
591
|
open: this.model.state.isOpen(),
|
|
@@ -257,13 +613,8 @@ export class CVDialog extends ReatomLitElement {
|
|
|
257
613
|
}));
|
|
258
614
|
}
|
|
259
615
|
dispatchLifecycleTransition(open) {
|
|
260
|
-
|
|
616
|
+
++this.lifecycleToken;
|
|
261
617
|
this.dispatchLifecycleEvent(open ? 'cv-show' : 'cv-hide');
|
|
262
|
-
this.updateComplete.then(() => {
|
|
263
|
-
if (this.lifecycleToken !== token)
|
|
264
|
-
return;
|
|
265
|
-
this.dispatchLifecycleEvent(open ? 'cv-after-show' : 'cv-after-hide');
|
|
266
|
-
});
|
|
267
618
|
}
|
|
268
619
|
applyInteractionResult(previous) {
|
|
269
620
|
const nextOpen = this.model.state.isOpen();
|
|
@@ -278,23 +629,149 @@ export class CVDialog extends ReatomLitElement {
|
|
|
278
629
|
else {
|
|
279
630
|
this.open = nextOpen;
|
|
280
631
|
}
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
632
|
+
if (!nextOpen) {
|
|
633
|
+
this.queueFocusRestore(previous.restoreTargetId);
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
captureFocusRestoreTarget() {
|
|
637
|
+
this.focusRestoreTarget = getFocusRestoreTarget();
|
|
638
|
+
}
|
|
639
|
+
queueFocusRestore(restoreTargetId) {
|
|
640
|
+
this.pendingFocusRestoreTargetId = restoreTargetId;
|
|
641
|
+
}
|
|
642
|
+
restoreQueuedFocus() {
|
|
643
|
+
this.restoreFocus(this.pendingFocusRestoreTargetId);
|
|
644
|
+
this.pendingFocusRestoreTargetId = null;
|
|
645
|
+
}
|
|
646
|
+
restoreFocus(restoreTargetId) {
|
|
647
|
+
const target = (this.focusRestoreTarget?.isConnected ? this.focusRestoreTarget : null) ??
|
|
648
|
+
(restoreTargetId
|
|
649
|
+
? (this.shadowRoot?.querySelector(`[id="${restoreTargetId}"]`) ?? null)
|
|
650
|
+
: null);
|
|
651
|
+
target?.focus({ preventScroll: true });
|
|
652
|
+
this.focusRestoreTarget = null;
|
|
653
|
+
}
|
|
654
|
+
getPortalOverlay() {
|
|
655
|
+
return this.shadowRoot?.querySelector('[part="overlay"]');
|
|
656
|
+
}
|
|
657
|
+
getContentElement() {
|
|
658
|
+
return this.shadowRoot?.querySelector('[part="content"]');
|
|
659
|
+
}
|
|
660
|
+
getModalShell() {
|
|
661
|
+
return this.shadowRoot?.querySelector('dialog.portal-shell');
|
|
662
|
+
}
|
|
663
|
+
getPopoverShell() {
|
|
664
|
+
return this.shadowRoot?.querySelector('.popover-shell');
|
|
665
|
+
}
|
|
666
|
+
getCurrentPortalShell() {
|
|
667
|
+
return this.modal ? this.getModalShell() : this.getPopoverShell();
|
|
668
|
+
}
|
|
669
|
+
isPopoverOpen(shell) {
|
|
670
|
+
return shell.dataset['popoverOpen'] === 'true';
|
|
671
|
+
}
|
|
672
|
+
openPopoverShell(shell) {
|
|
673
|
+
shell.hidden = false;
|
|
674
|
+
if (typeof shell.showPopover === 'function') {
|
|
675
|
+
try {
|
|
676
|
+
shell.showPopover();
|
|
677
|
+
}
|
|
678
|
+
catch {
|
|
679
|
+
// noop fallback for tests or unsupported environments
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
shell.dataset['popoverOpen'] = 'true';
|
|
683
|
+
}
|
|
684
|
+
closePopoverShell(shell) {
|
|
685
|
+
if (typeof shell.hidePopover === 'function' && this.isPopoverOpen(shell)) {
|
|
686
|
+
try {
|
|
687
|
+
shell.hidePopover();
|
|
688
|
+
}
|
|
689
|
+
catch {
|
|
690
|
+
// noop fallback for tests or unsupported environments
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
delete shell.dataset['popoverOpen'];
|
|
694
|
+
shell.hidden = true;
|
|
695
|
+
}
|
|
696
|
+
syncTopLayerVisibility() {
|
|
697
|
+
const modalShell = this.getModalShell();
|
|
698
|
+
const popoverShell = this.getPopoverShell();
|
|
699
|
+
if (this.modal) {
|
|
700
|
+
if (popoverShell) {
|
|
701
|
+
this.closePopoverShell(popoverShell);
|
|
702
|
+
}
|
|
703
|
+
if (!modalShell)
|
|
704
|
+
return;
|
|
705
|
+
if (this.portalVisible) {
|
|
706
|
+
modalShell.hidden = false;
|
|
707
|
+
if (!modalShell.open) {
|
|
708
|
+
try {
|
|
709
|
+
modalShell.showModal();
|
|
710
|
+
}
|
|
711
|
+
catch {
|
|
712
|
+
modalShell.setAttribute('open', '');
|
|
713
|
+
}
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
else {
|
|
717
|
+
if (modalShell.open) {
|
|
718
|
+
try {
|
|
719
|
+
modalShell.close();
|
|
720
|
+
}
|
|
721
|
+
catch {
|
|
722
|
+
modalShell.removeAttribute('open');
|
|
723
|
+
}
|
|
724
|
+
}
|
|
725
|
+
modalShell.hidden = true;
|
|
726
|
+
}
|
|
727
|
+
return;
|
|
728
|
+
}
|
|
729
|
+
if (modalShell?.open) {
|
|
730
|
+
try {
|
|
731
|
+
modalShell.close();
|
|
732
|
+
}
|
|
733
|
+
catch {
|
|
734
|
+
modalShell.removeAttribute('open');
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
if (modalShell) {
|
|
738
|
+
modalShell.hidden = true;
|
|
739
|
+
}
|
|
740
|
+
if (!popoverShell)
|
|
741
|
+
return;
|
|
742
|
+
if (this.portalVisible) {
|
|
743
|
+
this.openPopoverShell(popoverShell);
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
this.closePopoverShell(popoverShell);
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
closeNativeShells() {
|
|
750
|
+
const modalShell = this.getModalShell();
|
|
751
|
+
const popoverShell = this.getPopoverShell();
|
|
752
|
+
if (modalShell?.open) {
|
|
753
|
+
try {
|
|
754
|
+
modalShell.close();
|
|
755
|
+
}
|
|
756
|
+
catch {
|
|
757
|
+
modalShell.removeAttribute('open');
|
|
758
|
+
}
|
|
759
|
+
}
|
|
760
|
+
if (popoverShell) {
|
|
761
|
+
this.closePopoverShell(popoverShell);
|
|
285
762
|
}
|
|
286
763
|
}
|
|
287
764
|
syncOutsideFocusListener(forceOff = false) {
|
|
288
765
|
const shouldListen = !forceOff && this.open;
|
|
289
766
|
if (shouldListen) {
|
|
290
|
-
document.addEventListener('focusin', this.
|
|
767
|
+
document.addEventListener('focusin', this.handleDocumentFocusInBound);
|
|
291
768
|
}
|
|
292
769
|
else {
|
|
293
|
-
document.removeEventListener('focusin', this.
|
|
770
|
+
document.removeEventListener('focusin', this.handleDocumentFocusInBound);
|
|
294
771
|
}
|
|
295
772
|
}
|
|
296
773
|
syncScrollLock() {
|
|
297
|
-
if (!this.
|
|
774
|
+
if (!this.shouldLockScrollForPresence()) {
|
|
298
775
|
this.releaseScrollLock();
|
|
299
776
|
return;
|
|
300
777
|
}
|
|
@@ -314,27 +791,87 @@ export class CVDialog extends ReatomLitElement {
|
|
|
314
791
|
const contentProps = this.model.contracts.getContentProps();
|
|
315
792
|
const requestedId = contentProps['data-initial-focus'];
|
|
316
793
|
if (requestedId) {
|
|
317
|
-
const explicit = this
|
|
318
|
-
this.shadowRoot?.querySelector(`#${requestedId}`);
|
|
794
|
+
const explicit = findComposedElementById(this, requestedId) ?? findComposedElementById(this.renderRoot, requestedId);
|
|
319
795
|
if (explicit) {
|
|
320
796
|
explicit.focus();
|
|
321
797
|
return;
|
|
322
798
|
}
|
|
323
799
|
}
|
|
324
|
-
|
|
325
|
-
|
|
800
|
+
this.getContentElement()?.focus();
|
|
801
|
+
}
|
|
802
|
+
getTouchClientY(event) {
|
|
803
|
+
const touch = event.touches[0] ?? event.changedTouches[0];
|
|
804
|
+
return touch?.clientY ?? null;
|
|
326
805
|
}
|
|
327
|
-
|
|
806
|
+
isVerticallyScrollable(element) {
|
|
807
|
+
const styles = window.getComputedStyle(element);
|
|
808
|
+
const overflowY = styles.overflowY || styles.overflow;
|
|
809
|
+
return /(auto|scroll|overlay)/.test(overflowY) && element.scrollHeight > element.clientHeight;
|
|
810
|
+
}
|
|
811
|
+
collectScrollableContentAncestors(event, content) {
|
|
812
|
+
const path = event.composedPath();
|
|
813
|
+
const contentIndex = path.indexOf(content);
|
|
814
|
+
if (contentIndex >= 0) {
|
|
815
|
+
return path
|
|
816
|
+
.slice(0, contentIndex + 1)
|
|
817
|
+
.filter((node) => node instanceof HTMLElement);
|
|
818
|
+
}
|
|
819
|
+
const { target } = event;
|
|
820
|
+
const eventTarget = target instanceof HTMLElement
|
|
821
|
+
? target
|
|
822
|
+
: target instanceof Node && target.parentElement instanceof HTMLElement
|
|
823
|
+
? target.parentElement
|
|
824
|
+
: null;
|
|
825
|
+
if (!eventTarget || !content.contains(eventTarget))
|
|
826
|
+
return [];
|
|
827
|
+
const ancestors = [];
|
|
828
|
+
let current = eventTarget;
|
|
829
|
+
while (current) {
|
|
830
|
+
ancestors.push(current);
|
|
831
|
+
if (current === content)
|
|
832
|
+
return ancestors;
|
|
833
|
+
current = current.parentElement;
|
|
834
|
+
}
|
|
835
|
+
return [];
|
|
836
|
+
}
|
|
837
|
+
canScrollDialogContent(event, deltaY) {
|
|
838
|
+
const content = this.getContentElement();
|
|
839
|
+
if (!content)
|
|
840
|
+
return false;
|
|
841
|
+
for (const current of this.collectScrollableContentAncestors(event, content)) {
|
|
842
|
+
if (!this.isVerticallyScrollable(current))
|
|
843
|
+
continue;
|
|
844
|
+
const maxScrollTop = current.scrollHeight - current.clientHeight;
|
|
845
|
+
if (deltaY < 0 && current.scrollTop > 0)
|
|
846
|
+
return true;
|
|
847
|
+
if (deltaY > 0 && current.scrollTop < maxScrollTop)
|
|
848
|
+
return true;
|
|
849
|
+
if (deltaY === 0)
|
|
850
|
+
return true;
|
|
851
|
+
}
|
|
852
|
+
return false;
|
|
853
|
+
}
|
|
854
|
+
warnAboutDeprecatedTriggerSlot() {
|
|
855
|
+
if (hasWarnedAboutTriggerSlot)
|
|
856
|
+
return;
|
|
857
|
+
if (!this.querySelector('[slot="trigger"]'))
|
|
858
|
+
return;
|
|
859
|
+
hasWarnedAboutTriggerSlot = true;
|
|
860
|
+
globalThis['console']?.warn?.('[cv-dialog] slot="trigger" is deprecated. Control dialog visibility with `.open` or use createDialogController/dialogService.');
|
|
861
|
+
}
|
|
862
|
+
handleDocumentFocusIn(event) {
|
|
328
863
|
if (!this.open)
|
|
329
864
|
return;
|
|
330
865
|
const path = event.composedPath();
|
|
331
|
-
|
|
866
|
+
const overlay = this.getPortalOverlay();
|
|
867
|
+
if (path.includes(this) || (overlay && path.includes(overlay)))
|
|
332
868
|
return;
|
|
333
869
|
const previous = this.captureState();
|
|
334
870
|
this.model.actions.handleOutsideFocus();
|
|
335
871
|
this.applyInteractionResult(previous);
|
|
336
|
-
}
|
|
872
|
+
}
|
|
337
873
|
handleTriggerClick() {
|
|
874
|
+
this.captureFocusRestoreTarget();
|
|
338
875
|
const previous = this.captureState();
|
|
339
876
|
this.model.contracts.getTriggerProps().onClick();
|
|
340
877
|
this.applyInteractionResult(previous);
|
|
@@ -343,6 +880,7 @@ export class CVDialog extends ReatomLitElement {
|
|
|
343
880
|
if (event.key === 'Enter' || event.key === ' ' || event.key === 'Spacebar') {
|
|
344
881
|
event.preventDefault();
|
|
345
882
|
}
|
|
883
|
+
this.captureFocusRestoreTarget();
|
|
346
884
|
const previous = this.captureState();
|
|
347
885
|
this.model.contracts.getTriggerProps().onKeyDown({ key: event.key });
|
|
348
886
|
this.applyInteractionResult(previous);
|
|
@@ -354,60 +892,93 @@ export class CVDialog extends ReatomLitElement {
|
|
|
354
892
|
this.model.contracts.getOverlayProps().onPointerDownOutside();
|
|
355
893
|
this.applyInteractionResult(previous);
|
|
356
894
|
}
|
|
895
|
+
handleOverlayTouchStart(event) {
|
|
896
|
+
const clientY = this.getTouchClientY(event);
|
|
897
|
+
if (clientY !== null) {
|
|
898
|
+
this.lastTouchClientY = clientY;
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
handleOverlayTouchMove(event) {
|
|
902
|
+
if (!this.open || !this.modal)
|
|
903
|
+
return;
|
|
904
|
+
const clientY = this.getTouchClientY(event);
|
|
905
|
+
if (clientY === null) {
|
|
906
|
+
event.preventDefault();
|
|
907
|
+
return;
|
|
908
|
+
}
|
|
909
|
+
const deltaY = this.lastTouchClientY - clientY;
|
|
910
|
+
this.lastTouchClientY = clientY;
|
|
911
|
+
if (this.canScrollDialogContent(event, deltaY))
|
|
912
|
+
return;
|
|
913
|
+
event.preventDefault();
|
|
914
|
+
}
|
|
915
|
+
handleOverlayWheel(event) {
|
|
916
|
+
if (!this.open || !this.modal)
|
|
917
|
+
return;
|
|
918
|
+
if (this.canScrollDialogContent(event, event.deltaY))
|
|
919
|
+
return;
|
|
920
|
+
event.preventDefault();
|
|
921
|
+
}
|
|
357
922
|
handleContentKeyDown(event) {
|
|
358
923
|
if (event.key === 'Escape') {
|
|
359
924
|
event.preventDefault();
|
|
925
|
+
this.suppressNextNativeCancel = true;
|
|
360
926
|
}
|
|
361
927
|
const previous = this.captureState();
|
|
362
928
|
this.model.contracts.getContentProps().onKeyDown({ key: event.key });
|
|
363
929
|
this.applyInteractionResult(previous);
|
|
364
930
|
}
|
|
931
|
+
handleNativeCancel(event) {
|
|
932
|
+
event.preventDefault();
|
|
933
|
+
if (this.suppressNextNativeCancel) {
|
|
934
|
+
this.suppressNextNativeCancel = false;
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
if (!this.closeOnEscape)
|
|
938
|
+
return;
|
|
939
|
+
const previous = this.captureState();
|
|
940
|
+
this.model.contracts.getContentProps().onKeyDown({ key: 'Escape' });
|
|
941
|
+
this.applyInteractionResult(previous);
|
|
942
|
+
}
|
|
365
943
|
handleHeaderCloseClick() {
|
|
366
944
|
const previous = this.captureState();
|
|
367
945
|
this.model.contracts.getHeaderCloseButtonProps().onClick();
|
|
368
946
|
this.applyInteractionResult(previous);
|
|
369
947
|
}
|
|
370
|
-
|
|
371
|
-
const triggerProps = this.model.contracts.getTriggerProps();
|
|
372
|
-
const overlayProps = this.model.contracts.getOverlayProps();
|
|
948
|
+
renderContent() {
|
|
373
949
|
const contentProps = this.model.contracts.getContentProps();
|
|
374
950
|
const titleProps = this.model.contracts.getTitleProps();
|
|
375
951
|
const descriptionProps = this.model.contracts.getDescriptionProps();
|
|
376
952
|
const headerCloseProps = this.model.contracts.getHeaderCloseButtonProps();
|
|
377
953
|
return html `
|
|
378
|
-
<button
|
|
379
|
-
id=${triggerProps.id}
|
|
380
|
-
role=${triggerProps.role}
|
|
381
|
-
tabindex=${triggerProps.tabindex}
|
|
382
|
-
aria-haspopup=${triggerProps['aria-haspopup']}
|
|
383
|
-
aria-expanded=${triggerProps['aria-expanded']}
|
|
384
|
-
aria-controls=${triggerProps['aria-controls']}
|
|
385
|
-
part="trigger"
|
|
386
|
-
type="button"
|
|
387
|
-
@click=${this.handleTriggerClick}
|
|
388
|
-
@keydown=${this.handleTriggerKeyDown}
|
|
389
|
-
>
|
|
390
|
-
<slot name="trigger">Open dialog</slot>
|
|
391
|
-
</button>
|
|
392
|
-
|
|
393
954
|
<div
|
|
394
|
-
id=${
|
|
395
|
-
|
|
396
|
-
|
|
955
|
+
id=${this.model.contracts.getOverlayProps().id}
|
|
956
|
+
class="cv-u-discrete-presence"
|
|
957
|
+
data-open=${this.model.contracts.getOverlayProps()['data-open']}
|
|
958
|
+
data-state=${this.presenceState}
|
|
959
|
+
?hidden=${!this.portalVisible}
|
|
397
960
|
part="overlay"
|
|
398
961
|
@mousedown=${this.handleOverlayPointerDown}
|
|
962
|
+
@click=${this.handleOverlayPointerDown}
|
|
963
|
+
@touchstart=${this.handleOverlayTouchStart}
|
|
964
|
+
@touchmove=${this.handleOverlayTouchMove}
|
|
965
|
+
@wheel=${this.handleOverlayWheel}
|
|
399
966
|
>
|
|
400
967
|
<section
|
|
401
968
|
id=${contentProps.id}
|
|
969
|
+
class="cv-u-discrete-presence"
|
|
402
970
|
role=${contentProps.role}
|
|
403
971
|
tabindex=${contentProps.tabindex}
|
|
404
972
|
aria-modal=${contentProps['aria-modal']}
|
|
405
973
|
aria-labelledby=${contentProps['aria-labelledby'] ?? nothing}
|
|
406
974
|
aria-describedby=${contentProps['aria-describedby'] ?? nothing}
|
|
407
975
|
data-initial-focus=${contentProps['data-initial-focus'] ?? nothing}
|
|
976
|
+
data-state=${this.presenceState}
|
|
408
977
|
part="content"
|
|
409
978
|
@keydown=${this.handleContentKeyDown}
|
|
410
979
|
>
|
|
980
|
+
<slot name="before-header"></slot>
|
|
981
|
+
|
|
411
982
|
<header part="header" ?hidden=${this.noHeader}>
|
|
412
983
|
<h2 id=${titleProps.id} part="title">
|
|
413
984
|
<slot name="title">Dialog</slot>
|
|
@@ -417,31 +988,31 @@ export class CVDialog extends ReatomLitElement {
|
|
|
417
988
|
</p>
|
|
418
989
|
${this.closable
|
|
419
990
|
? html `
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
>
|
|
429
|
-
<slot name="header-close"
|
|
430
|
-
><svg
|
|
431
|
-
width="16"
|
|
432
|
-
height="16"
|
|
433
|
-
viewBox="0 0 16 16"
|
|
434
|
-
fill="none"
|
|
435
|
-
stroke="currentColor"
|
|
436
|
-
stroke-width="1.5"
|
|
437
|
-
stroke-linecap="round"
|
|
438
|
-
>
|
|
439
|
-
<line x1="4" y1="4" x2="12" y2="12" />
|
|
440
|
-
<line x1="12" y1="4" x2="4" y2="12" />
|
|
441
|
-
</svg></slot
|
|
991
|
+
<button
|
|
992
|
+
id=${headerCloseProps.id}
|
|
993
|
+
role=${headerCloseProps.role}
|
|
994
|
+
tabindex=${headerCloseProps.tabindex}
|
|
995
|
+
aria-label=${headerCloseProps['aria-label']}
|
|
996
|
+
type="button"
|
|
997
|
+
part="header-close"
|
|
998
|
+
@click=${this.handleHeaderCloseClick}
|
|
442
999
|
>
|
|
443
|
-
|
|
444
|
-
|
|
1000
|
+
<slot name="header-close"
|
|
1001
|
+
><svg
|
|
1002
|
+
width="16"
|
|
1003
|
+
height="16"
|
|
1004
|
+
viewBox="0 0 16 16"
|
|
1005
|
+
fill="none"
|
|
1006
|
+
stroke="currentColor"
|
|
1007
|
+
stroke-width="1.5"
|
|
1008
|
+
stroke-linecap="round"
|
|
1009
|
+
>
|
|
1010
|
+
<line x1="4" y1="4" x2="12" y2="12" />
|
|
1011
|
+
<line x1="12" y1="4" x2="4" y2="12" />
|
|
1012
|
+
</svg></slot
|
|
1013
|
+
>
|
|
1014
|
+
</button>
|
|
1015
|
+
`
|
|
445
1016
|
: nothing}
|
|
446
1017
|
</header>
|
|
447
1018
|
|
|
@@ -456,4 +1027,45 @@ export class CVDialog extends ReatomLitElement {
|
|
|
456
1027
|
</div>
|
|
457
1028
|
`;
|
|
458
1029
|
}
|
|
1030
|
+
render() {
|
|
1031
|
+
const triggerProps = this.model.contracts.getTriggerProps();
|
|
1032
|
+
return html `
|
|
1033
|
+
<button
|
|
1034
|
+
id=${triggerProps.id}
|
|
1035
|
+
role=${triggerProps.role}
|
|
1036
|
+
tabindex=${triggerProps.tabindex}
|
|
1037
|
+
aria-haspopup=${triggerProps['aria-haspopup']}
|
|
1038
|
+
aria-expanded=${triggerProps['aria-expanded']}
|
|
1039
|
+
aria-controls=${triggerProps['aria-controls']}
|
|
1040
|
+
part="trigger"
|
|
1041
|
+
type="button"
|
|
1042
|
+
@click=${this.handleTriggerClick}
|
|
1043
|
+
@keydown=${this.handleTriggerKeyDown}
|
|
1044
|
+
>
|
|
1045
|
+
<slot name="trigger">Open dialog</slot>
|
|
1046
|
+
</button>
|
|
1047
|
+
|
|
1048
|
+
${this.modal
|
|
1049
|
+
? html `
|
|
1050
|
+
<dialog
|
|
1051
|
+
class="portal-shell cv-u-discrete-presence"
|
|
1052
|
+
data-state=${this.presenceState}
|
|
1053
|
+
?hidden=${!this.portalVisible}
|
|
1054
|
+
@cancel=${this.handleNativeCancel}
|
|
1055
|
+
>
|
|
1056
|
+
${this.renderContent()}
|
|
1057
|
+
</dialog>
|
|
1058
|
+
`
|
|
1059
|
+
: html `
|
|
1060
|
+
<div
|
|
1061
|
+
class="portal-shell popover-shell cv-u-discrete-presence"
|
|
1062
|
+
popover="manual"
|
|
1063
|
+
data-state=${this.presenceState}
|
|
1064
|
+
?hidden=${!this.portalVisible}
|
|
1065
|
+
>
|
|
1066
|
+
${this.renderContent()}
|
|
1067
|
+
</div>
|
|
1068
|
+
`}
|
|
1069
|
+
`;
|
|
1070
|
+
}
|
|
459
1071
|
}
|