@shortfuse/materialdesignweb 0.7.6 → 0.8.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 (114) hide show
  1. package/README.md +57 -68
  2. package/components/Badge.js +2 -2
  3. package/components/BottomAppBar.js +3 -5
  4. package/components/Box.js +33 -3
  5. package/components/Button.js +48 -21
  6. package/components/Button.md +9 -9
  7. package/components/Card.js +9 -16
  8. package/components/Checkbox.js +45 -36
  9. package/components/CheckboxIcon.js +2 -2
  10. package/components/Chip.js +1 -1
  11. package/components/Dialog.js +228 -359
  12. package/components/DialogActions.js +2 -2
  13. package/components/Divider.js +3 -3
  14. package/components/ExtendedFab.js +4 -8
  15. package/components/Fab.js +1 -2
  16. package/components/FilterChip.js +4 -4
  17. package/components/Headline.js +1 -1
  18. package/components/Icon.js +8 -8
  19. package/components/IconButton.js +9 -14
  20. package/components/Input.js +273 -1
  21. package/components/Layout.js +485 -16
  22. package/components/List.js +6 -4
  23. package/components/ListItem.js +12 -12
  24. package/components/ListOption.js +21 -5
  25. package/components/Listbox.js +239 -0
  26. package/components/Menu.js +77 -526
  27. package/components/MenuItem.js +12 -14
  28. package/components/Nav.js +0 -2
  29. package/components/NavBar.js +8 -79
  30. package/components/NavDrawer.js +12 -11
  31. package/components/NavDrawerItem.js +2 -1
  32. package/components/NavItem.js +18 -8
  33. package/components/NavRail.js +15 -7
  34. package/components/NavRailItem.js +3 -1
  35. package/components/Popup.js +20 -0
  36. package/components/Progress.js +24 -23
  37. package/components/Radio.js +42 -35
  38. package/components/RadioIcon.js +3 -3
  39. package/components/Ripple.js +2 -3
  40. package/components/Search.js +85 -0
  41. package/components/SegmentedButton.js +1 -10
  42. package/components/SegmentedButtonGroup.js +16 -10
  43. package/components/Select.js +4 -4
  44. package/components/Shape.js +1 -1
  45. package/components/Slider.js +43 -50
  46. package/components/Snackbar.js +4 -5
  47. package/components/Surface.js +3 -3
  48. package/components/Switch.js +55 -21
  49. package/components/SwitchIcon.js +10 -8
  50. package/components/Tab.js +11 -9
  51. package/components/TabContent.js +4 -3
  52. package/components/TabList.js +2 -2
  53. package/components/TabPanel.js +11 -8
  54. package/components/TextArea.js +38 -35
  55. package/components/Tooltip.js +2 -2
  56. package/components/TopAppBar.js +65 -147
  57. package/core/Composition.js +985 -628
  58. package/core/CompositionAdapter.js +315 -0
  59. package/core/CustomElement.js +153 -90
  60. package/core/DomAdapter.js +586 -0
  61. package/core/ICustomElement.d.ts +2 -2
  62. package/core/css.js +8 -7
  63. package/core/customTypes.js +53 -31
  64. package/{utils → core}/jsonMergePatch.js +36 -14
  65. package/core/observe.js +111 -57
  66. package/core/optimizations.js +23 -0
  67. package/core/template.js +17 -11
  68. package/core/test.js +126 -0
  69. package/core/typings.d.ts +11 -5
  70. package/core/uid.js +13 -0
  71. package/dist/index.min.js +83 -152
  72. package/dist/index.min.js.map +4 -4
  73. package/dist/meta.json +1 -1
  74. package/mixins/AriaReflectorMixin.js +1 -2
  75. package/mixins/AriaToolbarMixin.js +2 -3
  76. package/mixins/ControlMixin.js +25 -17
  77. package/mixins/DensityMixin.js +0 -1
  78. package/mixins/FlexableMixin.js +1 -2
  79. package/mixins/FormAssociatedMixin.js +13 -10
  80. package/mixins/InputMixin.js +2 -9
  81. package/mixins/KeyboardNavMixin.js +14 -1
  82. package/mixins/PopupMixin.js +757 -0
  83. package/mixins/RTLObserverMixin.js +0 -1
  84. package/mixins/ResizeObserverMixin.js +0 -1
  85. package/mixins/RippleMixin.js +3 -4
  86. package/mixins/ScrollListenerMixin.js +41 -32
  87. package/mixins/SemiStickyMixin.js +151 -0
  88. package/mixins/ShapeMixin.js +29 -24
  89. package/mixins/StateMixin.js +11 -6
  90. package/mixins/SurfaceMixin.js +3 -57
  91. package/mixins/TextFieldMixin.js +57 -65
  92. package/mixins/ThemableMixin.js +78 -156
  93. package/mixins/TooltipTriggerMixin.js +7 -13
  94. package/mixins/TouchTargetMixin.js +4 -3
  95. package/package.json +9 -5
  96. package/theming/index.js +1 -1
  97. package/theming/themableMixinLoader.js +12 -0
  98. package/utils/{hct → material-color}/blend.js +8 -10
  99. package/utils/{hct → material-color/hct}/Cam16.js +196 -69
  100. package/utils/{hct → material-color/hct}/Hct.js +61 -19
  101. package/utils/{hct → material-color/hct}/ViewingConditions.js +3 -3
  102. package/utils/{hct → material-color/hct}/hctSolver.js +9 -16
  103. package/utils/{hct → material-color}/helper.js +11 -18
  104. package/utils/{hct → material-color/palettes}/CorePalette.js +79 -19
  105. package/utils/{hct → material-color/palettes}/TonalPalette.js +12 -4
  106. package/utils/material-color/scheme/Scheme.js +376 -0
  107. package/utils/{hct/colorUtils.js → material-color/utils/color.js} +61 -1
  108. package/utils/popup.js +46 -25
  109. package/components/ListSelect.js +0 -220
  110. package/components/Option.js +0 -91
  111. package/components/Pane.js +0 -281
  112. package/core/identify.js +0 -40
  113. package/utils/hct/Scheme.js +0 -587
  114. /package/utils/{hct/mathUtils.js → material-color/utils/math.js} +0 -0
