@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.
- package/README.md +57 -68
- package/components/Badge.js +2 -2
- package/components/BottomAppBar.js +3 -5
- package/components/Box.js +33 -3
- package/components/Button.js +48 -21
- package/components/Button.md +9 -9
- package/components/Card.js +9 -16
- package/components/Checkbox.js +45 -36
- package/components/CheckboxIcon.js +2 -2
- package/components/Chip.js +1 -1
- package/components/Dialog.js +228 -359
- package/components/DialogActions.js +2 -2
- package/components/Divider.js +3 -3
- package/components/ExtendedFab.js +4 -8
- package/components/Fab.js +1 -2
- package/components/FilterChip.js +4 -4
- package/components/Headline.js +1 -1
- package/components/Icon.js +8 -8
- package/components/IconButton.js +9 -14
- package/components/Input.js +273 -1
- package/components/Layout.js +485 -16
- package/components/List.js +6 -4
- package/components/ListItem.js +12 -12
- package/components/ListOption.js +21 -5
- package/components/Listbox.js +239 -0
- package/components/Menu.js +77 -526
- package/components/MenuItem.js +12 -14
- package/components/Nav.js +0 -2
- package/components/NavBar.js +8 -79
- package/components/NavDrawer.js +12 -11
- package/components/NavDrawerItem.js +2 -1
- package/components/NavItem.js +18 -8
- package/components/NavRail.js +15 -7
- package/components/NavRailItem.js +3 -1
- package/components/Popup.js +20 -0
- package/components/Progress.js +24 -23
- package/components/Radio.js +42 -35
- package/components/RadioIcon.js +3 -3
- package/components/Ripple.js +2 -3
- package/components/Search.js +85 -0
- package/components/SegmentedButton.js +1 -10
- package/components/SegmentedButtonGroup.js +16 -10
- package/components/Select.js +4 -4
- package/components/Shape.js +1 -1
- package/components/Slider.js +43 -50
- package/components/Snackbar.js +4 -5
- package/components/Surface.js +3 -3
- package/components/Switch.js +55 -21
- package/components/SwitchIcon.js +10 -8
- package/components/Tab.js +11 -9
- package/components/TabContent.js +4 -3
- package/components/TabList.js +2 -2
- package/components/TabPanel.js +11 -8
- package/components/TextArea.js +38 -35
- package/components/Tooltip.js +2 -2
- package/components/TopAppBar.js +65 -147
- package/core/Composition.js +985 -628
- package/core/CompositionAdapter.js +315 -0
- package/core/CustomElement.js +153 -90
- package/core/DomAdapter.js +586 -0
- package/core/ICustomElement.d.ts +2 -2
- package/core/css.js +8 -7
- package/core/customTypes.js +53 -31
- package/{utils → core}/jsonMergePatch.js +36 -14
- package/core/observe.js +111 -57
- package/core/optimizations.js +23 -0
- package/core/template.js +17 -11
- package/core/test.js +126 -0
- package/core/typings.d.ts +11 -5
- package/core/uid.js +13 -0
- package/dist/index.min.js +83 -152
- package/dist/index.min.js.map +4 -4
- package/dist/meta.json +1 -1
- package/mixins/AriaReflectorMixin.js +1 -2
- package/mixins/AriaToolbarMixin.js +2 -3
- package/mixins/ControlMixin.js +25 -17
- package/mixins/DensityMixin.js +0 -1
- package/mixins/FlexableMixin.js +1 -2
- package/mixins/FormAssociatedMixin.js +13 -10
- package/mixins/InputMixin.js +2 -9
- package/mixins/KeyboardNavMixin.js +14 -1
- package/mixins/PopupMixin.js +757 -0
- package/mixins/RTLObserverMixin.js +0 -1
- package/mixins/ResizeObserverMixin.js +0 -1
- package/mixins/RippleMixin.js +3 -4
- package/mixins/ScrollListenerMixin.js +41 -32
- package/mixins/SemiStickyMixin.js +151 -0
- package/mixins/ShapeMixin.js +29 -24
- package/mixins/StateMixin.js +11 -6
- package/mixins/SurfaceMixin.js +3 -57
- package/mixins/TextFieldMixin.js +57 -65
- package/mixins/ThemableMixin.js +78 -156
- package/mixins/TooltipTriggerMixin.js +7 -13
- package/mixins/TouchTargetMixin.js +4 -3
- package/package.json +9 -5
- package/theming/index.js +1 -1
- package/theming/themableMixinLoader.js +12 -0
- package/utils/{hct → material-color}/blend.js +8 -10
- package/utils/{hct → material-color/hct}/Cam16.js +196 -69
- package/utils/{hct → material-color/hct}/Hct.js +61 -19
- package/utils/{hct → material-color/hct}/ViewingConditions.js +3 -3
- package/utils/{hct → material-color/hct}/hctSolver.js +9 -16
- package/utils/{hct → material-color}/helper.js +11 -18
- package/utils/{hct → material-color/palettes}/CorePalette.js +79 -19
- package/utils/{hct → material-color/palettes}/TonalPalette.js +12 -4
- package/utils/material-color/scheme/Scheme.js +376 -0
- package/utils/{hct/colorUtils.js → material-color/utils/color.js} +61 -1
- package/utils/popup.js +46 -25
- package/components/ListSelect.js +0 -220
- package/components/Option.js +0 -91
- package/components/Pane.js +0 -281
- package/core/identify.js +0 -40
- package/utils/hct/Scheme.js +0 -587
- /package/utils/{hct/mathUtils.js → material-color/utils/math.js} +0 -0
package/components/Menu.js
CHANGED
|
@@ -5,86 +5,27 @@ import CustomElement from '../core/CustomElement.js';
|
|
|
5
5
|
import { attemptFocus } from '../core/dom.js';
|
|
6
6
|
import DensityMixin from '../mixins/DensityMixin.js';
|
|
7
7
|
import KeyboardNavMixin from '../mixins/KeyboardNavMixin.js';
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
* @prop {HTMLElement} element
|
|
13
|
-
* @prop {Element} previousFocus
|
|
14
|
-
* @prop {Object} [state]
|
|
15
|
-
* @prop {Object} [previousState]
|
|
16
|
-
* @prop {MouseEvent|PointerEvent|HTMLElement|Element} [originalEvent]
|
|
17
|
-
* @prop {any} [pendingResizeOperation]
|
|
18
|
-
* @prop {window['history']['scrollRestoration']} [scrollRestoration]
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
|
-
const supportsHTMLDialogElement = typeof HTMLDialogElement !== 'undefined';
|
|
22
|
-
/** @type {MenuStack[]} */
|
|
23
|
-
const OPEN_MENUS = [];
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* @return {void}
|
|
27
|
-
*/
|
|
28
|
-
function onWindowResize() {
|
|
29
|
-
const lastOpenMenu = OPEN_MENUS.at(-1);
|
|
30
|
-
if (!lastOpenMenu || !lastOpenMenu.originalEvent) {
|
|
31
|
-
return;
|
|
32
|
-
}
|
|
33
|
-
if (lastOpenMenu.pendingResizeOperation) {
|
|
34
|
-
cancelAnimationFrame(lastOpenMenu.pendingResizeOperation);
|
|
35
|
-
}
|
|
36
|
-
lastOpenMenu.pendingResizeOperation = requestAnimationFrame(() => {
|
|
37
|
-
lastOpenMenu.element.updateMenuPosition(lastOpenMenu.originalEvent);
|
|
38
|
-
lastOpenMenu.pendingResizeOperation = null;
|
|
39
|
-
});
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* @param {PopStateEvent} event
|
|
44
|
-
*/
|
|
45
|
-
function onPopState(event) {
|
|
46
|
-
if (!event.state) return;
|
|
47
|
-
const lastOpenMenu = OPEN_MENUS.at(-1);
|
|
48
|
-
if (!lastOpenMenu || !lastOpenMenu.previousState) {
|
|
49
|
-
return;
|
|
50
|
-
}
|
|
51
|
-
if ((lastOpenMenu.previousState === event.state) || Object.keys(event.state)
|
|
52
|
-
.every((key) => event.state[key] === lastOpenMenu.previousState[key])) {
|
|
53
|
-
lastOpenMenu.element.close();
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** @param {BeforeUnloadEvent} event */
|
|
58
|
-
function onBeforeUnload(event) {
|
|
59
|
-
if (!OPEN_MENUS.length) return;
|
|
60
|
-
console.warn('Menu was open during page unload (refresh?).');
|
|
61
|
-
}
|
|
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';
|
|
62
12
|
|
|
63
13
|
export default CustomElement
|
|
14
|
+
.extend()
|
|
15
|
+
.mixin(ThemableMixin)
|
|
16
|
+
.mixin(SurfaceMixin)
|
|
17
|
+
.mixin(ShapeMixin)
|
|
18
|
+
.mixin(PopupMixin)
|
|
64
19
|
.mixin(DensityMixin)
|
|
65
20
|
.mixin(KeyboardNavMixin)
|
|
66
|
-
.extend()
|
|
67
|
-
.observe({
|
|
68
|
-
open: 'boolean',
|
|
69
|
-
flow: {
|
|
70
|
-
type: 'string',
|
|
71
|
-
/** @type {'corner'|'adjacent'|'overflow'|'vcenter'|'hcenter'|'center'} */
|
|
72
|
-
value: null,
|
|
73
|
-
},
|
|
74
|
-
submenu: 'boolean',
|
|
75
|
-
modal: 'boolean',
|
|
76
|
-
_isNativeModal: 'boolean',
|
|
77
|
-
color: { empty: 'surface' },
|
|
78
|
-
ink: 'string',
|
|
79
|
-
elevation: { empty: 2 },
|
|
80
|
-
outlined: 'boolean',
|
|
81
|
-
})
|
|
82
21
|
.set({
|
|
83
|
-
|
|
84
|
-
|
|
22
|
+
scrollable: true,
|
|
23
|
+
flow: 'corner',
|
|
24
|
+
_useScrim: false,
|
|
85
25
|
/** @type {WeakRef<HTMLElement>} */
|
|
86
26
|
_cascader: null,
|
|
87
|
-
|
|
27
|
+
/** @type {WeakRef<HTMLElement>} */
|
|
28
|
+
_submenu: null,
|
|
88
29
|
})
|
|
89
30
|
.define({
|
|
90
31
|
kbdNavChildren() {
|
|
@@ -107,484 +48,95 @@ export default CustomElement
|
|
|
107
48
|
this._cascader = value ? new WeakRef(value) : null;
|
|
108
49
|
},
|
|
109
50
|
},
|
|
51
|
+
submenu: {
|
|
52
|
+
get() {
|
|
53
|
+
return this._submenu?.deref();
|
|
54
|
+
},
|
|
55
|
+
/**
|
|
56
|
+
* @param {HTMLElement} value
|
|
57
|
+
*/
|
|
58
|
+
set(value) {
|
|
59
|
+
this._submenu = value ? new WeakRef(value) : null;
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
})
|
|
63
|
+
.on({
|
|
64
|
+
composed() {
|
|
65
|
+
const { shape, surface, dialog, scrim } = this.refs;
|
|
66
|
+
surface.append(shape);
|
|
67
|
+
dialog.prepend(surface);
|
|
68
|
+
scrim.setAttribute('invisible', '');
|
|
69
|
+
|
|
70
|
+
// Wrap slot in scroller
|
|
71
|
+
},
|
|
110
72
|
})
|
|
111
|
-
.html/* html */`
|
|
112
|
-
<dialog id=dialog role=menu aria-hidden=${({ open }) => (open ? 'false' : 'true')}>
|
|
113
|
-
<div id=scrim aria-hidden=true modal={modal}></div>
|
|
114
|
-
<form id=form method=dialog role=none>
|
|
115
|
-
<mdw-surface id=surface elevation={elevation} color={color} ink={ink} outlined={outlined}>
|
|
116
|
-
<div id=scroller>
|
|
117
|
-
<slot id=slot on-slotchange={refreshTabIndexes}></slot>
|
|
118
|
-
</div>
|
|
119
|
-
</mdw-surface>
|
|
120
|
-
<slot id=submenu-slot name=submenu></slot>
|
|
121
|
-
</form>
|
|
122
|
-
</dialog>
|
|
123
|
-
`
|
|
124
73
|
.css`
|
|
125
74
|
/* https://m3.material.io/components/menus/specs */
|
|
126
75
|
|
|
127
76
|
:host {
|
|
128
|
-
--mdw-
|
|
129
|
-
--mdw-
|
|
130
|
-
/* Normal */
|
|
131
|
-
--mdw-menu__transform-origin-x: var(--mdw-menu__transform-origin-inline-start);
|
|
132
|
-
/* Down */
|
|
133
|
-
--mdw-menu__transform-origin-y: top;
|
|
134
|
-
--mdw-menu__inline-base: 56px;
|
|
135
|
-
--mdw-menu__size: 2;
|
|
136
|
-
--mdw-bg: var(--mdw-color__surface);
|
|
77
|
+
--mdw-shape__size: var(--mdw-shape__extra-small);
|
|
78
|
+
--mdw-bg: var(--mdw-color__surface-container);
|
|
137
79
|
--mdw-ink: var(--mdw-color__on-surface);
|
|
138
|
-
position: absolute;
|
|
139
|
-
/* Default position is bottom */
|
|
140
|
-
/* Default direction is start */
|
|
141
|
-
inset-block: 100% auto;
|
|
142
|
-
inset-inline: auto 0;
|
|
143
80
|
|
|
81
|
+
--mdw-surface__shadow__resting: var(--mdw-surface__shadow__2);
|
|
82
|
+
--mdw-surface__shadow__raised: var(--mdw-surface__shadow__resting);
|
|
144
83
|
display: block;
|
|
145
|
-
/* Hide scrollbar */
|
|
146
|
-
-ms-overflow-style: none;
|
|
147
|
-
/* Scroll mask */
|
|
148
|
-
overscroll-behavior: none;
|
|
149
|
-
overscroll-behavior: contain;
|
|
150
|
-
scrollbar-width: none;
|
|
151
|
-
|
|
152
|
-
pointer-events: none;
|
|
153
|
-
|
|
154
|
-
transform-origin: var(--mdw-menu__transform-origin-x) var(--mdw-menu__transform-origin-y);
|
|
155
|
-
|
|
156
|
-
transition-duration: motion.$fadeOutDuration;
|
|
157
|
-
transition-property: none;
|
|
158
|
-
transition-timing-function: motion.$decelerateEasing;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
:host(::after) {
|
|
162
|
-
content: '';
|
|
163
|
-
|
|
164
|
-
display: block;
|
|
165
|
-
|
|
166
|
-
block-size: 200%;
|
|
167
|
-
inline-size: 200%;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
:host(::-webkit-scrollbar) {
|
|
171
|
-
display: none;
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
dialog {
|
|
175
|
-
position: fixed;
|
|
176
|
-
inset: 0;
|
|
177
|
-
|
|
178
|
-
box-sizing: border-box;
|
|
179
|
-
block-size:100%;
|
|
180
|
-
max-block-size: none;
|
|
181
|
-
inline-size:100%;
|
|
182
|
-
max-inline-size: none;
|
|
183
|
-
margin: 0;
|
|
184
|
-
border: none;
|
|
185
|
-
padding: 0;
|
|
186
|
-
|
|
187
|
-
opacity: 0;
|
|
188
|
-
visibility: hidden;
|
|
189
|
-
z-index: 24;
|
|
190
|
-
|
|
191
|
-
background-color: transparent;
|
|
192
|
-
|
|
193
|
-
transition: none;
|
|
194
|
-
transition-property: opacity;
|
|
195
|
-
will-change: opacity;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
dialog::backdrop {
|
|
199
|
-
/** Use scrim instead */
|
|
200
|
-
display: none;
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
dialog[aria-hidden="false"],
|
|
204
|
-
dialog:modal {
|
|
205
|
-
display: block;
|
|
206
|
-
|
|
207
|
-
pointer-events: none;
|
|
208
|
-
|
|
209
|
-
opacity: 1;
|
|
210
|
-
visibility: visible;
|
|
211
|
-
|
|
212
|
-
transition-duration: var(--mdw-dialog__fade-in-duration);
|
|
213
|
-
transition-property: opacity;
|
|
214
|
-
transition-timing-function: var(--mdw-dialog__deceleration-easing);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
#scrim {
|
|
218
|
-
position: fixed;
|
|
219
|
-
inset: 0;
|
|
220
|
-
|
|
221
|
-
overflow-y: scroll;
|
|
222
|
-
overscroll-behavior: none;
|
|
223
|
-
overscroll-behavior: contain;
|
|
224
|
-
scrollbar-width: none;
|
|
225
|
-
|
|
226
|
-
block-size: 100%;
|
|
227
|
-
inline-size: 100%;
|
|
228
|
-
|
|
229
|
-
cursor: default;
|
|
230
|
-
pointer-events: auto;
|
|
231
|
-
-webkit-tap-highlight-color: transparent;
|
|
232
|
-
|
|
233
|
-
visibility: hidden; /* Only show if [modal] */
|
|
234
|
-
|
|
235
|
-
z-index:0;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
#form {
|
|
239
|
-
display: contents;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
#scrim::-webkit-scrollbar {
|
|
243
|
-
display: none;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
#scrim::after {
|
|
247
|
-
content: '';
|
|
248
|
-
|
|
249
|
-
display: block;
|
|
250
|
-
|
|
251
|
-
block-size: 200%;
|
|
252
|
-
inline-size: 200%;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
#surface {
|
|
256
|
-
--mdw-shape__size: var(--mdw-shape__extra-small);
|
|
257
|
-
position: sticky;
|
|
258
84
|
|
|
259
|
-
|
|
260
|
-
flex-direction: column;
|
|
261
|
-
|
|
262
|
-
inline-size: calc(var(--mdw-menu__size) * var(--mdw-menu__inline-base));
|
|
85
|
+
inline-size: auto;
|
|
263
86
|
min-inline-size: calc(var(--mdw-menu__inline-base) * 2);
|
|
264
87
|
max-inline-size: 100vw;
|
|
265
|
-
flex:1;
|
|
266
|
-
|
|
267
|
-
pointer-events: auto;
|
|
268
|
-
/* background-color: rgb(var(--mdw-color__surface)); */
|
|
269
|
-
/* color: rgb(var(--mdw-color__on-surface)); */
|
|
270
|
-
/* stylelint-disable-next-line liberty/use-logical-spec */
|
|
271
|
-
will-change: top, left;
|
|
272
88
|
}
|
|
273
89
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
position: absolute;
|
|
277
|
-
}
|
|
90
|
+
#shape {
|
|
91
|
+
background-color: rgb(var(--mdw-bg));
|
|
278
92
|
}
|
|
279
93
|
|
|
280
|
-
#
|
|
281
|
-
display:
|
|
282
|
-
align-items: stretch;
|
|
283
|
-
flex-direction: column;
|
|
284
|
-
overflow-y: auto;
|
|
285
|
-
overscroll-behavior: none;
|
|
286
|
-
overscroll-behavior: contain;
|
|
287
|
-
|
|
288
|
-
flex: 1;
|
|
289
|
-
|
|
290
|
-
padding-block: 8px;
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
#scrim[modal] {
|
|
294
|
-
visibility: visible;
|
|
94
|
+
#form {
|
|
95
|
+
display: contents;
|
|
295
96
|
}
|
|
296
97
|
`
|
|
297
98
|
.methods({
|
|
99
|
+
showModal(...args) {
|
|
100
|
+
this._useScrim = true;
|
|
101
|
+
const result = this.showPopup(...args);
|
|
102
|
+
this._useScrim = false;
|
|
103
|
+
return result;
|
|
104
|
+
},
|
|
298
105
|
focus() {
|
|
299
106
|
const [firstItem] = this.kbdNavChildren;
|
|
300
107
|
if (!attemptFocus(firstItem)) {
|
|
301
108
|
this.focusNext(firstItem);
|
|
302
109
|
}
|
|
303
110
|
},
|
|
304
|
-
/**
|
|
305
|
-
* @param {DOMRect|Element} [anchor]
|
|
306
|
-
* @return {void}
|
|
307
|
-
*/
|
|
308
|
-
updateMenuPosition(anchor) {
|
|
309
|
-
const surface = this.refs.surface;
|
|
310
|
-
surface.style.setProperty('max-height', 'none');
|
|
311
|
-
surface.style.setProperty('width', 'auto');
|
|
312
|
-
const newSize = Math.ceil(surface.clientWidth / 56);
|
|
313
|
-
surface.style.removeProperty('width');
|
|
314
|
-
surface.style.setProperty('--mdw-menu__size', newSize.toString(10));
|
|
315
|
-
|
|
316
|
-
/** @type {import('../utils/popup.js').CanAnchorPopUpOptions} */
|
|
317
|
-
const anchorOptions = {
|
|
318
|
-
anchor: anchor ?? this.getBoundingClientRect(),
|
|
319
|
-
width: surface.clientWidth,
|
|
320
|
-
height: surface.clientHeight,
|
|
321
|
-
// margin,
|
|
322
|
-
};
|
|
323
|
-
|
|
324
|
-
const isPageRTL = (getComputedStyle(this).direction === 'rtl');
|
|
325
|
-
const xStart = isPageRTL ? 'right' : 'left';
|
|
326
|
-
const xEnd = isPageRTL ? 'left' : 'right';
|
|
327
|
-
|
|
328
|
-
/* Automatic Positioning
|
|
329
|
-
*
|
|
330
|
-
* Positions:
|
|
331
|
-
* 3 7 4
|
|
332
|
-
* ┌─────────┐
|
|
333
|
-
* │ │
|
|
334
|
-
* 5 │ 9 │ 6
|
|
335
|
-
* │ │
|
|
336
|
-
* └─────────┘
|
|
337
|
-
* 1 8 2
|
|
338
|
-
*
|
|
339
|
-
* 1: Bottom Left
|
|
340
|
-
* 2: Bottom Right
|
|
341
|
-
* 3: Top Left
|
|
342
|
-
* 4: Top Right
|
|
343
|
-
* 5: VCenter Left
|
|
344
|
-
* 6: VCenter Right
|
|
345
|
-
* 7: HCenter Top
|
|
346
|
-
* 8: HCenter Bottom
|
|
347
|
-
* 9: VCenter HCenter
|
|
348
|
-
*
|
|
349
|
-
* Directions:
|
|
350
|
-
* a - Down LTR
|
|
351
|
-
* b - Down RTL
|
|
352
|
-
* c - Up LTR
|
|
353
|
-
* d - Up RTL
|
|
354
|
-
* e - LTR
|
|
355
|
-
* f - RTL
|
|
356
|
-
* g - Down
|
|
357
|
-
* h - Up
|
|
358
|
-
* i - Center
|
|
359
|
-
*
|
|
360
|
-
*
|
|
361
|
-
* 16 total combos
|
|
362
|
-
* 1a 1b 1c 1d └↘ └↙ └↗ └↖
|
|
363
|
-
* 2a 2b 2c 2d ┘↘ ┘↙ ┘↗ ┘↖
|
|
364
|
-
* 3a 3b 3c 3d ┌↘ ┌↙ ┌↗ ┌↖
|
|
365
|
-
* 4a 4b 4c 4d ┐↘ ┐↙ ┐↗ ┐↖
|
|
366
|
-
*
|
|
367
|
-
* Avoid using opposite angle
|
|
368
|
-
*
|
|
369
|
-
* 1a XX 1c 1d └↘ ██ └↗ └↖
|
|
370
|
-
* XX 2b 2c 2d ██ ┘↙ ┘↗ ┘↖
|
|
371
|
-
* 1a 3b 3c XX ┌↘ ┌↙ ┌↗ ██
|
|
372
|
-
* 4a 4b XX 4d ┐↘ ┐↙ ██ ┐↖
|
|
373
|
-
*
|
|
374
|
-
*
|
|
375
|
-
* Preference Order:
|
|
376
|
-
* - Flow from corner 1a 2b 3c 4d └↘ ┘↙ ┌↗ ┐↖
|
|
377
|
-
* - Open adjacent to target 4a 3b 2c 1d ┐↘ ┌↙ ┘↗ └↖
|
|
378
|
-
* - Overlay target 3a 4b 1c 2d ┌↘ ┐↙ └↗ ┘↖
|
|
379
|
-
* - Open from horizontal side 5e 6f │→ │←
|
|
380
|
-
* - Open from center 9i █·
|
|
381
|
-
*/
|
|
382
|
-
|
|
383
|
-
/** @type {import('../utils/popup.js').CanAnchorPopUpOptions[]} */
|
|
384
|
-
const preferences = [
|
|
385
|
-
(!this.submenu && (this.flow ?? 'corner') === 'corner') ? [
|
|
386
|
-
{ clientY: 'bottom', clientX: xStart },
|
|
387
|
-
{ clientY: 'bottom', clientX: xEnd },
|
|
388
|
-
{ clientY: 'top', clientX: xStart },
|
|
389
|
-
{ clientY: 'top', clientX: xEnd },
|
|
390
|
-
] : [],
|
|
391
|
-
(this.submenu || (this.flow ?? 'adjacent') === 'adjacent') ? [
|
|
392
|
-
{ clientY: 'top', clientX: xEnd, directionX: xEnd, directionY: 'down' },
|
|
393
|
-
{ clientY: 'top', clientX: xStart, directionX: xStart, directionY: 'down' },
|
|
394
|
-
{ clientY: 'bottom', clientX: xEnd, directionX: xEnd, directionY: 'up' },
|
|
395
|
-
{ clientY: 'bottom', clientX: xStart, directionX: xStart, directionY: 'up' },
|
|
396
|
-
] : [],
|
|
397
|
-
(!this.submenu && (this.flow ?? 'overlay') === 'overlay') ? [
|
|
398
|
-
{ clientY: 'top', clientX: xStart, directionX: xEnd, directionY: 'down' },
|
|
399
|
-
{ clientY: 'top', clientX: xEnd, directionX: xStart, directionY: 'down' },
|
|
400
|
-
{ clientY: 'bottom', clientX: xStart, directionX: xEnd, directionY: 'up' },
|
|
401
|
-
{ clientY: 'bottom', clientX: xEnd, directionX: xStart, directionY: 'up' },
|
|
402
|
-
] : [],
|
|
403
|
-
(!this.submenu && (this.flow ?? 'vcenter') === 'vcenter') ? [
|
|
404
|
-
{ clientY: 'center', clientX: xEnd, directionX: xEnd, directionY: 'center' },
|
|
405
|
-
{ clientY: 'center', clientX: xStart, directionX: xStart, directionY: 'center' },
|
|
406
|
-
] : [],
|
|
407
|
-
(!this.submenu && (this.flow ?? 'hcenter') === 'hcenter') ? [
|
|
408
|
-
{ clientY: 'bottom', clientX: 'center', directionX: 'center', directionY: 'down' },
|
|
409
|
-
{ clientY: 'top', clientX: 'center', directionX: 'center', directionY: 'up' },
|
|
410
|
-
] : [],
|
|
411
|
-
(!this.submenu && (this.flow ?? 'center') === 'center') ? [
|
|
412
|
-
{ clientY: 'center', clientX: 'center', directionX: 'center', directionY: 'center' },
|
|
413
|
-
] : [],
|
|
414
|
-
].flat();
|
|
415
|
-
|
|
416
|
-
let anchorResult;
|
|
417
|
-
for (const preference of preferences) {
|
|
418
|
-
anchorResult = canAnchorPopup({
|
|
419
|
-
...anchorOptions,
|
|
420
|
-
...preference,
|
|
421
|
-
});
|
|
422
|
-
if (anchorResult) break;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
if (!anchorResult) {
|
|
426
|
-
anchorResult = canAnchorPopup({
|
|
427
|
-
...anchorOptions,
|
|
428
|
-
...preferences[0],
|
|
429
|
-
force: true,
|
|
430
|
-
});
|
|
431
|
-
}
|
|
432
|
-
|
|
433
|
-
surface.style.setProperty('inset-block-start', `${anchorResult.pageY}px`);
|
|
434
|
-
surface.style.setProperty('inset-inline-start', `${anchorResult.pageX}px`);
|
|
435
|
-
surface.style.setProperty('margin', '0');
|
|
436
|
-
surface.style.setProperty('transform-origin', `${anchorResult.transformOriginY} ${anchorResult.transformOriginX}`);
|
|
437
|
-
surface.scrollIntoView();
|
|
438
|
-
},
|
|
439
|
-
/**
|
|
440
|
-
* @param {MouseEvent|PointerEvent|HTMLElement|Element} source
|
|
441
|
-
* @return {boolean} handled
|
|
442
|
-
*/
|
|
443
|
-
showModal(source) {
|
|
444
|
-
if (this.open) return false;
|
|
445
|
-
this.modal = true;
|
|
446
|
-
if (supportsHTMLDialogElement) {
|
|
447
|
-
this._dialog.showModal();
|
|
448
|
-
this._isNativeModal = true;
|
|
449
|
-
}
|
|
450
|
-
return this.showPopup(source);
|
|
451
|
-
},
|
|
452
|
-
/**
|
|
453
|
-
* @param {MouseEvent|PointerEvent|HTMLElement|Element} source
|
|
454
|
-
* @return {boolean} handled
|
|
455
|
-
*/
|
|
456
|
-
showPopup(source) {
|
|
457
|
-
if (this.open) return false;
|
|
458
|
-
this.open = true;
|
|
459
|
-
|
|
460
|
-
const previousFocus = source instanceof HTMLElement ? source : document.activeElement;
|
|
461
|
-
this.updateMenuPosition(source);
|
|
462
|
-
if (supportsHTMLDialogElement && !this._dialog.open) {
|
|
463
|
-
this._dialog.show();
|
|
464
|
-
}
|
|
465
|
-
|
|
466
|
-
const newState = { hash: Math.random().toString(36).slice(2, 18) };
|
|
467
|
-
let previousState = null;
|
|
468
|
-
|
|
469
|
-
if (!window.history.state) {
|
|
470
|
-
// Create new previous state
|
|
471
|
-
window.history.replaceState({
|
|
472
|
-
hash: Math.random().toString(36).slice(2, 18),
|
|
473
|
-
}, document.title);
|
|
474
|
-
}
|
|
475
|
-
previousState = window.history.state;
|
|
476
|
-
const scrollRestoration = window.history.scrollRestoration;
|
|
477
|
-
window.history.scrollRestoration = 'manual';
|
|
478
|
-
window.history.pushState(newState, document.title);
|
|
479
|
-
console.debug('Menu pushed page');
|
|
480
|
-
window.addEventListener('popstate', onPopState);
|
|
481
|
-
window.addEventListener('beforeunload', onBeforeUnload);
|
|
482
|
-
|
|
483
|
-
window.addEventListener('resize', onWindowResize);
|
|
484
|
-
window.addEventListener('scroll', onWindowResize);
|
|
485
|
-
|
|
486
|
-
OPEN_MENUS.push({
|
|
487
|
-
element: this,
|
|
488
|
-
previousFocus,
|
|
489
|
-
state: newState,
|
|
490
|
-
previousState,
|
|
491
|
-
originalEvent: source,
|
|
492
|
-
scrollRestoration,
|
|
493
|
-
});
|
|
494
|
-
|
|
495
|
-
this.focus();
|
|
496
|
-
|
|
497
|
-
return true;
|
|
498
|
-
},
|
|
499
|
-
/**
|
|
500
|
-
* @param {boolean} returnFocus Return focus to element focused during open
|
|
501
|
-
* @return {boolean} handled
|
|
502
|
-
*/
|
|
503
|
-
close(returnFocus = true) {
|
|
504
|
-
if (!this.open) return false;
|
|
505
|
-
if (this._closing) return false;
|
|
506
|
-
this._closing = true;
|
|
507
|
-
this.modal = false;
|
|
508
|
-
if (this._isNativeModal) {
|
|
509
|
-
this._isNativeModal = false;
|
|
510
|
-
} else {
|
|
511
|
-
const main = document.querySelector('main');
|
|
512
|
-
if (main) {
|
|
513
|
-
main.removeAttribute('aria-hidden');
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
// if (this.dialogElement.getAttribute('aria-hidden') === 'true') return false;
|
|
517
|
-
if (supportsHTMLDialogElement && this._dialog.open) {
|
|
518
|
-
const previousFocus = document.activeElement;
|
|
519
|
-
// Closing a native dialog will return focus automatically.
|
|
520
|
-
this._dialog.close();
|
|
521
|
-
if (!attemptFocus(previousFocus, { preventScroll: true })) {
|
|
522
|
-
document.activeElement?.blur?.();
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
// Will invoke observed attribute change: ('aria-hidden', 'true');
|
|
527
|
-
|
|
528
|
-
this.open = false;
|
|
529
|
-
this.dispatchEvent(new Event('close'));
|
|
530
|
-
|
|
531
|
-
const len = OPEN_MENUS.length;
|
|
532
|
-
for (let i = len - 1; i >= 0; i--) {
|
|
533
|
-
const entry = OPEN_MENUS[i];
|
|
534
|
-
if (entry.element === this) {
|
|
535
|
-
if (entry.state && window.history && window.history.state && entry.state.hash === window.history.state.hash) {
|
|
536
|
-
window.removeEventListener('popstate', onPopState);
|
|
537
|
-
window.history.back();
|
|
538
|
-
// Back does not set state immediately
|
|
539
|
-
// Needed to track submenu
|
|
540
|
-
// TODO: use window.history.go(indexDelta) instead for Safari (not Wekbit) submenu support
|
|
541
|
-
window.history.replaceState(entry.previousState, document.title);
|
|
542
|
-
window.history.scrollRestoration = entry.scrollRestoration || 'auto';
|
|
543
|
-
window.addEventListener('popstate', onPopState);
|
|
544
|
-
} else {
|
|
545
|
-
console.warn('Menu state mismatch?', entry, window.history.state);
|
|
546
|
-
}
|
|
547
|
-
if (returnFocus) {
|
|
548
|
-
entry.previousFocus?.focus?.({ preventScroll: true });
|
|
549
|
-
}
|
|
550
|
-
OPEN_MENUS.splice(i, 1);
|
|
551
|
-
break;
|
|
552
|
-
} else if (this.contains(entry.element)) {
|
|
553
|
-
console.debug('Closing submenu first');
|
|
554
|
-
entry.element.close(false);
|
|
555
|
-
console.debug('Sub menu closed. Continuing...');
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
if (!OPEN_MENUS.length) {
|
|
559
|
-
window.removeEventListener('popstate', onPopState);
|
|
560
|
-
window.removeEventListener('beforeunload', onBeforeUnload);
|
|
561
|
-
window.removeEventListener('resize', onWindowResize);
|
|
562
|
-
console.debug('All menus closed');
|
|
563
|
-
}
|
|
564
|
-
this._closing = false;
|
|
565
|
-
return true;
|
|
566
|
-
},
|
|
567
111
|
/**
|
|
568
112
|
* @param {HTMLElement} cascader Element that calls for submenu cascade
|
|
569
113
|
*/
|
|
570
114
|
cascade(cascader) {
|
|
571
115
|
this.cascader = cascader;
|
|
572
|
-
this.showPopup(cascader);
|
|
573
|
-
},
|
|
574
|
-
/**
|
|
575
|
-
* @param {MouseEvent|PointerEvent|HTMLElement|Element} source
|
|
576
|
-
* @return {boolean} handled
|
|
577
|
-
*/
|
|
578
|
-
show(source) {
|
|
579
|
-
// Auto-select type based on default platform convention
|
|
580
|
-
// Mac OS X / iPad does not expect clickthrough
|
|
581
|
-
if (navigator.userAgent.includes('Mac OS X')) {
|
|
582
|
-
return this.showModal(source);
|
|
583
|
-
}
|
|
584
|
-
return this.showPopup(source);
|
|
116
|
+
this.showPopup(cascader, true, 'adjacent');
|
|
585
117
|
},
|
|
586
118
|
})
|
|
587
119
|
.events({
|
|
120
|
+
'mdw-menu-item:cascade'(event) {
|
|
121
|
+
const menuItem = event.target;
|
|
122
|
+
const subMenuId = event.detail;
|
|
123
|
+
event.stopPropagation();
|
|
124
|
+
|
|
125
|
+
const submenu = this.getRootNode().getElementById(subMenuId);
|
|
126
|
+
this.submenu = submenu;
|
|
127
|
+
submenu.cascade(menuItem);
|
|
128
|
+
},
|
|
129
|
+
'mdw-menu-item:cascader-blur'() {
|
|
130
|
+
const submenu = this.submenu;
|
|
131
|
+
// Wait for focus event (if mouse focus on sub menu item)
|
|
132
|
+
queueMicrotask(() => {
|
|
133
|
+
// Stay open if submenu is focused
|
|
134
|
+
if (submenu && submenu.matches(':focus-within,:focus')) return;
|
|
135
|
+
|
|
136
|
+
submenu.close(false);
|
|
137
|
+
});
|
|
138
|
+
},
|
|
139
|
+
|
|
588
140
|
'~click'(event) {
|
|
589
141
|
if (this !== event.target) return;
|
|
590
142
|
// Clicked self (scrim-like)
|
|
@@ -608,7 +160,7 @@ export default CustomElement
|
|
|
608
160
|
// Unless menu hiding is cancelled
|
|
609
161
|
case 'ArrowLeft':
|
|
610
162
|
case 'ArrowRight':
|
|
611
|
-
if (!this.submenu) break;
|
|
163
|
+
// if (!this.submenu) break;
|
|
612
164
|
if (getComputedStyle(this).direction === 'rtl') {
|
|
613
165
|
if (event.key === 'ArrowLeft') break;
|
|
614
166
|
} else if (event.key === 'ArrowRight') break;
|
|
@@ -622,17 +174,16 @@ export default CustomElement
|
|
|
622
174
|
default:
|
|
623
175
|
}
|
|
624
176
|
},
|
|
625
|
-
focusout(
|
|
177
|
+
focusout() {
|
|
626
178
|
if (!this.open) return;
|
|
627
179
|
if (this.modal) return;
|
|
628
180
|
// Wait until end of event loop cycle to see if focus really is lost
|
|
629
181
|
queueMicrotask(() => {
|
|
630
182
|
if (this.matches(':focus-within')) return;
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
}
|
|
183
|
+
const { cascader, submenu } = this;
|
|
184
|
+
|
|
185
|
+
if (cascader && cascader.matches(':is(:focus-within,:focus)')) return;
|
|
186
|
+
if (submenu && submenu.matches(':is(:focus-within,:focus)')) return;
|
|
636
187
|
this.close(false);
|
|
637
188
|
});
|
|
638
189
|
},
|