@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
@@ -1,41 +1,99 @@
1
1
  import './Button.js';
2
- import './Surface.js';
3
2
  import './Divider.js';
4
3
  import './Icon.js';
5
4
  import './DialogActions.js';
6
5
 
7
- import { handleTabKeyPress } from '../aria/modal.js';
8
6
  import CustomElement from '../core/CustomElement.js';
9
-
10
- /** @typedef {Object<string,any>} DialogStackState */
11
-
12
- /** @typedef {InstanceType<import('./Dialog.js').default>} Dialog */
7
+ import { attemptFocus } from '../core/dom.js';
8
+ import PopupMixin from '../mixins/PopupMixin.js';
9
+ import ShapeMixin from '../mixins/ShapeMixin.js';
10
+ import SurfaceMixin from '../mixins/SurfaceMixin.js';
11
+ import ThemableMixin from '../mixins/ThemableMixin.js';
13
12
 
14
13
  /**
15
- * @typedef {Object} DialogStack
16
- * @prop {Dialog} element
17
- * @prop {Element} [previousFocus]
18
- * @prop {DialogStackState} [state]
19
- * @prop {DialogStackState} [previousState]
14
+ * Returns array of elements that *may* be focusable over tab
15
+ * @param {Node} root
16
+ * @return {Element[]}
20
17
  */
18
+ function listTabbables(root) {
19
+ const treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
20
+ const focusables = [];
21
+ /** @type {Element} */
22
+ let node;
23
+ while ((node = treeWalker.nextNode())) {
24
+ if (node.tagName === 'SLOT') {
25
+ for (const el of (/** @type {HTMLSlotElement} */ (node)).assignedElements()) {
26
+ if (el.tabIndex >= 0 && !el.matches(':disabled')) {
27
+ focusables.push(el);
28
+ }
29
+ focusables.push(...listTabbables(el));
30
+ }
31
+ }
32
+ if (node.tabIndex >= 0 && !node.matches(':disabled')) {
33
+ focusables.push(node);
34
+ }
35
+ }
36
+ return focusables;
37
+ }
21
38
 
22
- /** @type {DialogStack[]} */
23
- const OPEN_DIALOGS = [];
24
-
25
- const supportsHTMLDialogElement = typeof HTMLDialogElement !== 'undefined';
39
+ /**
40
+ * Iterate through root looking for autofocusable, or first focusable element
41
+ * Attempt focus on each and return true if successful
42
+ * @param {Node} root
43
+ * @param {boolean} [autofocus=true]
44
+ * @param {boolean} [forward=true]
45
+ * @return {boolean} focused
46
+ */
47
+ function focusOnTree(root, autofocus, forward = true) {
48
+ const treeWalker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
49
+ const focusables = [];
50
+ /** @type {Element} */
51
+ let node;
52
+ while ((node = treeWalker.nextNode())) {
53
+ if (autofocus && node.hasAttribute('autofocus')) {
54
+ if (attemptFocus(node)) return true;
55
+ continue;
56
+ }
57
+ if (node.tagName === 'SLOT') {
58
+ for (const el of (/** @type {HTMLSlotElement} */ (node)).assignedElements({ flatten: true })) {
59
+ if (autofocus && el.hasAttribute('autofocus')) {
60
+ if (attemptFocus(el)) return true;
61
+ continue;
62
+ }
63
+ if (el.tabIndex >= 0) {
64
+ // Can focus, add to later in case we find an autofocusable
65
+ if (autofocus || !forward) {
66
+ focusables.push(node);
67
+ } else if (attemptFocus(node)) return true;
68
+ }
69
+ if (focusOnTree(el, autofocus, forward)) return true;
70
+ }
71
+ // Step through
72
+ }
73
+ if (node.tabIndex >= 0) {
74
+ if (autofocus || !forward) {
75
+ focusables.push(node);
76
+ } else if (attemptFocus(node)) return true;
77
+ }
78
+ }
79
+ for (const el of forward ? focusables : focusables.reverse()) {
80
+ if (attemptFocus(el)) return true;
81
+ }
82
+ return false;
83
+ }
26
84
 
27
85
  export default CustomElement
28
86
  .extend()