@@ -0,0 +1,757 @@
1
+ /**
2
+ * @typedef {Object} PopupStack
3
+ * @prop {Element} element
4
+ * @prop {Element} previousFocus
5
+ * @prop {boolean} [centered=false]
6
+ * @prop {Record<string, any>} [state]
7
+ * @prop {Record<string, any>} [previousState]
8
+ * @prop {MouseEvent|PointerEvent|HTMLElement|Element} [originalEvent]
9
+ * @prop {any} [pendingResizeOperation]
10
+ * @prop {window['history']['scrollRestoration']} [scrollRestoration]
11
+ */
12
+
13
+ import CustomElement from '../core/CustomElement.js';
14
+ import { attemptFocus } from '../core/dom.js';
15
+ import { canAnchorPopup } from '../utils/popup.js';
16
+
17
+ CustomElement
18
+ .extend()
19
+ .observe({
20
+ hidden: 'boolean',
21
+ })
22
+ .html`<div id=scroll-blocker></div>`
23
+ .css`
24
+ :host {
25
+ position: fixed;
26
+ inset: 0;
27
+
28
+ display: block;
29
+ overflow: overlay;
30
+
31
+ overscroll-behavior: none;
32
+ overscroll-behavior: contain;
33
+ scrollbar-color: transparent transparent;
34
+ scrollbar-width: thin;
35
+
36
+ opacity: 0;
37
+
38
+ z-index: 23;
39
+
40
+ background-color: rgb(var(--mdw-color__scrim));
41
+
42
+ animation: fade-in 200ms forwards ease-out;
43
+
44
+ will-change: opacity;
45
+ }
46
+
47
+ :host([hidden]) {
48
+ animation-name: fade-out;
49
+ animation-timing-function: ease-in;
50
+ }
51
+
52
+ :host([invisible]) {
53
+ background: transparent;
54
+ }
55
+
56
+ #scroll-blocker {
57
+ position: absolute;
58
+ top: 0;
59
+ left: 0;
60
+
61
+ display: block;
62
+
63
+ height: 200%;
64
+ width: 200%;
65
+ }
66
+
67
+ @keyframes fade-in {
68
+ from {
69
+ opacity: 0;
70
+ }
71
+
72
+ to {
73
+ opacity: 0.38;
74
+ }
75
+ }
76
+
77
+ @keyframes fade-out {
78
+ from {
79
+ opacity: 0.38;
80
+ }
81
+
82
+ to {
83
+ opacity: 0;
84
+ }
85
+ }
86
+ `
87
+ .events({
88
+ animationend() {
89
+ if (this.hidden) this.remove();
90
+ },
91
+ })
92
+ .autoRegister('mdw-scrim');
93
+
94
+ const supportsHTMLDialogElement = typeof HTMLDialogElement !== 'undefined';
95
+ /** @type {PopupStack[]} */
96
+ const OPEN_POPUPS = [];
97
+
98
+ /**
99
+ * @return {void}
100
+ */
101
+ function onWindowResize() {
102
+ const lastOpenPopup = OPEN_POPUPS.at(-1);
103
+ if (!lastOpenPopup || !lastOpenPopup.originalEvent) {
104
+ return;
105
+ }
106
+ if (lastOpenPopup.pendingResizeOperation) {
107
+ cancelAnimationFrame(lastOpenPopup.pendingResizeOperation);
108
+ }
109
+ lastOpenPopup.pendingResizeOperation = requestAnimationFrame(() => {
110
+ lastOpenPopup.element.updatePopupPosition(lastOpenPopup.originalEvent);
111
+ lastOpenPopup.pendingResizeOperation = null;
112
+ });
113
+ }
114
+
115
+ /**
116
+ * @param {PopStateEvent} event
117
+ */
118
+ function onPopState(event) {
119
+ if (!event.state) return;
120
+ const lastOpenPopup = OPEN_POPUPS.at(-1);
121
+ if (!lastOpenPopup || !lastOpenPopup.previousState) {
122
+ return;
123
+ }
124
+ if ((lastOpenPopup.previousState === event.state) || Object.keys(event.state)
125
+ .every((key) => event.state[key] === lastOpenPopup.previousState[key])) {
126
+ // Close (cancel event) can be prevented. Fire and check if prevented
127
+ const cancelEvent = new Event('cancel', { cancelable: true });
128
+ if (lastOpenPopup.element.dispatchEvent(cancelEvent)) {
129
+ lastOpenPopup.element.close();
130
+ } else {
131
+ // Revert pop state by pushing state again
132
+ window.history.pushState(lastOpenPopup.state, lastOpenPopup.state.title);
133
+ }
134
+ }
135
+ }
136
+
137
+ /** @param {BeforeUnloadEvent} event */
138
+ function onBeforeUnload(event) {
139
+ if (!OPEN_POPUPS.length) return;
140
+ console.warn('Popup was open during page unload (refresh?).');
141
+ }
142
+
143
+ /**
144
+ * @param {typeof import('../core/CustomElement.js').default} Base
145
+ */
146
+ export default function PopupMixin(Base) {
147
+ return Base
148
+ .observe({
149
+ open: 'boolean',
150
+ modal: 'boolean',
151
+ _isNativeModal: 'boolean',
152
+ scrollable: 'boolean',
153
+ matchSourceWidth: 'boolean',
154
+ _currentFlow: 'string',
155
+ flow: {
156
+ type: 'string',
157
+ /** @type {'corner'|'adjacent'|'overflow'|'vcenter'|'hcenter'|'center'} */
158
+ value: null,
159
+ },
160
+ })
161
+ .set({
162
+ returnValue: '',
163
+ delegatesFocus: true,
164
+ _closing: false,
165
+ _useScrim: false,
166
+ })
167
+ .define({
168
+ _dialog() {
169
+ return /** @type {HTMLDialogElement} */ (this.refs.dialog);
170
+ },
171
+ })
172
+ .methods({
173
+ /**
174
+ * @param {DOMRect|Element} [anchor]
175
+ * @return {void}
176
+ */
177
+ updatePopupPosition(anchor) {
178
+ const flow = this._currentFlow ?? this.flow;
179
+ this.style.setProperty('min-width', 'none');
180
+ this.style.setProperty('min-height', 'none');
181
+ this.style.setProperty('width', 'auto');
182
+ this.style.setProperty('height', 'auto');
183
+ this.style.setProperty('max-width', 'none');
184
+ this.style.setProperty('max-height', 'none');
185
+ this.style.setProperty('top', '0');
186
+ this.style.setProperty('left', '0');
187
+ this.style.setProperty('--mdw-popup__x-offset', '0');
188
+ this.style.setProperty('--mdw-popup__y-offset', '0');
189
+
190
+ const layoutElement = this._isNativeModal ? this._dialog : this;
191
+ layoutElement.style.setProperty('width', 'auto');
192
+ layoutElement.style.setProperty('height', 'auto');
193
+
194
+ const width = (anchor && this.matchSourceWidth)
195
+ ? anchor.clientWidth
196
+ : 56 * Math.ceil(layoutElement.clientWidth / 56);
197
+
198
+ this.style.setProperty('width', `${width}px`);
199
+
200
+ const height = layoutElement.clientHeight;
201
+ layoutElement.style.removeProperty('width');
202
+ layoutElement.style.removeProperty('height');
203
+
204
+ const initialRect = this.getBoundingClientRect();
205
+ /** @type {import('../utils/popup.js').CanAnchorPopUpOptions} */
206
+ const anchorOptions = {
207
+ anchor: anchor == null
208
+ ? this.getBoundingClientRect()
209
+ : (anchor instanceof Element ? anchor.getBoundingClientRect() : anchor),
210
+ width,
211
+ height,
212
+ margin: 0,
213
+ };
214
+
215
+ const isPageRTL = (getComputedStyle(this).direction === 'rtl');
216
+ const xStart = isPageRTL ? 'right' : 'left';
217
+ const xEnd = isPageRTL ? 'left' : 'right';
218
+
219
+ /* Automatic Positioning
220
+ *
221
+ * Positions:
222
+ *
223
+ * 3 7 4
224
+ * ┌─────────┐
225
+ * │ │
226
+ * 5 │ 9 │ 6
227
+ * │ │
228
+ * └─────────┘
229
+ * 1 8 2
230
+ *
231
+ * 1: Bottom Left
232
+ * 2: Bottom Right
233
+ * 3: Top Left
234
+ * 4: Top Right
235
+ * 5: VCenter Left
236
+ * 6: VCenter Right
237
+ * 7: HCenter Top
238
+ * 8: HCenter Bottom
239
+ * 9: VCenter HCenter
240
+ *
241
+ * Directions:
242
+ * a - Down LTR
243
+ * b - Down RTL
244
+ * c - Up LTR
245
+ * d - Up RTL
246
+ * e - LTR
247
+ * f - RTL
248
+ * g - Down
249
+ * h - Up
250
+ * i - Center
251
+ *
252
+ *
253
+ * 16 total combos
254
+ * 1a 1b 1c 1d └↘ └↙ └↗ └↖
255
+ * 2a 2b 2c 2d ┘↘ ┘↙ ┘↗ ┘↖
256
+ * 3a 3b 3c 3d ┌↘ ┌↙ ┌↗ ┌↖
257
+ * 4a 4b 4c 4d ┐↘ ┐↙ ┐↗ ┐↖
258
+ *
259
+ * Avoid using opposite angle
260
+ *
261
+ * 1a XX 1c 1d └↘ ██ └↗ └↖
262
+ * XX 2b 2c 2d ██ ┘↙ ┘↗ ┘↖
263
+ * 1a 3b 3c XX ┌↘ ┌↙ ┌↗ ██
264
+ * 4a 4b XX 4d ┐↘ ┐↙ ██ ┐↖
265
+ *
266
+ *
267
+ * Preference Order:
268
+ * - Flow from corner 1a 2b 3c 4d └↘ ┘↙ ┌↗ ┐↖
269
+ * - Open adjacent to target 4a 3b 2c 1d ┐↘ ┌↙ ┘↗ └↖
270
+ * - Overlay target 3a 4b 1c 2d ┌↘ ┐↙ └↗ ┘↖
271
+ * - Open from horizontal side 5e 6f │→ │←
272
+ * - Open from center 9i █·
273
+ */
274
+
275
+ /** @type {import('../utils/popup.js').CanAnchorPopUpOptions[]} */
276
+ const preferences = [
277
+ ((flow ?? 'corner') === 'corner') ? [
278
+ { clientY: 'bottom', clientX: xStart },
279
+ { clientY: 'bottom', clientX: xEnd },
280
+ { clientY: 'top', clientX: xStart },
281
+ { clientY: 'top', clientX: xEnd },
282
+ ] : [],
283
+ ((flow ?? 'adjacent') === 'adjacent') ? [
284
+ { clientY: 'top', clientX: xEnd, directionX: xEnd, directionY: 'down' },
285
+ { clientY: 'top', clientX: xStart, directionX: xStart, directionY: 'down' },
286
+ { clientY: 'bottom', clientX: xEnd, directionX: xEnd, directionY: 'up' },
287
+ { clientY: 'bottom', clientX: xStart, directionX: xStart, directionY: 'up' },
288
+ ] : [],
289
+ ((flow ?? 'overlay') === 'overlay') ? [
290
+ { clientY: 'top', clientX: xStart, directionX: xEnd, directionY: 'down' },
291
+ { clientY: 'top', clientX: xEnd, directionX: xStart, directionY: 'down' },
292
+ { clientY: 'bottom', clientX: xStart, directionX: xEnd, directionY: 'up' },
293
+ { clientY: 'bottom', clientX: xEnd, directionX: xStart, directionY: 'up' },
294
+ ] : [],
295
+ ((flow ?? 'vcenter') === 'vcenter') ? [
296
+ { clientY: 'center', clientX: xEnd, directionX: xEnd, directionY: 'center' },
297
+ { clientY: 'center', clientX: xStart, directionX: xStart, directionY: 'center' },
298
+ ] : [],
299
+ ((flow ?? 'hcenter') === 'hcenter') ? [
300
+ { clientY: 'bottom', clientX: 'center', directionX: 'center', directionY: 'down' },
301
+ { clientY: 'top', clientX: 'center', directionX: 'center', directionY: 'up' },
302
+ ] : [],
303
+ ((flow ?? 'center') === 'center') ? [
304
+ { clientY: 'center', clientX: 'center', directionX: 'center', directionY: 'center' },
305
+ ] : [],
306
+ ].flat();
307
+
308
+ let anchorResult;
309
+ for (const preference of preferences) {
310
+ const result = canAnchorPopup({
311
+ ...anchorOptions,
312
+ ...preference,
313
+ });
314
+ if (!anchorResult || anchorResult.visibility < result.visibility) {
315
+ anchorResult = result;
316
+ }
317
+ if (result.visibility === 1) break;
318
+ }
319
+
320
+ this.style.setProperty('top', `${anchorResult.top - initialRect.y}px`);
321
+ this.style.setProperty('left', `${anchorResult.left - initialRect.x}px`);
322
+ this.style.setProperty('min-width', `${anchorResult.right - anchorResult.left}px`);
323
+ this.style.setProperty('min-height', `${anchorResult.bottom - anchorResult.top}px`);
324
+ this.style.removeProperty('width');
325
+ this.style.removeProperty('height');
326
+ this.style.setProperty('max-width', `${anchorResult.right - anchorResult.left}px`);
327
+ this.style.setProperty('max-height', `${anchorResult.bottom - anchorResult.top}px`);
328
+ this.style.setProperty('transform-origin', `${anchorResult.transformOriginY} ${anchorResult.transformOriginX}`);
329
+ this.scrollIntoView();
330
+ },
331
+ /**
332
+ * @param {Event & {currentTarget: HTMLSlotElement}} event
333
+ * @return {void}
334
+ */
335
+ onSlotChange({ currentTarget }) {
336
+ const nodes = currentTarget.assignedNodes();
337
+ const hasContent = nodes.some((node) => (node.nodeType === node.ELEMENT_NODE)
338
+ || (node.nodeType === node.TEXT_NODE && node.nodeValue.trim().length));
339
+ currentTarget.toggleAttribute('slotted', hasContent);
340
+ },
341
+
342
+ /**
343
+ * @param {MouseEvent|PointerEvent|HTMLElement|Event} [source]
344
+ * @param {boolean} focus
345
+ * @param {string} flow
346
+ * @return {boolean} handled
347
+ */
348
+ showPopup(source, focus = true, flow = null) {
349
+ if (this.open) return false;
350
+ this.open = true;
351
+
352
+ // SCRIM
353
+ if (this._useScrim) {
354
+ document.body.append(this.refs.scrim);
355
+ this.refs.scrim.hidden = false;
356
+ } else {
357
+ this.refs.scrim.remove();
358
+ }
359
+
360
+ const previousFocus = source instanceof HTMLElement ? source : document.activeElement;
361
+
362
+ if (supportsHTMLDialogElement && focus) {
363
+ // Calling show will force focus which is not intended for non-modals
364
+ this._dialog.show();
365
+ }
366
+
367
+ this._currentFlow = flow;
368
+
369
+ // Short first, then move
370
+ // Native modals can fail update bounds on Chrome
371
+ this.updatePopupPosition(source);
372
+
373
+ const newState = { hash: Math.random().toString(36).slice(2, 18) };
374
+ let previousState = null;
375
+
376
+ if (!window.history.state) {
377
+ // Create new previous state
378
+ window.history.replaceState({
379
+ hash: Math.random().toString(36).slice(2, 18),
380
+ }, document.title);
381
+ }
382
+ previousState = window.history.state;
383
+
384
+ const scrollRestoration = window.history.scrollRestoration;
385
+ window.history.scrollRestoration = 'manual';
386
+ window.history.pushState(newState, document.title);
387
+ console.debug('Popup pushed page');
388
+ window.addEventListener('popstate', onPopState);
389
+ window.addEventListener('beforeunload', onBeforeUnload);
390
+
391
+ window.addEventListener('resize', onWindowResize);
392
+ window.addEventListener('scroll', onWindowResize);
393
+
394
+ OPEN_POPUPS.push({
395
+ element: this,
396
+ previousFocus,
397
+ state: newState,
398
+ previousState,
399
+ originalEvent: source,
400
+ scrollRestoration,
401
+ });
402
+
403
+ // Overrideable
404
+ if (focus) {
405
+ console.log('focusing!');
406
+ this.focus();
407
+ }
408
+
409
+ return true;
410
+ },
411
+ /**
412
+ * @param {MouseEvent|PointerEvent|HTMLElement|Event} [source]
413
+ * @param {boolean} [focus]
414
+ * @param {string} [flow]
415
+ * @return {boolean} handled
416
+ */
417
+ showModal(source, focus, flow) {
418
+ if (this.open) return false;
419
+ this.modal = true;
420
+ if (supportsHTMLDialogElement) {
421
+ this._dialog.showModal();
422
+ this._isNativeModal = true;
423
+ }
424
+ return this.showPopup(source, focus, flow);
425
+ },
426
+ /**
427
+ * @param {MouseEvent|PointerEvent|HTMLElement|Event} [source]
428
+ * @param {boolean} [focus]
429
+ * @param {string} [flow]
430
+ * @return {boolean} handled
431
+ */
432
+ show(source, focus, flow) {
433
+ // Auto-select type based on default platform convention
434
+ // Mac OS X / iPad does not expect clickthrough
435
+ if (navigator.userAgent.includes('Mac OS X')) {
436
+ return this.showModal(source, focus, flow);
437
+ }
438
+ return this.showPopup(source, focus, flow);
439
+ },
440
+ /**
441
+ * @param {any} [returnValue]
442
+ * @param {boolean} [returnFocus=true]
443
+ * @return {boolean} handled
444
+ */
445
+ close(returnValue = undefined, returnFocus = true) {
446
+ if (!this.open) return false;
447
+ if (this._closing) return false;
448
+ this._closing = true;
449
+ this.modal = false;
450
+
451
+ // SCRIM
452
+ this.refs.scrim.hidden = true;
453
+
454
+ if (this._isNativeModal) {
455
+ this._isNativeModal = false;
456
+ } else {
457
+ const main = document.querySelector('main');
458
+ if (main) {
459
+ main.removeAttribute('aria-hidden');
460
+ }
461
+ }
462
+ // if (this.dialogElement.getAttribute('aria-hidden') === 'true') return false;
463
+ if (supportsHTMLDialogElement && this._dialog.open) {
464
+ const previousFocus = document.activeElement;
465
+ // Closing a native dialog will return focus automatically.
466
+ this._dialog.close();
467
+ if (!attemptFocus(previousFocus, { preventScroll: true })) {
468
+ document.activeElement?.blur?.();
469
+ }
470
+ } else {
471
+ this._dialog.returnValue = returnValue;
472
+ }
473
+
474
+ // Will invoke observed attribute change: ('aria-hidden', 'true');
475
+ this.open = false;
476
+ this._currentFlow = null;
477
+
478
+ this.dispatchEvent(new Event('close'));
479
+
480
+ const len = OPEN_POPUPS.length;
481
+ for (let i = len - 1; i >= 0; i--) {
482
+ const entry = OPEN_POPUPS[i];
483
+ if (entry.element === this) {
484
+ if (entry.state && window.history
485
+ && window.history.state && entry.state.hash === window.history.state.hash) {
486
+ window.removeEventListener('popstate', onPopState);
487
+ window.history.back();
488
+ // Back does not set state immediately
489
+ // Needed to track submenu
490
+ // TODO: use window.history.go(indexDelta) instead for Safari (not Webkit) submenu support
491
+ window.history.replaceState(entry.previousState, document.title);
492
+ window.history.scrollRestoration = entry.scrollRestoration || 'auto';
493
+ window.addEventListener('popstate', onPopState);
494
+ } else {
495
+ console.warn('Menu state mismatch?', entry, window.history.state);
496
+ }
497
+ if (returnFocus) {
498
+ console.log('not returning focus');
499
+ entry.previousFocus?.focus?.({ preventScroll: true });
500
+ }
501
+ OPEN_POPUPS.splice(i, 1);
502
+ break;
503
+ } else if (this.contains(entry.element)) {
504
+ console.debug('Closing submenu first');
505
+ entry.element.close(false);
506
+ console.debug('Sub menu closed. Continuing...');
507
+ }
508
+ }
509
+ if (!OPEN_POPUPS.length) {
510
+ window.removeEventListener('popstate', onPopState);
511
+ window.removeEventListener('beforeunload', onBeforeUnload);
512
+ window.removeEventListener('resize', onWindowResize);
513
+ console.debug('All menus closed');
514
+ }
515
+ this._closing = false;
516
+ return true;
517
+ },
518
+ })
519
+ .expressions({
520
+ _ariaHidden({ open }) { return (open ? 'false' : 'true'); },
521
+ })
522
+ .html`
523
+ <mdw-scrim id=scrim tabindex=-1 aria-hidden=true></mdw-scrim>
524
+ <dialog id=dialog aria-modal=true role=dialog
525
+ aria-hidden={_ariaHidden} scrollable={scrollable}>
526
+ <slot id=slot on-slotchange={onSlotChange}></slot>
527
+ </dialog>
528
+ `
529
+ .css`
530
+ /* https://m3.material.io/components/dialogs/specs */
531
+
532
+ :host {
533
+ --mdw-popup__expand-duration: var(--mdw-motion-expand-duration, 250ms);
534
+ --mdw-popup__simple-duration: var(--mdw-motion-simple-duration, 100ms);
535
+ --mdw-popup__standard-easing: var(--mdw-motion-standard-easing, cubic-bezier(0.4, 0.0, 0.2, 1));
536
+ --mdw-popup__deceleration-easing: var(--mdw-motion-deceleration-easing, cubic-bezier(0.0, 0.0, 0.2, 1));
537
+ --mdw-popup__fade-in-duration: var(--mdw-motion-fade-in-duration, 150ms);
538
+ --mdw-popup__x-offset: -50%;
539
+ --mdw-popup__y-offset: -50%;
540
+
541
+ --mdw-shape__size: 28px;
542
+
543
+ --mdw-surface__shadow__resting: var(--mdw-surface__shadow__3);
544
+ --mdw-surface__shadow__raised: var(--mdw-surface__shadow__resting);
545
+ /* padding-inline: 12px; */
546
+
547
+ --mdw-bg: var(--mdw-color__surface);
548
+ --mdw-ink: var(--mdw-color__on-surface);
549
+
550
+ position: fixed;
551
+
552
+ /* stylelint-disable-next-line liberty/use-logical-spec */
553
+ top: 50%;
554
+ /* stylelint-disable-next-line liberty/use-logical-spec */
555
+ left: 50%;
556
+ align-self: center;
557
+ justify-self: center;
558
+
559
+ display: block;
560
+ overflow: auto;
561
+ -webkit-overflow-scrolling: touch;
562
+ overscroll-behavior: none;
563
+ overscroll-behavior: contain;
564
+
565
+ box-sizing: border-box;
566
+ block-size: auto;
567
+ min-block-size: none;
568
+ max-block-size: 100vh;
569
+ inline-size: auto;
570
+ min-inline-size: none;
571
+ max-inline-size: 100vw;
572
+
573
+
574
+ pointer-events: none;
575
+
576
+ opacity: 0;
577
+
578
+ transform: translateX(var(--mdw-popup__x-offset)) translateY(var(--mdw-popup__y-offset)) scale(0) ;
579
+ /* visiblity:hidden still registers events, hide from pointer with scale(0) */
580
+ transform-origin: top center;
581
+ visibility: hidden;
582
+ z-index: 24;
583
+
584
+ color: rgb(var(--mdw-ink));
585
+
586
+ font: var(--mdw-type__font);
587
+ letter-spacing: var(--mdw-type__letter-spacing);
588
+
589
+ transition-delay: 0s, 200ms, 200ms;
590
+ transition-duration: 200ms, 0s, 0s;
591
+ transition-property: opacity, transform, visibility;
592
+ transition-timing-function: ease-out;
593
+
594
+ will-change: display, transform;
595
+ will-change: opacity;
596
+
597
+ }
598
+
599
+ :host([open]) {
600
+ pointer-events: auto;
601
+
602
+ opacity: 1;
603
+
604
+ transform: translateX(var(--mdw-popup__x-offset)) translateY(var(--mdw-popup__y-offset)) scale(1);
605
+ visibility: visible;
606
+
607
+ transition-delay: 0s;
608
+ transition-duration: 0s;
609
+ transition-timing-function: ease-in;
610
+ }
611
+
612
+
613
+
614
+ #dialog {
615
+ position: static;
616
+ inset-block-start: 0;
617
+ inset-inline-start: 0;
618
+
619
+ display: contents;
620
+ align-items: inherit;
621
+ flex-direction: inherit;
622
+ gap: inherit;
623
+ justify-content: inherit;
624
+ justify-items: inherit;
625
+ place-items: inherit;
626
+
627
+ box-sizing: border-box;
628
+
629
+ block-size: inherit;
630
+
631
+
632
+ flex: inherit;
633
+ margin:0;
634
+ border: none;
635
+ padding: inherit;
636
+ padding: 0;
637
+
638
+ pointer-events: auto;
639
+
640
+ opacity: 1;
641
+
642
+ transform:inherit;
643
+ visibility: inherit;
644
+ /* visiblity:hidden still registers events, hide from pointer with scale(0) */
645
+ z-index: 24;
646
+
647
+ background-color: transparent;
648
+
649
+ color:inherit;
650
+
651
+ }
652
+
653
+ :host([scrollable]) {
654
+ overflow-y:auto;
655
+ }
656
+
657
+ #dialog::backdrop {
658
+ /** Use scrim instead */
659
+ background-color: transparent;
660
+ }
661
+
662
+ #dialog:modal {
663
+ position: inherit;
664
+ inset: inherit;
665
+
666
+ display: inherit;
667
+ align-items: inherit;
668
+ flex-direction: inherit;
669
+ gap: inherit;
670
+ justify-content: inherit;
671
+ justify-items: inherit;
672
+ place-items: inherit;
673
+
674
+ block-size: auto;
675
+ min-block-size: inherit;
676
+ max-block-size: inherit;
677
+
678
+ inline-size:auto;
679
+ min-inline-size: inherit;
680
+ max-inline-size: inherit;
681
+ flex: inherit;
682
+ padding: inherit;
683
+
684
+ pointer-events: auto;
685
+
686
+ transform: inherit;
687
+ visibility: inherit;
688
+ }
689
+
690
+ #dialog[scrollable][open] {
691
+ display: inherit;
692
+ align-items: inherit;
693
+ flex-direction: inherit;
694
+ gap: inherit;
695
+ justify-content: inherit;
696
+ justify-items: inherit;
697
+
698
+ place-items: inherit;
699
+
700
+ height: 100%;
701
+ max-height: none;
702
+ width: 100%;
703
+ max-width: none;
704
+
705
+ flex: 1;
706
+ }
707
+
708
+
709
+
710
+ #dialog[scrollable][open]:modal {
711
+ overflow:auto;
712
+
713
+ height:100%;
714
+ min-height: none;
715
+ max-height: inherit;
716
+ width:100%;
717
+ min-width: none;
718
+ max-width: inherit;
719
+ flex: inherit;
720
+ padding: inherit;
721
+ }
722
+
723
+
724
+ `
725
+ .childEvents({
726
+ dialog: {
727
+ cancel(event) {
728
+ event.stopPropagation();
729
+ const cancelEvent = new Event('cancel', { cancelable: true });
730
+ if (!this.dispatchEvent(cancelEvent)) {
731
+ event.preventDefault();
732
+ }
733
+ },
734
+ close(event) {
735
+ event.stopPropagation();
736
+ this.close(this.returnValue);
737
+ },
738
+ '~click'(event) {
739
+ // Track if click on backdrop
740
+ if (event.target !== event.currentTarget) return;
741
+ if (!this._isNativeModal) return;
742
+ if (event.offsetX >= 0 && event.offsetX < event.currentTarget.offsetWidth
743
+ && event.offsetY >= 0 && event.offsetY < event.currentTarget.offsetHeight) return;
744
+ const cancelEvent = new Event('cancel', { cancelable: true });
745
+ if (!this.dispatchEvent(cancelEvent)) return;
746
+ this.close();
747
+ },
748
+ },
749
+ scrim: {
750
+ '~click'() {
751
+ const cancelEvent = new Event('cancel', { cancelable: true });
752
+ if (!this.dispatchEvent(cancelEvent)) return;
753
+ this.close();
754
+ },
755
+ },
756
+ });
757
+ }