mtrl 0.9.4 → 0.9.6
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/dist/components/badge/api.d.ts +0 -1
- package/dist/components/badge/api.js +3 -1
- package/dist/components/bottom-app-bar/bottom-app-bar.js +9 -2
- package/dist/components/button/features/progress.d.ts +5 -0
- package/dist/components/button/features/progress.js +16 -12
- package/dist/components/card/api.js +17 -14
- package/dist/components/card/config.js +1 -1
- package/dist/components/card/content.js +2 -5
- package/dist/components/dialog/config.js +0 -1
- package/dist/components/dialog/features.js +5 -4
- package/dist/components/dialog/types.d.ts +3 -2
- package/dist/components/divider/features.d.ts +2 -46
- package/dist/components/divider/features.js +54 -115
- package/dist/components/menu/config.js +2 -2
- package/dist/components/menu/features/controller.js +37 -23
- package/dist/components/menu/features/opener.js +4 -2
- package/dist/components/menu/types.d.ts +12 -1
- package/dist/components/radios/api.d.ts +4 -6
- package/dist/components/radios/api.js +5 -25
- package/dist/components/radios/radio.js +8 -33
- package/dist/components/search/features/states.js +2 -0
- package/dist/components/search/features/suggestions.js +3 -0
- package/dist/components/select/api.js +2 -0
- package/dist/components/select/features.js +252 -31
- package/dist/components/slider/features/controller.js +11 -2
- package/dist/components/switch/switch.js +1 -1
- package/dist/components/tabs/api.js +12 -6
- package/dist/components/tabs/features.js +5 -7
- package/dist/components/tabs/tabs.js +16 -1
- package/dist/components/tabs/utils.d.ts +8 -0
- package/dist/components/tabs/utils.js +42 -17
- package/dist/components/textfield/features/error.d.ts +2 -0
- package/dist/components/textfield/features/error.js +22 -0
- package/dist/components/textfield/features/supporting-text.d.ts +1 -0
- package/dist/components/textfield/features/supporting-text.js +25 -0
- package/dist/components/tooltip/api.js +42 -7
- package/dist/components/top-app-bar/top-app-bar.js +3 -0
- package/dist/core/compose/features/input.js +1 -1
- package/dist/core/compose/features/textinput.d.ts +4 -0
- package/dist/core/compose/features/textinput.js +1 -0
- package/dist/core/compose/features/textlabel.js +3 -0
- package/dist/index.cjs +0 -0
- package/dist/package.json +1 -1
- package/dist/styles/badge.css +1 -1
- package/dist/styles/base.css +2 -2
- package/dist/styles/bottom-app-bar.css +1 -1
- package/dist/styles/bottom-sheet.css +2 -2
- package/dist/styles/button-group.css +1 -1
- package/dist/styles/button.css +2 -2
- package/dist/styles/card.css +2 -2
- package/dist/styles/carousel.css +1 -1
- package/dist/styles/checkbox.css +2 -2
- package/dist/styles/chips.css +2 -2
- package/dist/styles/dialog.css +2 -2
- package/dist/styles/divider.css +1 -1
- package/dist/styles/drawer.css +2 -2
- package/dist/styles/extended-fab.css +2 -2
- package/dist/styles/fab.css +2 -2
- package/dist/styles/icon-button.css +2 -2
- package/dist/styles/list.css +2 -2
- package/dist/styles/loading-indicator.css +1 -1
- package/dist/styles/menu.css +2 -2
- package/dist/styles/navigation-rail.css +1 -1
- package/dist/styles/navigation.css +2 -2
- package/dist/styles/progress.css +2 -2
- package/dist/styles/radios.css +1 -1
- package/dist/styles/search.css +1 -1
- package/dist/styles/segmented-button.css +2 -2
- package/dist/styles/select.css +2 -2
- package/dist/styles/side-sheet.css +2 -2
- package/dist/styles/slider.css +1 -1
- package/dist/styles/snackbar.css +1 -1
- package/dist/styles/split-button.css +2 -2
- package/dist/styles/switch.css +2 -2
- package/dist/styles/tabs.css +1 -1
- package/dist/styles/textfield.css +2 -2
- package/dist/styles/timepicker.css +1 -1
- package/dist/styles/tooltip.css +2 -2
- package/dist/styles/top-app-bar.css +1 -1
- package/dist/styles/utilities.css +1 -1
- package/dist/styles.css +2 -2
- package/dist/themes/autumn.css +1 -1
- package/dist/themes/baseline.css +1 -1
- package/dist/themes/brownbeige.css +1 -1
- package/dist/themes/browngreen.css +1 -1
- package/dist/themes/desert.css +1 -1
- package/dist/themes/forest.css +1 -1
- package/dist/themes/highcontrast.css +1 -1
- package/dist/themes/legacy.css +1 -1
- package/dist/themes/material.css +1 -1
- package/dist/themes/ocean.css +1 -1
- package/dist/themes/sageivory.css +1 -1
- package/dist/themes/spring.css +1 -1
- package/dist/themes/summer.css +1 -1
- package/dist/themes/sunset.css +1 -1
- package/dist/themes/tealcaramel.css +1 -1
- package/dist/themes/winter.css +1 -1
- package/package.json +1 -1
- package/src/styles/abstract/_mixins.scss +26 -0
- package/src/styles/abstract/_variables.scss +25 -11
- package/src/styles/components/_bottom-sheet.scss +23 -7
- package/src/styles/components/_dialog.scss +59 -26
- package/src/styles/components/_drawer.scss +60 -7
- package/src/styles/components/_menu.scss +12 -7
- package/src/styles/components/_side-sheet.scss +33 -4
- package/src/styles/components/_tooltip.scss +3 -0
|
@@ -30,7 +30,6 @@ interface ComponentWithElements {
|
|
|
30
30
|
};
|
|
31
31
|
getClass: (name: string) => string;
|
|
32
32
|
addClass: (...classes: string[]) => ComponentWithElements;
|
|
33
|
-
removeClass: (...classes: string[]) => ComponentWithElements;
|
|
34
33
|
on: (event: string, handler: Function) => ComponentWithElements;
|
|
35
34
|
off: (event: string, handler: Function) => ComponentWithElements;
|
|
36
35
|
}
|
|
@@ -84,9 +84,11 @@ export const withAPI = ({ visibility, lifecycle }) => (component) => {
|
|
|
84
84
|
component.config.variant = variant;
|
|
85
85
|
if (variant === VARIANT_SMALL) {
|
|
86
86
|
component.element.textContent = "";
|
|
87
|
+
component.element.removeAttribute("role");
|
|
87
88
|
component.element.setAttribute("aria-hidden", "true");
|
|
88
89
|
}
|
|
89
90
|
else {
|
|
91
|
+
component.element.removeAttribute("aria-hidden");
|
|
90
92
|
component.element.setAttribute("role", "status");
|
|
91
93
|
if (component.config.label !== undefined) {
|
|
92
94
|
badgeComponent.setLabel(component.config.label);
|
|
@@ -146,7 +148,7 @@ export const withAPI = ({ visibility, lifecycle }) => (component) => {
|
|
|
146
148
|
return badgeComponent;
|
|
147
149
|
},
|
|
148
150
|
removeClass: (...classes) => {
|
|
149
|
-
component.
|
|
151
|
+
component.element.classList.remove(...classes.filter(Boolean));
|
|
150
152
|
return badgeComponent;
|
|
151
153
|
},
|
|
152
154
|
on: (event, handler) => {
|
|
@@ -37,13 +37,11 @@ export const createBottomAppBar = (config = {}) => {
|
|
|
37
37
|
if (currentScrollY > prevScrollY + 10) {
|
|
38
38
|
if (isVisible) {
|
|
39
39
|
bottomBar.hide();
|
|
40
|
-
componentConfig.onVisibilityChange?.(false);
|
|
41
40
|
}
|
|
42
41
|
}
|
|
43
42
|
else if (currentScrollY < prevScrollY - 10) {
|
|
44
43
|
if (!isVisible) {
|
|
45
44
|
bottomBar.show();
|
|
46
|
-
componentConfig.onVisibilityChange?.(true);
|
|
47
45
|
}
|
|
48
46
|
}
|
|
49
47
|
prevScrollY = currentScrollY;
|
|
@@ -57,6 +55,9 @@ export const createBottomAppBar = (config = {}) => {
|
|
|
57
55
|
}
|
|
58
56
|
const bottomBar = {
|
|
59
57
|
...withLifecycleComponent,
|
|
58
|
+
destroy() {
|
|
59
|
+
withLifecycleComponent.lifecycle.destroy();
|
|
60
|
+
},
|
|
60
61
|
addAction(button) {
|
|
61
62
|
actionsContainer.appendChild(button);
|
|
62
63
|
return this;
|
|
@@ -69,12 +70,18 @@ export const createBottomAppBar = (config = {}) => {
|
|
|
69
70
|
},
|
|
70
71
|
show() {
|
|
71
72
|
this.element.classList.remove(`${component.getClass("bottom-app-bar")}--hidden`);
|
|
73
|
+
const changed = !isVisible;
|
|
72
74
|
isVisible = true;
|
|
75
|
+
if (changed)
|
|
76
|
+
componentConfig.onVisibilityChange?.(true);
|
|
73
77
|
return this;
|
|
74
78
|
},
|
|
75
79
|
hide() {
|
|
76
80
|
this.element.classList.add(`${component.getClass("bottom-app-bar")}--hidden`);
|
|
81
|
+
const changed = isVisible;
|
|
77
82
|
isVisible = false;
|
|
83
|
+
if (changed)
|
|
84
|
+
componentConfig.onVisibilityChange?.(false);
|
|
78
85
|
return this;
|
|
79
86
|
},
|
|
80
87
|
isVisible() {
|
|
@@ -17,6 +17,11 @@ interface ProgressEnhancedComponent {
|
|
|
17
17
|
};
|
|
18
18
|
setText?: (text: string) => unknown;
|
|
19
19
|
getText?: () => string;
|
|
20
|
+
/** The label manager withText installs; the setText/getText above only exist after withAPI. */
|
|
21
|
+
text?: {
|
|
22
|
+
setText: (text: string) => unknown;
|
|
23
|
+
getText: () => string;
|
|
24
|
+
};
|
|
20
25
|
showProgress?: () => Promise<ProgressEnhancedComponent>;
|
|
21
26
|
showProgressSync?: () => ProgressEnhancedComponent;
|
|
22
27
|
hideProgress?: () => Promise<ProgressEnhancedComponent>;
|
|
@@ -109,32 +109,36 @@ export const withProgress = (config) => (component) => {
|
|
|
109
109
|
component.setIndeterminate(indeterminate);
|
|
110
110
|
return component;
|
|
111
111
|
};
|
|
112
|
+
const readLabel = () => component.text ? component.text.getText() : component.getText?.();
|
|
113
|
+
const writeLabel = (value) => {
|
|
114
|
+
if (component.text)
|
|
115
|
+
component.text.setText(value);
|
|
116
|
+
else
|
|
117
|
+
component.setText?.(value);
|
|
118
|
+
};
|
|
112
119
|
component.setLoading = async function (loading, text) {
|
|
113
120
|
if (loading && !isLoading) {
|
|
114
|
-
|
|
115
|
-
originalText = component.getText();
|
|
116
|
-
}
|
|
121
|
+
originalText = readLabel() ?? "";
|
|
117
122
|
isLoading = true;
|
|
123
|
+
component.element.setAttribute("aria-busy", "true");
|
|
118
124
|
await component.showProgress();
|
|
119
125
|
if (component.disabled?.disable) {
|
|
120
126
|
component.disabled.disable();
|
|
121
127
|
}
|
|
122
|
-
if (text
|
|
123
|
-
|
|
124
|
-
}
|
|
128
|
+
if (text)
|
|
129
|
+
writeLabel(text);
|
|
125
130
|
}
|
|
126
131
|
else if (!loading && isLoading) {
|
|
127
132
|
isLoading = false;
|
|
133
|
+
component.element.removeAttribute("aria-busy");
|
|
128
134
|
await component.hideProgress();
|
|
129
135
|
if (component.disabled?.enable) {
|
|
130
136
|
component.disabled.enable();
|
|
131
137
|
}
|
|
132
|
-
if (text
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
component.setText(originalText);
|
|
137
|
-
}
|
|
138
|
+
if (text)
|
|
139
|
+
writeLabel(text);
|
|
140
|
+
else if (originalText)
|
|
141
|
+
writeLabel(originalText);
|
|
138
142
|
}
|
|
139
143
|
return component;
|
|
140
144
|
};
|
|
@@ -9,24 +9,27 @@ export const withAPI = ({ lifecycle, config }) => (component) => ({
|
|
|
9
9
|
return this;
|
|
10
10
|
},
|
|
11
11
|
setHeader(headerElement) {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
const card = component.getClass('card');
|
|
13
|
+
if (headerElement && headerElement.classList.contains(`${card}-header`)) {
|
|
14
|
+
const title = (header) => header?.querySelector(`.${card}-header-title`)?.id || null;
|
|
15
|
+
const existingHeader = component.element.querySelector(`.${card}-header`);
|
|
14
16
|
if (existingHeader) {
|
|
17
|
+
const previous = title(existingHeader);
|
|
18
|
+
if (previous && component.element.getAttribute('aria-labelledby') === previous) {
|
|
19
|
+
component.element.removeAttribute('aria-labelledby');
|
|
20
|
+
}
|
|
15
21
|
existingHeader.remove();
|
|
16
22
|
}
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
const lastMedia = mediaElements[mediaElements.length - 1];
|
|
21
|
-
if (lastMedia.nextSibling) {
|
|
22
|
-
component.element.insertBefore(headerElement, lastMedia.nextSibling);
|
|
23
|
-
}
|
|
24
|
-
else {
|
|
25
|
-
component.element.appendChild(headerElement);
|
|
26
|
-
}
|
|
23
|
+
let before = component.element.firstElementChild;
|
|
24
|
+
while (before && before.classList.contains(`${card}-media`)) {
|
|
25
|
+
before = before.nextElementSibling;
|
|
27
26
|
}
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
component.element.insertBefore(headerElement, before);
|
|
28
|
+
const titleId = title(headerElement);
|
|
29
|
+
if (titleId &&
|
|
30
|
+
!component.element.hasAttribute('aria-labelledby') &&
|
|
31
|
+
!component.element.hasAttribute('aria-label')) {
|
|
32
|
+
component.element.setAttribute('aria-labelledby', titleId);
|
|
30
33
|
}
|
|
31
34
|
}
|
|
32
35
|
return this;
|
|
@@ -75,7 +75,7 @@ export const getElementConfig = (config) => {
|
|
|
75
75
|
if (config.aria) {
|
|
76
76
|
Object.entries(config.aria).forEach(([key, value]) => {
|
|
77
77
|
if (value !== undefined) {
|
|
78
|
-
const attrName = key.startsWith("aria-") ? key : `aria-${key}`;
|
|
78
|
+
const attrName = key === "role" || key.startsWith("aria-") ? key : `aria-${key}`;
|
|
79
79
|
ariaAttributes[attrName] = value;
|
|
80
80
|
}
|
|
81
81
|
});
|
|
@@ -4,6 +4,7 @@ import { createBase, withElement } from "../../core/compose/component.js";
|
|
|
4
4
|
import { createElement } from "../../core/dom/create.js";
|
|
5
5
|
import { safeUrl } from "../../core/utils/url.js";
|
|
6
6
|
export const CARD_CONTENT_PADDING = true;
|
|
7
|
+
let headerCount = 0;
|
|
7
8
|
export const createCardContent = (config = {}) => {
|
|
8
9
|
const baseConfig = {
|
|
9
10
|
...config,
|
|
@@ -73,13 +74,9 @@ export const createCardHeader = (config = {}) => {
|
|
|
73
74
|
text: config.title,
|
|
74
75
|
container: textContainer,
|
|
75
76
|
attributes: {
|
|
76
|
-
id: `${header.element.id ||
|
|
77
|
+
id: `${header.element.id || `card-header-${++headerCount}`}-title`
|
|
77
78
|
}
|
|
78
79
|
});
|
|
79
|
-
const parentCard = header.element.closest(`.${PREFIX}-card`);
|
|
80
|
-
if (parentCard && !parentCard.hasAttribute('aria-labelledby')) {
|
|
81
|
-
parentCard.setAttribute('aria-labelledby', `${header.element.id || 'card-header'}-title`);
|
|
82
|
-
}
|
|
83
80
|
}
|
|
84
81
|
if (config.subtitle) {
|
|
85
82
|
createElement({
|
|
@@ -236,7 +236,8 @@ const addButton = (footer, buttonConfig, component) => {
|
|
|
236
236
|
};
|
|
237
237
|
export const withVisibility = () => (component) => {
|
|
238
238
|
const isOpen = component.config.open === true;
|
|
239
|
-
const
|
|
239
|
+
const openDuration = component.config.animationDuration ?? 500;
|
|
240
|
+
const closeDuration = component.config.animationDuration ?? 150;
|
|
240
241
|
const focusableElements = 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])';
|
|
241
242
|
let previouslyFocusedElement = null;
|
|
242
243
|
let mouseDownOnOverlay = false;
|
|
@@ -370,8 +371,8 @@ export const withVisibility = () => (component) => {
|
|
|
370
371
|
const container = component.structure.container || document.body;
|
|
371
372
|
container.appendChild(component.overlay);
|
|
372
373
|
}
|
|
373
|
-
addClass(component.overlay, `${component.getClass("dialog-overlay")}--visible`);
|
|
374
374
|
setTimeout(() => {
|
|
375
|
+
addClass(component.overlay, `${component.getClass("dialog-overlay")}--visible`);
|
|
375
376
|
addClass(component.element, `${component.getClass("dialog")}--visible`);
|
|
376
377
|
trapFocus();
|
|
377
378
|
setupEvents();
|
|
@@ -379,7 +380,7 @@ export const withVisibility = () => (component) => {
|
|
|
379
380
|
component.emit(DIALOG_EVENTS.OPEN, { dialog: component });
|
|
380
381
|
setTimeout(() => {
|
|
381
382
|
component.emit(DIALOG_EVENTS.AFTER_OPEN, { dialog: component });
|
|
382
|
-
},
|
|
383
|
+
}, openDuration);
|
|
383
384
|
}
|
|
384
385
|
}, 10);
|
|
385
386
|
},
|
|
@@ -414,7 +415,7 @@ export const withVisibility = () => (component) => {
|
|
|
414
415
|
if (typeof component.emit === "function") {
|
|
415
416
|
component.emit(DIALOG_EVENTS.AFTER_CLOSE, { dialog: component });
|
|
416
417
|
}
|
|
417
|
-
},
|
|
418
|
+
}, closeDuration);
|
|
418
419
|
},
|
|
419
420
|
toggle(open) {
|
|
420
421
|
if (open === undefined) {
|
|
@@ -164,8 +164,9 @@ export interface DialogConfig {
|
|
|
164
164
|
*/
|
|
165
165
|
zIndex?: number;
|
|
166
166
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
167
|
+
* How long `afteropen` and `afterclose` wait after opening and closing, in
|
|
168
|
+
* milliseconds, and when a closed dialog leaves the DOM. By default they
|
|
169
|
+
* follow the stylesheet: 450 after opening, 200 after closing.
|
|
169
170
|
*/
|
|
170
171
|
animationDuration?: number;
|
|
171
172
|
/**
|
|
@@ -1,50 +1,6 @@
|
|
|
1
1
|
import { BaseComponent, ElementComponent } from "../../core/compose/index.js";
|
|
2
2
|
import { DividerConfig } from "./config.js";
|
|
3
3
|
import { DividerComponent } from "./types.js";
|
|
4
|
-
/**
|
|
5
|
-
* Adds orientation functionality to divider
|
|
6
|
-
*
|
|
7
|
-
* Higher-order function that enhances a component with the ability to be
|
|
8
|
-
* oriented horizontally (default) or vertically. Controls the appropriate
|
|
9
|
-
* CSS classes and dimensional styling based on orientation.
|
|
10
|
-
*
|
|
11
|
-
* @param config - Divider configuration
|
|
12
|
-
* @returns Function that enhances a component with orientation capabilities
|
|
13
|
-
*
|
|
14
|
-
* @internal
|
|
15
|
-
*/
|
|
16
4
|
export declare const withOrientation: (config: DividerConfig) => <C extends ElementComponent & BaseComponent>(component: C) => C & Partial<DividerComponent>;
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
*
|
|
20
|
-
* Higher-order function that enhances a component with the ability to have
|
|
21
|
-
* customized inset spacing. Supports three variants (full-width, inset, middle-inset)
|
|
22
|
-
* and allows fine-grained control over start and end insets.
|
|
23
|
-
*
|
|
24
|
-
* Insets are applied as margins in the appropriate direction based on orientation:
|
|
25
|
-
* - For horizontal dividers: left/right margins
|
|
26
|
-
* - For vertical dividers: top/bottom margins
|
|
27
|
-
*
|
|
28
|
-
* @param config - Divider configuration
|
|
29
|
-
* @returns Function that enhances a component with inset capabilities
|
|
30
|
-
*
|
|
31
|
-
* @internal
|
|
32
|
-
*/
|
|
33
|
-
export declare const withInset: (config: DividerConfig) => <C extends ElementComponent & Partial<DividerComponent>>(component: C) => C & Partial<DividerComponent>;
|
|
34
|
-
/**
|
|
35
|
-
* Adds style customization to divider
|
|
36
|
-
*
|
|
37
|
-
* Higher-order function that enhances a component with visual customization
|
|
38
|
-
* capabilities, including:
|
|
39
|
-
* - Custom thickness (height for horizontal, width for vertical dividers)
|
|
40
|
-
* - Custom colors (background-color CSS property)
|
|
41
|
-
*
|
|
42
|
-
* These styling options allow dividers to be visually adapted to different
|
|
43
|
-
* design requirements while maintaining Material Design principles.
|
|
44
|
-
*
|
|
45
|
-
* @param config - Divider configuration
|
|
46
|
-
* @returns Function that enhances a component with style capabilities
|
|
47
|
-
*
|
|
48
|
-
* @internal
|
|
49
|
-
*/
|
|
50
|
-
export declare const withStyle: (config: DividerConfig) => <C extends ElementComponent & Partial<DividerComponent>>(component: C) => C & Partial<DividerComponent>;
|
|
5
|
+
export declare const withInset: (config: DividerConfig) => <C extends ElementComponent & BaseComponent & Partial<DividerComponent>>(component: C) => C & Partial<DividerComponent>;
|
|
6
|
+
export declare const withStyle: (config: DividerConfig) => <C extends ElementComponent & BaseComponent & Partial<DividerComponent>>(component: C) => C & Partial<DividerComponent>;
|
|
@@ -1,138 +1,77 @@
|
|
|
1
|
-
|
|
1
|
+
const ORIENTATIONS = ['horizontal', 'vertical'];
|
|
2
|
+
const VARIANTS = ['full-width', 'inset', 'middle-inset'];
|
|
3
|
+
const DEFAULT_INSET = 16;
|
|
4
|
+
const applyLayout = (component, config) => {
|
|
5
|
+
const { element } = component;
|
|
6
|
+
const base = component.getClass('divider');
|
|
2
7
|
const orientation = config.orientation || 'horizontal';
|
|
3
|
-
|
|
8
|
+
const variant = config.variant || 'full-width';
|
|
4
9
|
const thickness = config.thickness || 1;
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
const horizontal = orientation === 'horizontal';
|
|
11
|
+
ORIENTATIONS.forEach(name => element.classList.toggle(`${base}--${name}`, name === orientation));
|
|
12
|
+
VARIANTS.forEach(name => element.classList.toggle(`${base}--${name}`, name === variant));
|
|
13
|
+
if (horizontal)
|
|
14
|
+
element.removeAttribute('aria-orientation');
|
|
15
|
+
else
|
|
16
|
+
element.setAttribute('aria-orientation', 'vertical');
|
|
17
|
+
const style = element.style;
|
|
18
|
+
style.marginLeft = style.marginRight = style.marginTop = style.marginBottom = '';
|
|
19
|
+
const cross = horizontal ? 'height' : 'width';
|
|
20
|
+
const main = horizontal ? 'width' : 'height';
|
|
21
|
+
style[cross] = `${thickness}px`;
|
|
22
|
+
style[main] = '100%';
|
|
23
|
+
if (variant !== 'full-width') {
|
|
24
|
+
const insetStart = config.insetStart !== undefined ? config.insetStart : DEFAULT_INSET;
|
|
25
|
+
const insetEnd = config.insetEnd !== undefined
|
|
26
|
+
? config.insetEnd
|
|
27
|
+
: (variant === 'middle-inset' ? DEFAULT_INSET : 0);
|
|
28
|
+
style[horizontal ? 'marginLeft' : 'marginTop'] = `${insetStart}px`;
|
|
29
|
+
style[horizontal ? 'marginRight' : 'marginBottom'] = `${insetEnd}px`;
|
|
30
|
+
style[main] = 'auto';
|
|
12
31
|
}
|
|
32
|
+
};
|
|
33
|
+
export const withOrientation = (config) => (component) => {
|
|
34
|
+
applyLayout(component, config);
|
|
13
35
|
return {
|
|
14
36
|
...component,
|
|
15
37
|
getOrientation() {
|
|
16
|
-
return orientation;
|
|
38
|
+
return config.orientation || 'horizontal';
|
|
17
39
|
},
|
|
18
40
|
setOrientation(newOrientation) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
if (newOrientation === 'horizontal') {
|
|
22
|
-
component.element.style.height = `${thickness}px`;
|
|
23
|
-
component.element.style.width = '100%';
|
|
24
|
-
component.element.style.marginTop = '';
|
|
25
|
-
component.element.style.marginBottom = '';
|
|
26
|
-
}
|
|
27
|
-
else {
|
|
28
|
-
component.element.style.width = `${thickness}px`;
|
|
29
|
-
component.element.style.height = '100%';
|
|
30
|
-
component.element.style.marginLeft = '';
|
|
31
|
-
component.element.style.marginRight = '';
|
|
32
|
-
}
|
|
41
|
+
config.orientation = newOrientation;
|
|
42
|
+
applyLayout(component, config);
|
|
33
43
|
return this;
|
|
34
44
|
}
|
|
35
45
|
};
|
|
36
46
|
};
|
|
37
|
-
export const withInset = (config) => (component) => {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
}
|
|
47
|
+
export const withInset = (config) => (component) => ({
|
|
48
|
+
...component,
|
|
49
|
+
getVariant() {
|
|
50
|
+
return config.variant || 'full-width';
|
|
51
|
+
},
|
|
52
|
+
setVariant(newVariant) {
|
|
53
|
+
config.variant = newVariant;
|
|
54
|
+
applyLayout(component, config);
|
|
55
|
+
return this;
|
|
56
|
+
},
|
|
57
|
+
setInset(insetStart, insetEnd) {
|
|
58
|
+
if (insetStart !== undefined)
|
|
59
|
+
config.insetStart = insetStart;
|
|
60
|
+
if (insetEnd !== undefined)
|
|
61
|
+
config.insetEnd = insetEnd;
|
|
62
|
+
applyLayout(component, config);
|
|
63
|
+
return this;
|
|
55
64
|
}
|
|
56
|
-
|
|
57
|
-
...component,
|
|
58
|
-
getVariant() {
|
|
59
|
-
return variant;
|
|
60
|
-
},
|
|
61
|
-
setVariant(newVariant) {
|
|
62
|
-
component.element.classList.remove(`${component.getClass('divider')}--${variant}`);
|
|
63
|
-
component.element.classList.add(`${component.getClass('divider')}--${newVariant}`);
|
|
64
|
-
const currentOrientation = component.getOrientation ? component.getOrientation() : orientation;
|
|
65
|
-
if (newVariant === 'full-width') {
|
|
66
|
-
if (currentOrientation === 'horizontal') {
|
|
67
|
-
component.element.style.marginLeft = '';
|
|
68
|
-
component.element.style.marginRight = '';
|
|
69
|
-
}
|
|
70
|
-
else {
|
|
71
|
-
component.element.style.marginTop = '';
|
|
72
|
-
component.element.style.marginBottom = '';
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
else {
|
|
76
|
-
const insetStart = config.insetStart !== undefined ? config.insetStart : 16;
|
|
77
|
-
const insetEnd = config.insetEnd !== undefined ? config.insetEnd : (newVariant === 'middle-inset' ? 16 : 0);
|
|
78
|
-
if (currentOrientation === 'horizontal') {
|
|
79
|
-
component.element.style.marginLeft = `${insetStart}px`;
|
|
80
|
-
component.element.style.marginRight = `${insetEnd}px`;
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
component.element.style.marginTop = `${insetStart}px`;
|
|
84
|
-
component.element.style.marginBottom = `${insetEnd}px`;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return this;
|
|
88
|
-
},
|
|
89
|
-
setInset(insetStart, insetEnd) {
|
|
90
|
-
const currentOrientation = component.getOrientation ? component.getOrientation() : orientation;
|
|
91
|
-
const currentVariant = component.getVariant ? component.getVariant() : variant;
|
|
92
|
-
if (currentVariant !== 'full-width') {
|
|
93
|
-
if (currentOrientation === 'horizontal') {
|
|
94
|
-
if (insetStart !== undefined) {
|
|
95
|
-
component.element.style.marginLeft = `${insetStart}px`;
|
|
96
|
-
}
|
|
97
|
-
if (insetEnd !== undefined) {
|
|
98
|
-
component.element.style.marginRight = `${insetEnd}px`;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
else {
|
|
102
|
-
if (insetStart !== undefined) {
|
|
103
|
-
component.element.style.marginTop = `${insetStart}px`;
|
|
104
|
-
}
|
|
105
|
-
if (insetEnd !== undefined) {
|
|
106
|
-
component.element.style.marginBottom = `${insetEnd}px`;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return this;
|
|
111
|
-
}
|
|
112
|
-
};
|
|
113
|
-
};
|
|
65
|
+
});
|
|
114
66
|
export const withStyle = (config) => (component) => {
|
|
115
67
|
if (config.color) {
|
|
116
68
|
component.element.style.backgroundColor = config.color;
|
|
117
69
|
}
|
|
118
|
-
const thickness = config.thickness || 1;
|
|
119
|
-
const orientation = config.orientation || 'horizontal';
|
|
120
|
-
if (orientation === 'horizontal') {
|
|
121
|
-
component.element.style.height = `${thickness}px`;
|
|
122
|
-
}
|
|
123
|
-
else {
|
|
124
|
-
component.element.style.width = `${thickness}px`;
|
|
125
|
-
}
|
|
126
70
|
return {
|
|
127
71
|
...component,
|
|
128
72
|
setThickness(newThickness) {
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
component.element.style.height = `${newThickness}px`;
|
|
132
|
-
}
|
|
133
|
-
else {
|
|
134
|
-
component.element.style.width = `${newThickness}px`;
|
|
135
|
-
}
|
|
73
|
+
config.thickness = newThickness;
|
|
74
|
+
applyLayout(component, config);
|
|
136
75
|
return this;
|
|
137
76
|
},
|
|
138
77
|
setColor(color) {
|
|
@@ -26,8 +26,8 @@ export const getElementConfig = (config) => {
|
|
|
26
26
|
styles.maxHeight = config.maxHeight;
|
|
27
27
|
}
|
|
28
28
|
const attributes = {
|
|
29
|
-
role: "menu",
|
|
30
|
-
tabindex: "-1",
|
|
29
|
+
role: config.listbox ? "presentation" : "menu",
|
|
30
|
+
...(config.listbox ? {} : { tabindex: "-1" }),
|
|
31
31
|
"aria-hidden": (!config.visible).toString(),
|
|
32
32
|
style: Object.entries(styles)
|
|
33
33
|
.map(([key, value]) => `${key}: ${value}`)
|
|
@@ -6,6 +6,11 @@ const withController = (config) => (component) => {
|
|
|
6
6
|
return component;
|
|
7
7
|
}
|
|
8
8
|
const tasks = createMenuTasks();
|
|
9
|
+
const listbox = config.listbox === true;
|
|
10
|
+
const optionIdPrefix = `${component.getClass("menu")}-${Math.random().toString(36).slice(2, 9)}`;
|
|
11
|
+
if (listbox) {
|
|
12
|
+
component.element.addEventListener("mousedown", (e) => e.preventDefault());
|
|
13
|
+
}
|
|
9
14
|
const state = {
|
|
10
15
|
visible: config.visible || false,
|
|
11
16
|
items: config.items || [],
|
|
@@ -51,8 +56,14 @@ const withController = (config) => (component) => {
|
|
|
51
56
|
const itemElement = document.createElement("li");
|
|
52
57
|
const itemClass = `${component.getClass("menu-item")}`;
|
|
53
58
|
itemElement.className = itemClass;
|
|
54
|
-
|
|
55
|
-
|
|
59
|
+
if (listbox) {
|
|
60
|
+
itemElement.setAttribute("role", "option");
|
|
61
|
+
itemElement.id = `${optionIdPrefix}-option-${index}`;
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
itemElement.setAttribute("role", "menuitem");
|
|
65
|
+
itemElement.setAttribute("tabindex", "-1");
|
|
66
|
+
}
|
|
56
67
|
itemElement.setAttribute("data-id", item.id);
|
|
57
68
|
itemElement.setAttribute("data-index", index.toString());
|
|
58
69
|
if (item.disabled) {
|
|
@@ -144,7 +155,9 @@ const withController = (config) => (component) => {
|
|
|
144
155
|
const renderMenuItems = () => {
|
|
145
156
|
const menuList = document.createElement("ul");
|
|
146
157
|
menuList.className = `${component.getClass("menu-list")}`;
|
|
147
|
-
menuList.setAttribute("role", "menu");
|
|
158
|
+
menuList.setAttribute("role", listbox ? "listbox" : "menu");
|
|
159
|
+
if (listbox)
|
|
160
|
+
menuList.id = `${optionIdPrefix}-listbox`;
|
|
148
161
|
let target = menuList;
|
|
149
162
|
const startGroup = () => {
|
|
150
163
|
const group = document.createElement("li");
|
|
@@ -256,31 +269,32 @@ const withController = (config) => (component) => {
|
|
|
256
269
|
component.element.style.opacity = "";
|
|
257
270
|
void component.element.getBoundingClientRect();
|
|
258
271
|
component.element.classList.add(`${component.getClass("menu--visible")}`);
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
component.keyboard.handleInitialFocus
|
|
262
|
-
|
|
263
|
-
else {
|
|
264
|
-
const items = Array.from(component.element.querySelectorAll(`.${component.getClass("menu-item")}`));
|
|
265
|
-
items.forEach((item) => {
|
|
266
|
-
item.tabIndex = -1;
|
|
267
|
-
});
|
|
268
|
-
if (items.length > 0)
|
|
269
|
-
items[0].tabIndex = 0;
|
|
270
|
-
if (interactionType === "keyboard" && items.length > 0) {
|
|
271
|
-
items[0].focus();
|
|
272
|
+
if (!listbox)
|
|
273
|
+
tasks.setTimeout(() => {
|
|
274
|
+
if (component.keyboard && component.keyboard.handleInitialFocus) {
|
|
275
|
+
component.keyboard.handleInitialFocus(component.element, interactionType);
|
|
272
276
|
}
|
|
273
277
|
else {
|
|
274
|
-
component.element.
|
|
275
|
-
|
|
278
|
+
const items = Array.from(component.element.querySelectorAll(`.${component.getClass("menu-item")}`));
|
|
279
|
+
items.forEach((item) => {
|
|
280
|
+
item.tabIndex = -1;
|
|
281
|
+
});
|
|
282
|
+
if (items.length > 0)
|
|
283
|
+
items[0].tabIndex = 0;
|
|
284
|
+
if (interactionType === "keyboard" && items.length > 0) {
|
|
285
|
+
items[0].focus();
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
component.element.tabIndex = -1;
|
|
289
|
+
component.element.focus();
|
|
290
|
+
}
|
|
276
291
|
}
|
|
277
|
-
}
|
|
278
|
-
}, 100);
|
|
292
|
+
}, 100);
|
|
279
293
|
tasks.setTimeout(() => {
|
|
280
294
|
if (config.closeOnClickOutside && state.visible) {
|
|
281
295
|
document.addEventListener("click", handleDocumentClick);
|
|
282
296
|
}
|
|
283
|
-
if (config.closeOnEscape) {
|
|
297
|
+
if (config.closeOnEscape && !listbox) {
|
|
284
298
|
document.addEventListener("keydown", handleDocumentKeydown);
|
|
285
299
|
}
|
|
286
300
|
window.addEventListener("resize", handleWindowResize, {
|
|
@@ -432,7 +446,7 @@ const withController = (config) => (component) => {
|
|
|
432
446
|
};
|
|
433
447
|
const initMenu = () => {
|
|
434
448
|
renderMenuItems();
|
|
435
|
-
if (component.keyboard && component.keyboard.setupKeyboardHandlers) {
|
|
449
|
+
if (!listbox && component.keyboard && component.keyboard.setupKeyboardHandlers) {
|
|
436
450
|
component.keyboard.setupKeyboardHandlers(component.element, state, {
|
|
437
451
|
closeMenu,
|
|
438
452
|
closeSubmenu: component.submenu ? component.submenu.closeSubmenu : null,
|
|
@@ -454,7 +468,7 @@ const withController = (config) => (component) => {
|
|
|
454
468
|
if (config.closeOnClickOutside) {
|
|
455
469
|
document.addEventListener("click", handleDocumentClick);
|
|
456
470
|
}
|
|
457
|
-
if (config.closeOnEscape) {
|
|
471
|
+
if (config.closeOnEscape && !listbox) {
|
|
458
472
|
document.addEventListener("keydown", handleDocumentKeydown);
|
|
459
473
|
}
|
|
460
474
|
window.addEventListener("resize", handleWindowResize);
|