@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.
Files changed (192) hide show
  1. package/LICENSE +19 -6
  2. package/README.md +1 -0
  3. package/dist/components/cv-accordion-item.d.ts +1 -1
  4. package/dist/components/cv-accordion.d.ts +1 -1
  5. package/dist/components/cv-accordion.js +2 -1
  6. package/dist/components/cv-alert-dialog.d.ts +1 -1
  7. package/dist/components/cv-alert-dialog.js +17 -2
  8. package/dist/components/cv-alert.d.ts +1 -1
  9. package/dist/components/cv-alert.js +2 -1
  10. package/dist/components/cv-badge.d.ts +1 -1
  11. package/dist/components/cv-badge.js +2 -1
  12. package/dist/components/cv-bottom-sheet.d.ts +127 -0
  13. package/dist/components/cv-bottom-sheet.js +513 -0
  14. package/dist/components/cv-breadcrumb-item.d.ts +1 -1
  15. package/dist/components/cv-breadcrumb-item.js +1 -1
  16. package/dist/components/cv-breadcrumb.d.ts +1 -1
  17. package/dist/components/cv-breadcrumb.js +2 -1
  18. package/dist/components/cv-button.d.ts +23 -1
  19. package/dist/components/cv-button.js +194 -37
  20. package/dist/components/cv-callout.d.ts +8 -1
  21. package/dist/components/cv-callout.js +18 -1
  22. package/dist/components/cv-card.d.ts +1 -1
  23. package/dist/components/cv-card.js +2 -2
  24. package/dist/components/cv-carousel-slide.d.ts +1 -1
  25. package/dist/components/cv-carousel.d.ts +1 -1
  26. package/dist/components/cv-carousel.js +2 -1
  27. package/dist/components/cv-checkbox.d.ts +1 -1
  28. package/dist/components/cv-combobox-group.d.ts +1 -1
  29. package/dist/components/cv-combobox-option.d.ts +1 -1
  30. package/dist/components/cv-combobox-option.js +2 -2
  31. package/dist/components/cv-combobox.d.ts +3 -1
  32. package/dist/components/cv-combobox.js +49 -8
  33. package/dist/components/cv-command-item.d.ts +1 -1
  34. package/dist/components/cv-command-item.js +2 -2
  35. package/dist/components/cv-command-palette.d.ts +1 -1
  36. package/dist/components/cv-command-palette.js +21 -1
  37. package/dist/components/cv-context-menu.d.ts +1 -1
  38. package/dist/components/cv-context-menu.js +2 -1
  39. package/dist/components/cv-copy-button.d.ts +37 -9
  40. package/dist/components/cv-copy-button.js +129 -41
  41. package/dist/components/cv-date-picker.d.ts +1 -1
  42. package/dist/components/cv-date-picker.js +20 -1
  43. package/dist/components/cv-dialog.d.ts +44 -2
  44. package/dist/components/cv-dialog.js +686 -74
  45. package/dist/components/cv-disclosure.d.ts +1 -1
  46. package/dist/components/cv-disclosure.js +2 -1
  47. package/dist/components/cv-drawer.d.ts +29 -1
  48. package/dist/components/cv-drawer.js +229 -4
  49. package/dist/components/cv-feed-article.d.ts +1 -1
  50. package/dist/components/cv-feed-article.js +2 -1
  51. package/dist/components/cv-feed.d.ts +1 -1
  52. package/dist/components/cv-feed.js +2 -1
  53. package/dist/components/cv-grid-cell.d.ts +1 -1
  54. package/dist/components/cv-grid-cell.js +3 -3
  55. package/dist/components/cv-grid-column.d.ts +1 -1
  56. package/dist/components/cv-grid-column.js +1 -1
  57. package/dist/components/cv-grid-row.d.ts +1 -1
  58. package/dist/components/cv-grid.d.ts +1 -1
  59. package/dist/components/cv-grid.js +2 -1
  60. package/dist/components/cv-guidance-anchor.d.ts +47 -0
  61. package/dist/components/cv-guidance-anchor.js +113 -0
  62. package/dist/components/cv-guidance-panel.d.ts +29 -0
  63. package/dist/components/cv-guidance-panel.js +245 -0
  64. package/dist/components/cv-icon.d.ts +2 -1
  65. package/dist/components/cv-icon.js +28 -3
  66. package/dist/components/cv-input.d.ts +7 -1
  67. package/dist/components/cv-input.js +33 -1
  68. package/dist/components/cv-landmark.d.ts +1 -1
  69. package/dist/components/cv-landmark.js +2 -1
  70. package/dist/components/cv-link.d.ts +1 -1
  71. package/dist/components/cv-link.js +2 -1
  72. package/dist/components/cv-listbox-group.d.ts +1 -1
  73. package/dist/components/cv-listbox.d.ts +1 -1
  74. package/dist/components/cv-listbox.js +2 -1
  75. package/dist/components/cv-menu-button.d.ts +24 -1
  76. package/dist/components/cv-menu-button.js +226 -18
  77. package/dist/components/cv-menu-group.d.ts +1 -1
  78. package/dist/components/cv-menu-item.d.ts +1 -1
  79. package/dist/components/cv-menu-item.js +6 -2
  80. package/dist/components/cv-menu.d.ts +1 -1
  81. package/dist/components/cv-menu.js +21 -1
  82. package/dist/components/cv-meter.d.ts +1 -1
  83. package/dist/components/cv-meter.js +6 -22
  84. package/dist/components/cv-number.d.ts +1 -1
  85. package/dist/components/cv-option.d.ts +1 -1
  86. package/dist/components/cv-option.js +3 -9
  87. package/dist/components/cv-popover-positioning.d.ts +22 -0
  88. package/dist/components/cv-popover-positioning.js +112 -0
  89. package/dist/components/cv-popover.d.ts +45 -8
  90. package/dist/components/cv-popover.js +395 -113
  91. package/dist/components/cv-progress-ring.d.ts +1 -1
  92. package/dist/components/cv-progress-ring.js +2 -1
  93. package/dist/components/cv-progress.d.ts +8 -1
  94. package/dist/components/cv-progress.js +41 -10
  95. package/dist/components/cv-radio-group.d.ts +1 -1
  96. package/dist/components/cv-radio.d.ts +1 -1
  97. package/dist/components/cv-radio.js +1 -1
  98. package/dist/components/cv-select-group.d.ts +1 -1
  99. package/dist/components/cv-select-option.d.ts +1 -1
  100. package/dist/components/cv-select-option.js +2 -2
  101. package/dist/components/cv-select.d.ts +1 -1
  102. package/dist/components/cv-select.js +28 -1
  103. package/dist/components/cv-sidebar-item.d.ts +1 -1
  104. package/dist/components/cv-sidebar.d.ts +1 -1
  105. package/dist/components/cv-sidebar.js +3 -2
  106. package/dist/components/cv-slider-multi-thumb.d.ts +1 -1
  107. package/dist/components/cv-slider-multi-thumb.js +2 -1
  108. package/dist/components/cv-slider.d.ts +17 -4
  109. package/dist/components/cv-slider.js +63 -21
  110. package/dist/components/cv-spinbutton.d.ts +1 -1
  111. package/dist/components/cv-spinner.d.ts +1 -1
  112. package/dist/components/cv-spinner.js +2 -1
  113. package/dist/components/cv-switch.d.ts +1 -1
  114. package/dist/components/cv-tab-panel.d.ts +1 -1
  115. package/dist/components/cv-tab.d.ts +1 -1
  116. package/dist/components/cv-table-cell.d.ts +1 -1
  117. package/dist/components/cv-table-cell.js +1 -1
  118. package/dist/components/cv-table-column.d.ts +1 -1
  119. package/dist/components/cv-table-column.js +1 -1
  120. package/dist/components/cv-table-row.d.ts +1 -1
  121. package/dist/components/cv-table-row.js +1 -4
  122. package/dist/components/cv-table.d.ts +1 -3
  123. package/dist/components/cv-table.js +4 -11
  124. package/dist/components/cv-tabs.d.ts +1 -1
  125. package/dist/components/cv-tabs.js +3 -2
  126. package/dist/components/cv-textarea.d.ts +11 -1
  127. package/dist/components/cv-textarea.js +33 -0
  128. package/dist/components/cv-toast-region.d.ts +1 -1
  129. package/dist/components/cv-toast-region.js +2 -1
  130. package/dist/components/cv-toast.d.ts +1 -1
  131. package/dist/components/cv-toast.js +20 -27
  132. package/dist/components/cv-toolbar-item.d.ts +1 -1
  133. package/dist/components/cv-toolbar-separator.d.ts +1 -1
  134. package/dist/components/cv-toolbar.d.ts +1 -1
  135. package/dist/components/cv-toolbar.js +2 -1
  136. package/dist/components/cv-tooltip.d.ts +1 -1
  137. package/dist/components/cv-tooltip.js +2 -1
  138. package/dist/components/cv-treegrid-cell.d.ts +1 -1
  139. package/dist/components/cv-treegrid-cell.js +1 -1
  140. package/dist/components/cv-treegrid-column.d.ts +1 -1
  141. package/dist/components/cv-treegrid-column.js +1 -1
  142. package/dist/components/cv-treegrid-row.d.ts +1 -1
  143. package/dist/components/cv-treegrid-row.js +1 -1
  144. package/dist/components/cv-treegrid.d.ts +1 -1
  145. package/dist/components/cv-treegrid.js +4 -3
  146. package/dist/components/cv-treeitem.d.ts +1 -1
  147. package/dist/components/cv-treeitem.js +2 -2
  148. package/dist/components/cv-treeview.d.ts +1 -1
  149. package/dist/components/cv-treeview.js +2 -1
  150. package/dist/components/cv-window-splitter.d.ts +1 -1
  151. package/dist/components/cv-window-splitter.js +2 -1
  152. package/dist/components/index.d.ts +7 -0
  153. package/dist/components/index.js +3 -0
  154. package/dist/dialog/create-dialog-controller.d.ts +12 -4
  155. package/dist/dialog/create-dialog-controller.js +84 -22
  156. package/dist/dialog/index.d.ts +1 -1
  157. package/dist/index.d.ts +1 -1
  158. package/dist/reatom-lit/ReatomLitElement.d.ts +6 -3
  159. package/dist/reatom-lit/ReatomLitElement.js +18 -8
  160. package/dist/reatom-lit/createAfterRenderScheduler.d.ts +10 -0
  161. package/dist/reatom-lit/createAfterRenderScheduler.js +33 -0
  162. package/dist/reatom-lit/index.d.ts +2 -0
  163. package/dist/reatom-lit/index.js +1 -0
  164. package/dist/reatom-lit/watch.d.ts +1 -1
  165. package/dist/reatom-lit/withReatomElement.js +16 -2
  166. package/dist/register.js +4 -1
  167. package/dist/styles/component-styles.js +4 -0
  168. package/dist/styles/uno-generated.d.ts +2 -0
  169. package/dist/styles/uno-generated.js +1 -0
  170. package/dist/styles/uno-utilities.d.ts +5 -0
  171. package/dist/styles/uno-utilities.js +7 -0
  172. package/dist/theme/cv-theme-provider.d.ts +1 -1
  173. package/dist/theme/cv-theme-provider.js +2 -2
  174. package/dist/theme/tokens.css +619 -162
  175. package/package.json +9 -5
  176. package/specs/components/bottom-sheet.md +93 -0
  177. package/specs/components/button.md +8 -0
  178. package/specs/components/callout.md +8 -0
  179. package/specs/components/copy-button.md +54 -17
  180. package/specs/components/dialog.md +72 -43
  181. package/specs/components/drawer.md +18 -13
  182. package/specs/components/guidance-anchor.md +64 -0
  183. package/specs/components/guidance-panel.md +92 -0
  184. package/specs/components/input.md +7 -0
  185. package/specs/components/menu.md +8 -0
  186. package/specs/components/option.md +9 -9
  187. package/specs/components/progress.md +11 -0
  188. package/specs/components/sidebar.md +12 -12
  189. package/specs/components/table.md +13 -13
  190. package/specs/components/theme.md +13 -13
  191. package/specs/components/treegrid.md +15 -15
  192. package/specs/components/treeview.md +10 -10