87
+ .mixin(ThemableMixin)
88
+ .mixin(SurfaceMixin)
89
+ .mixin(ShapeMixin)
90
+ .mixin(PopupMixin)
29
91
  .define({
30
- _dialog() {
31
- return /** @type {HTMLDialogElement} */ (this.refs.dialog);
32
- },
33
92
  returnValue() {
34
93
  return /** @type {HTMLDialogElement} */ (this.refs.dialog).returnValue;
35
94
  },
36
95
  })
37
96
  .observe({
38
- open: 'boolean',
39
97
  dividers: {
40
98
  /** @type {'full'|''|'inset'} */
41
99
  value: null,
@@ -45,23 +103,16 @@ export default CustomElement
45
103
  default: { value: 'confirm' },
46
104
  cancel: { value: 'Cancel' },
47
105
  confirm: { value: 'Confirm' },
48
- _isNativeModal: 'boolean',
49
- color: { empty: 'surface' },
50
- ink: 'string',
51
- outlined: 'boolean',
52
- elevation: { empty: 3 },
53
106
  })
54
- .methods({
55
- /**
56
- * @param {TransitionEvent} event
57
- * @return {void}
58
- */
59
- onTransitionEnd(event) {
60
- if (event.propertyName !== 'opacity') return;
61
- if (this.getAttribute('aria-hidden') !== 'true') return;
62
- this.setAttribute('mdw-ready', '');
107
+ .set({
108
+ _useScrim: true,
109
+ })
110
+ .overrides({
111
+ updatePopupPosition() {
112
+ // noop (keep centered);
63
113
  },
64
-
114
+ })
115
+ .methods({
65
116
  /**
66
117
  * @param {Event & {currentTarget: HTMLSlotElement}} event
67
118
  * @return {void}
@@ -96,344 +147,117 @@ export default CustomElement
96
147
  const [form] = currentTarget.assignedNodes();
97
148
  form?.addEventListener('submit', (e) => this.onFormSubmit(e));
98
149
  },
99
-
100
- /**
101
- * @param {PopStateEvent} event
102
- * @return {void}
103
- */
104
- onPopState(event) {
105
- if (!event.state) return;
106
-
107
- const lastOpenDialog = OPEN_DIALOGS.at(-1);
108
- if (!lastOpenDialog || !lastOpenDialog.previousState) {
109
- return;
110
- }
111
- if ((lastOpenDialog.previousState === event.state) || Object.entries(event.state)
112
- .every(([key, value]) => value === lastOpenDialog.previousState[key])) {
113
- const cancelEvent = new Event('cancel', { cancelable: true });
114
- if (lastOpenDialog.element.dispatchEvent(cancelEvent)) {
115
- lastOpenDialog.element.close();
116
- } else {
117
- // Revert pop state by pushing state again
118
- window.history.pushState(lastOpenDialog.state, lastOpenDialog.state.title);
119
- }
120
- }
121
- },
122
-
123
- /**
124
- * @param {any} returnValue
125
- * @return {boolean} handled
126
- */
127
- close(returnValue) {
128
- if (!this.open) return false;
129
- if (this._isNativeModal) {
130
- this._isNativeModal = false;
131
- } else {
132
- const main = document.querySelector('main');
133
- if (main) {
134
- main.removeAttribute('aria-hidden');
135
- }
136
- }
137
- // if (this.dialogElement.getAttribute('aria-hidden') === 'true') return false;
138
- if (supportsHTMLDialogElement && this._dialog.open) {
139
- // Force close native dialog
140
- this._dialog.close(returnValue);
141
- } else {
142
- this._dialog.returnValue = returnValue;
143
- }
144
-
145
- // Will invoke observed attribute change: ('aria-hidden', 'true');
146
- this.open = false;
147
- this.dispatchEvent(new Event('close'));
148
- // .mdw-dialog__popup hidden by transitionEnd event
149
- let stackIndex = -1;
150
- OPEN_DIALOGS.some((stack, index) => {
151
- // @ts-ignore Skip unknown
152
- if (stack.element === this) {
153
- stackIndex = index;
154
- return true;
155
- }
156
- return false;
157
- });
158
- if (stackIndex !== -1) {
159
- const stack = OPEN_DIALOGS[stackIndex];
160
- if (stack.previousFocus
161
- && stack.previousFocus instanceof HTMLElement
162
- && document.activeElement?.closest(this.constructor.elementName) === this) {
163
- // Only pop focus back when hiding a dialog with focus within itself.
164
- try {
165
- stack.previousFocus.focus();
166
- } catch {
167
- // Failed to focus
168
- }
169
- }
170
- OPEN_DIALOGS.splice(stackIndex, 1);
171
- if (stack.state && window.history && window.history.state // IE11 returns a cloned state object, not the original
172
- && stack.state.hash === window.history.state.hash) {
173
- window.history.back();
174
- }
175
- }
176
- if (!OPEN_DIALOGS.length) {
177
- window.removeEventListener('popstate', this.onPopState);
178
- }
179
- return true;
180
- },
181
-
182
- /**
183
- * @param {Event} [event]
184
- * @return {boolean} handled
185
- */
186
- showModal(event) {
187
- if (this.open) return false;
188
- if (supportsHTMLDialogElement) {
189
- this._dialog.showModal();
190
- this._isNativeModal = true;
191
- }
192
- return this.show(event);
193
- },
194
-
195
- /**
196
- * @param {MouseEvent|PointerEvent|HTMLElement|Event} [source]
197
- * @return {boolean} handled
198
- */
199
- show(source) {
200
- if (this.open) return false;
201
- this.open = true;
202
-
203
- if (supportsHTMLDialogElement) {
204
- this._dialog.show();
205
- const main = document.querySelector('main');
206
- if (main) {
207
- main.setAttribute('aria-hidden', 'true');
208
- }
209
- }
210
-
211
- const previousFocus = document.activeElement;
212
- const title = this.headline || this.textContent;
213
- const newState = { time: Date.now(), random: Math.random(), title };
214
- let previousState = null;
215
-
216
- if (!window.history.state) {
217
- window.history.replaceState({
218
- hash: Math.random().toString(36).slice(2, 18),
219
- }, document.title);
220
- }
221
- previousState = window.history.state;
222
- window.history.pushState(newState, title);
223
- window.addEventListener('popstate', this.onPopState);
224
-
225
- /** @type {DialogStack} */
226
- const dialogStack = {
227
- // @ts-ignore Recursive cast
228
- element: this,
229
- previousFocus,
230
- state: newState,
231
- previousState,
232
- };
233
- OPEN_DIALOGS.push(dialogStack);
234
- const focusElement = this.querySelector('[autofocus]')
235
- ?? this.shadowRoot.querySelector('[autofocus]');
236
- try {
237
- if (focusElement && focusElement instanceof HTMLElement) {
238
- if (focusElement.scrollIntoView) {
239
- focusElement.scrollIntoView();
240
- }
241
- focusElement.focus();
242
- } else {
243
- this.refs.surface.focus();
244
- }
245
- } catch {
246
- // Failed to focus
247
- }
248
- return true;
150
+ focus() {
151
+ focusOnTree(this.shadowRoot, true, true);
249
152
  },
250
153
  })
251
154
  .expressions({
252
155
  cancelAutoFocus({ default: d }) { return d === 'cancel'; },
253
156
  confirmAutoFocus({ default: d }) { return d === 'confirm'; },
254
- _ariaHidden({ open }) { return (open ? 'false' : 'true'); },
255
157
  })
256
- .html/* html */`
257
- <dialog id=dialog aria-modal=true role=dialog
258
- aria-hidden={_ariaHidden}
259
- aria-labelledby=headline aria-describedby=slot>
260
- <div _if={open} id=scrim aria-hidden=true></div>
261
- <mdw-surface id=surface open={open} icon={icon} elevation={elevation} color={color} ink={ink} outlined={outlined}>
262
- <mdw-icon _if={icon} id=icon class=content ink=secondary aria-hidden=true>{icon}</mdw-icon>
263
- <slot id=headline name=headline on-slotchange={onSlotChange} role=header>{headline}</slot>
264
- <slot id=fixed name=fixed class=content on-slotchange={onSlotChange}></slot>
265
- <mdw-divider id=divider-top size={dividers}></mdw-divider>
266
- <slot id=slot class=content on-slotchange={onSlotChange}></slot>
267
- <mdw-divider id=divider-bottom size={dividers}></mdw-divider>
268
- <slot name=form id=form-slot on-slotchange={onFormSlotChange}>
269
- <form id=form method=dialog role=none on-submit={onFormSubmit}>
270
- <mdw-dialog-actions>
271
- <mdw-button id=cancel type=submit value=cancel
272
- autofocus={cancelAutoFocus}>{cancel}</mdw-button>
273
- <mdw-button id=confirm type=submit value=confirm
274
- autofocus={confirmAutoFocus}>{confirm}</mdw-button>
275
- </mdw-dialog-actions>
276
- </form>
277
- </slot>
278
- </mdw-surface>
279
- </dialog>
158
+ .html`
159
+ <div id=prepend>
160
+ <mdw-icon mdw-if={icon} id=icon class=content ink=secondary aria-hidden=true>{icon}</mdw-icon>
161
+ <slot id=headline name=headline on-slotchange={onSlotChange} role=header>{headline}</slot>
162
+ <slot id=fixed name=fixed class=content on-slotchange={onSlotChange}></slot>
163
+ <mdw-divider id=divider-top size={dividers}></mdw-divider>
164
+ </div>
165
+ <div id=append>
166
+ <mdw-divider id=divider-bottom size={dividers}></mdw-divider>
167
+ <slot name=form id=form-slot on-slotchange={onFormSlotChange}>
168
+ <form id=form method=dialog role=none on-submit={onFormSubmit}>
169
+ <mdw-dialog-actions>
170
+ <mdw-button id=cancel type=submit value=cancel
171
+ autofocus={cancelAutoFocus}>{cancel}</mdw-button>
172
+ <mdw-button id=confirm type=submit value=confirm
173
+ autofocus={confirmAutoFocus}>{confirm}</mdw-button>
174
+ </mdw-dialog-actions>
175
+ </form>
176
+ </slot>
177
+ </div>
280
178
  `
179
+ .on({
180
+ composed() {
181
+ const { prepend, append, surface, shape, dialog, slot } = this.refs;
182
+ dialog.setAttribute('aria-labelledby', 'headline');
183
+ dialog.setAttribute('aria-describedby', 'slot');
184
+ surface.append(shape);
185
+
186
+ slot.classList.add('content');
187
+
188
+ dialog.prepend(surface, ...prepend.childNodes);
189
+ dialog.append(...append.childNodes);
190
+ prepend.remove();
191
+ append.remove();
192
+ },
193
+ })
281
194
  .css`
282
195
  /* https://m3.material.io/components/dialogs/specs */
283
196
 
284
197
  :host {
285
- --mdw-dialog__expand-duration: var(--mdw-motion-expand-duration, 250ms);
286
- --mdw-dialog__simple-duration: var(--mdw-motion-simple-duration, 100ms);
287
- --mdw-dialog__standard-easing: var(--mdw-motion-standard-easing, cubic-bezier(0.4, 0.0, 0.2, 1));
288
- --mdw-dialog__deceleration-easing: var(--mdw-motion-deceleration-easing, cubic-bezier(0.0, 0.0, 0.2, 1));
289
- --mdw-dialog__fade-in-duration: var(--mdw-motion-fade-in-duration, 150ms);
290
-
291
- position: fixed;
292
- inset: 0;
293
-
294
- pointer-events: none;
198
+ --mdw-shape__size: 28px;
295
199
 
296
- z-index: 24;
297
- }
200
+ --mdw-surface__shadow__resting: var(--mdw-surface__shadow__3);
201
+ --mdw-surface__shadow__raised: var(--mdw-surface__shadow__resting);
202
+ /* padding-inline: 12px; */
298
203
 
299
- #dialog {
204
+ --mdw-bg: var(--mdw-color__surface-container-high);
205
+ --mdw-ink: var(--mdw-color__on-surface);
300
206
  position: fixed;
301
- inset-block-start: 0;
302
- inset-inline-start: 0;
207
+
208
+ /* stylelint-disable-next-line liberty/use-logical-spec */
209
+ top: 50%;
210
+ /* stylelint-disable-next-line liberty/use-logical-spec */
211
+ left: 50%;
303
212
 
304
213
  display: flex;
305
- align-items: center;
306
- flex-direction: row;
214
+ align-items: flex-start;
215
+ flex-direction: column;
307
216
  justify-content: center;
217
+ overflow: visible;
218
+ -webkit-overflow-scrolling: touch;
219
+ overscroll-behavior: none;
220
+ overscroll-behavior: contain;
308
221
 
309
222
  box-sizing: border-box;
310
- block-size:100%;
311
- max-block-size: none;
312
- inline-size:100%;
313
- max-inline-size: none;
314
- margin:0;
315
- border: none;
316
- padding: 48px;
223
+ max-block-size: calc(100% - 40px);
224
+ inline-size: max-content;
225
+ min-inline-size: 280px;
226
+ max-inline-size: min(560px, calc(100% - 40px));
227
+
228
+ padding-block-start: 8px;
229
+
230
+ pointer-events: none;
317
231
 
318
232
  opacity: 0;
233
+
234
+ transform: translateX(-50%) translateY(-50%) scale(0);
319
235
  /* visiblity:hidden still registers events, hide from pointer with scale(0) */
320
- transform: scale(0);
236
+ transform-origin: top center;
321
237
  visibility: hidden;
322
- z-index: 24;
323
238
 
324
- background-color: transparent;
239
+ color: rgb(var(--mdw-ink));
240
+
241
+ font: var(--mdw-type__font);
242
+ letter-spacing: var(--mdw-type__letter-spacing);
325
243
 
326
244
  transition-delay: 0s, 200ms, 200ms;
327
245
  transition-duration: 200ms, 0s, 0s;
328
246
  transition-property: opacity, transform, visibility;
329
247
  transition-timing-function: ease-out;
330
- will-change: opacity;
331
- }
332
-
333
- @media (min-width: 1440px) {
334
- #dialog {
335
- padding: 56px;
336
- }
337
- }
338
-
339
- #dialog::backdrop {
340
- /** Use scrim instead */
341
- display: none;
248
+ will-change: display, transform, opacity;
342
249
  }
343
250
 
344
- #dialog[aria-hidden="false"],
345
251
  #dialog:modal {
346
- pointer-events: auto;
347
-
348
- opacity: 1;
349
-
350
- transform: none;
351
- visibility: visible;
352
-
353
- transition-delay: 0s;
354
- transition-duration: 0s;
355
- transition-timing-function: ease-in;
356
- }
357
-
358
- #scrim {
359
- position: fixed;
360
- inset: 0;
361
-
362
- overflow-y: scroll;
363
- overscroll-behavior: none;
364
- overscroll-behavior: contain;
365
- scrollbar-width: none;
366
-
367
- block-size: 100%;
368
- inline-size: 100%;
369
-
370
- cursor: default;
371
- pointer-events: inherit;
372
- -webkit-tap-highlight-color: transparent;
373
-
374
- opacity: 0.38;
375
- z-index: 0;
376
-
377
- background-color: rgb(var(--mdw-color__scrim));
378
- }
379
-
380
- #scrim::-webkit-scrollbar {
381
- display: none;
252
+ overflow: visible;
382
253
  }
383
254
 
384
- #scrim::after {
385
- content: '';
386
-
387
- display: block;
388
-
389
- block-size: 200%;
390
- inline-size: 200%;
391
- }
392
- @keyframes scaleUpAnimation {
393
- from {
394
- transform: scaleY(0);
395
- }
396
-
397
- to {
398
- transform: scaleY(1);
399
- }
400
- }
401
-
402
- #surface {
403
- --mdw-shape__size: 28px;
404
-
405
- position: relative;
406
-
407
- display: flex;
408
- align-items: flex-start;
409
- flex-direction: column;
410
- -webkit-overflow-scrolling: touch;
411
- overscroll-behavior: none;
412
- overscroll-behavior: contain;
413
-
414
- box-sizing: border-box;
415
- max-block-size: 100%;
416
- min-inline-size: 280px;
417
- max-inline-size: 560px;
418
- flex-shrink: 1;
419
-
420
- padding-block-start: 8px;
421
-
422
- transform: scale(1);
423
- transform-origin: top center;
424
- z-index: 24;
425
-
426
- will-change: display, transform;
427
- }
428
-
429
- #surface[icon] {
255
+ :host([icon]) {
430
256
  align-items: center;
431
257
  }
432
258
 
433
- #surface[open] {
434
- animation-name: scaleUpAnimation;
435
- animation-duration: 200ms;
436
- animation-direction: forwards;
259
+ #shape {
260
+ background-color: rgb(var(--mdw-bg));
437
261
  }