@@ -1,7 +1,55 @@
1
1
  import { createDialog } from '@chromvoid/headless-ui/dialog';
2
- import { css, html, nothing } from 'lit';
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: { type: Boolean, attribute: 'close-on-outside-pointer', reflect: true },
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: var(--cv-dialog-overlay-color, color-mix(in oklab, black 56%, transparent));
70
- padding: var(--cv-space-4, 16px);
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-block-size: var(--cv-dialog-max-height, calc(100dvh - 32px));
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: color-mix(in oklab, var(--cv-color-text, #e8ecf6) 8%, transparent);
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 (previousOpen !== undefined && previousOpen !== this.open) {
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
- const token = ++this.lifecycleToken;
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
- const restoreTargetId = this.model.state.restoreTargetId();
282
- if (restoreTargetId && previous.restoreTargetId !== restoreTargetId) {
283
- const trigger = this.shadowRoot?.querySelector(`[id="${restoreTargetId}"]`);
284
- trigger?.focus();
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.handleDocumentFocusIn);
767
+ document.addEventListener('focusin', this.handleDocumentFocusInBound);
291
768
  }
292
769
  else {
293
- document.removeEventListener('focusin', this.handleDocumentFocusIn);
770
+ document.removeEventListener('focusin', this.handleDocumentFocusInBound);
294
771
  }
295
772
  }
296
773
  syncScrollLock() {
297
- if (!this.model.state.shouldLockScroll()) {
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.querySelector(`#${requestedId}`) ??
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
- const content = this.shadowRoot?.querySelector('[part="content"]');
325
- content?.focus();
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
- handleDocumentFocusIn = (event) => {
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
- if (path.includes(this))
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
- render() {
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=${overlayProps.id}
395
- data-open=${overlayProps['data-open']}
396
- ?hidden=${overlayProps.hidden}
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
- <button
421
- id=${headerCloseProps.id}
422
- role=${headerCloseProps.role}
423
- tabindex=${headerCloseProps.tabindex}
424
- aria-label=${headerCloseProps['aria-label']}
425
- type="button"
426
- part="header-close"
427
- @click=${this.handleHeaderCloseClick}
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
- </button>
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
  }