438
262
 
439
263
  #icon {
@@ -515,6 +339,69 @@ export default CustomElement
515
339
  display: contents;
516
340
  }
517
341
  `
342
+ .events({
343
+ keydown(event) {
344
+ if (event.key === 'Tab') {
345
+ if (!this._isNativeModal) {
346
+ // Tab trap
347
+ event.preventDefault();
348
+ const tabbables = listTabbables(this.shadowRoot);
349
+ if (event.shiftKey) {
350
+ tabbables.reverse();
351
+ }
352
+ let focusNext = false;
353
+ let focused = false;
354
+ // Find and mark next
355
+ for (const el of tabbables) {
356
+ if (focusNext) {
357
+ if (attemptFocus(el)) {
358
+ focused = true;
359
+ break;
360
+ }
361
+ } else {
362
+ focusNext = el.matches(':focus');
363
+ }
364
+ }
365
+ // Loop
366
+ if (!focused) {
367
+ for (const el of tabbables) {
368
+ if (attemptFocus(el)) {
369
+ return;
370
+ }
371
+ }
372
+ }
373
+ }
374
+ return;
375
+ }
376
+
377
+ if (event.key === 'Escape' || event.key === 'Esc') {
378
+ event.preventDefault();
379
+ event.stopPropagation();
380
+ const cancelEvent = new Event('cancel', { cancelable: true });
381
+ if (this.dispatchEvent(cancelEvent)) {
382
+ this.close();
383
+ }
384
+ }
385
+ },
386
+ focusout(event) {
387
+ if (!this.open) return;
388
+ if (this._closing) return;
389
+ if (this.modal) return;
390
+ if (event.relatedTarget === this.refs.scrim) return;
391
+
392
+ // Wait until end of event loop cycle to see if focus really is lost
393
+ queueMicrotask(() => {
394
+ if (this.matches(':focus-within')) return;
395
+ const activeElement = document.activeElement;
396
+ if (activeElement && this.contains(activeElement)) {
397
+ return;
398
+ }
399
+ // Focus has left dialog (programmatic?)
400
+ // Invoke cancel without returning focus
401
+ this.close(undefined, false);
402
+ });
403
+ },
404
+ })
518
405
  .childEvents({
519
406
  dialog: {
520
407
  cancel(event) {
@@ -528,34 +415,16 @@ export default CustomElement
528
415
  event.stopPropagation();
529
416
  this.close(this.returnValue);
530
417
  },
531
- },
532
- scrim: {
533
- '~click'() {
418
+ '~click'(event) {
419
+ // Track if click on backdrop
420
+ if (event.target !== event.currentTarget) return;
421
+ if (!this._isNativeModal) return;
422
+ if (event.offsetX >= 0 && event.offsetX < event.currentTarget.offsetWidth
423
+ && event.offsetY >= 0 && event.offsetY < event.currentTarget.offsetHeight) return;
534
424
  const cancelEvent = new Event('cancel', { cancelable: true });
535
425
  if (!this.dispatchEvent(cancelEvent)) return;
536
426
  this.close();
537
427
  },
538
428
  },
539
- surface: {
540
- keydown(event) {
541
- if (event.key === 'Tab') {
542
- const surface = /** @type {HTMLElement} */ (event.currentTarget);
543
- if (!this._isNativeModal) {
544
- // Move via Light or Shadow DOM, depending on target
545
- const context = surface.contains(event.target) ? surface : this;
546
- handleTabKeyPress.call(context, event);
547
- }
548
- return;
549
- }
550
- if (event.key === 'Escape' || event.key === 'Esc') {
551
- event.preventDefault();
552
- event.stopPropagation();
553
- const cancelEvent = new Event('cancel', { cancelable: true });
554
- if (this.dispatchEvent(cancelEvent)) {
555
- this.close();
556
- }
557
- }
558
- },
559
- },
560
429
  })
561
430
  .autoRegister('mdw-dialog');
@@ -10,5 +10,5 @@ export default CustomElement
10
10
  padding-inline: 24px;
11
11
  }
12
12
  `
13
- .html/* html */`<slot id=slot></slot>`
14
- .register('mdw-dialog-actions');
13
+ .html`<slot id=slot></slot>`
14
+ .autoRegister('mdw-dialog-actions');