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
|
@@ -82,6 +82,8 @@ const withOpener = (config) => (component) => {
|
|
|
82
82
|
openerElement.addEventListener("keydown", handleOpenerKeydown);
|
|
83
83
|
openerElement.addEventListener("blur", handleOpenerBlur);
|
|
84
84
|
}
|
|
85
|
+
if (config.listbox)
|
|
86
|
+
return;
|
|
85
87
|
openerElement.setAttribute("aria-haspopup", "true");
|
|
86
88
|
openerElement.setAttribute("aria-expanded", "false");
|
|
87
89
|
let menuId = component.element.id;
|
|
@@ -256,13 +258,13 @@ const withOpener = (config) => (component) => {
|
|
|
256
258
|
};
|
|
257
259
|
}
|
|
258
260
|
component.on("open", () => {
|
|
259
|
-
if (state.openerElement) {
|
|
261
|
+
if (state.openerElement && !config.listbox) {
|
|
260
262
|
state.openerElement.setAttribute("aria-expanded", "true");
|
|
261
263
|
setOpenerActive(true);
|
|
262
264
|
}
|
|
263
265
|
});
|
|
264
266
|
component.on("close", (event) => {
|
|
265
|
-
if (state.openerElement) {
|
|
267
|
+
if (state.openerElement && !config.listbox) {
|
|
266
268
|
state.openerElement.setAttribute("aria-expanded", "false");
|
|
267
269
|
setOpenerActive(false);
|
|
268
270
|
if (event.restoreFocus && !isTabNavigation) {
|
|
@@ -232,6 +232,15 @@ export interface MenuConfig {
|
|
|
232
232
|
* @default false
|
|
233
233
|
*/
|
|
234
234
|
manualOpen?: boolean;
|
|
235
|
+
/**
|
|
236
|
+
* Renders the menu as the listbox popup of a combobox (WAI-ARIA select-only
|
|
237
|
+
* combobox): the list is `role="listbox"` and each item `role="option"` with
|
|
238
|
+
* its own id, focus stays with the combobox, which owns the keyboard and
|
|
239
|
+
* points at the active option with `aria-activedescendant`, and the opener
|
|
240
|
+
* gets no ARIA from the menu. Pair it with `manualOpen`.
|
|
241
|
+
* @default false
|
|
242
|
+
*/
|
|
243
|
+
listbox?: boolean;
|
|
235
244
|
/**
|
|
236
245
|
* When true, renders a compact menu with smaller items and tighter spacing.
|
|
237
246
|
* Useful in dense UIs like toolbars and action bars where the standard
|
|
@@ -317,9 +326,11 @@ export interface MenuComponent {
|
|
|
317
326
|
/**
|
|
318
327
|
* Closes the menu
|
|
319
328
|
* @param event - Optional event that triggered the close
|
|
329
|
+
* @param restoreFocus - Whether focus returns to the opener (default true)
|
|
330
|
+
* @param skipAnimation - Whether to close without the exit animation
|
|
320
331
|
* @returns The menu component for chaining
|
|
321
332
|
*/
|
|
322
|
-
close: (event?: Event) => MenuComponent;
|
|
333
|
+
close: (event?: Event, restoreFocus?: boolean, skipAnimation?: boolean) => MenuComponent;
|
|
323
334
|
/**
|
|
324
335
|
* Toggles the menu's open state
|
|
325
336
|
* @param event - Optional event that triggered the toggle
|
|
@@ -21,11 +21,9 @@ interface ComponentWithRadio {
|
|
|
21
21
|
enableOption: (value: string) => void;
|
|
22
22
|
disableOption: (value: string) => void;
|
|
23
23
|
getClass: (name: string) => string;
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
emit?: (event: string, data: unknown) => void;
|
|
28
|
-
};
|
|
24
|
+
on: (event: string, handler: Function) => void;
|
|
25
|
+
off: (event: string, handler: Function) => void;
|
|
26
|
+
emit?: (event: string, data: unknown) => void;
|
|
29
27
|
}
|
|
30
28
|
/**
|
|
31
29
|
* Enhances a radios component with API methods
|
|
@@ -33,5 +31,5 @@ interface ComponentWithRadio {
|
|
|
33
31
|
* @returns {Function} Higher-order function that adds API methods to component
|
|
34
32
|
* @internal This is an internal utility for the Radios component
|
|
35
33
|
*/
|
|
36
|
-
export declare const withAPI: ({
|
|
34
|
+
export declare const withAPI: ({ lifecycle }: ApiOptions) => (component: ComponentWithRadio) => RadiosComponent;
|
|
37
35
|
export {};
|
|
@@ -1,24 +1,4 @@
|
|
|
1
|
-
export const withAPI = ({
|
|
2
|
-
if (!component.events) {
|
|
3
|
-
component.events = {
|
|
4
|
-
on: (event, handler) => {
|
|
5
|
-
component.element.addEventListener(event, ((e) => handler(e.detail)));
|
|
6
|
-
},
|
|
7
|
-
off: (event, handler) => {
|
|
8
|
-
component.element.removeEventListener(event, handler);
|
|
9
|
-
},
|
|
10
|
-
emit: (event, data) => {
|
|
11
|
-
const customEvent = new CustomEvent(event, { detail: data });
|
|
12
|
-
component.element.dispatchEvent(customEvent);
|
|
13
|
-
},
|
|
14
|
-
};
|
|
15
|
-
}
|
|
16
|
-
else if (!component.events.emit) {
|
|
17
|
-
component.events.emit = (event, data) => {
|
|
18
|
-
const customEvent = new CustomEvent(event, { detail: data });
|
|
19
|
-
component.element.dispatchEvent(customEvent);
|
|
20
|
-
};
|
|
21
|
-
}
|
|
1
|
+
export const withAPI = ({ lifecycle }) => (component) => {
|
|
22
2
|
const radiosComponent = {
|
|
23
3
|
element: component.element,
|
|
24
4
|
radios: component.radios,
|
|
@@ -41,11 +21,11 @@ export const withAPI = ({ disabled, lifecycle }) => (component) => {
|
|
|
41
21
|
return this;
|
|
42
22
|
},
|
|
43
23
|
enable() {
|
|
44
|
-
|
|
24
|
+
component.enable();
|
|
45
25
|
return this;
|
|
46
26
|
},
|
|
47
27
|
disable() {
|
|
48
|
-
|
|
28
|
+
component.disable();
|
|
49
29
|
return this;
|
|
50
30
|
},
|
|
51
31
|
enableOption(value) {
|
|
@@ -57,11 +37,11 @@ export const withAPI = ({ disabled, lifecycle }) => (component) => {
|
|
|
57
37
|
return this;
|
|
58
38
|
},
|
|
59
39
|
on(event, handler) {
|
|
60
|
-
component.
|
|
40
|
+
component.on(event, handler);
|
|
61
41
|
return this;
|
|
62
42
|
},
|
|
63
43
|
off(event, handler) {
|
|
64
|
-
component.
|
|
44
|
+
component.off(event, handler);
|
|
65
45
|
return this;
|
|
66
46
|
},
|
|
67
47
|
destroy() {
|
|
@@ -58,23 +58,11 @@ export const withRadio = (config) => (component) => {
|
|
|
58
58
|
radio.input.checked = false;
|
|
59
59
|
}
|
|
60
60
|
});
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
});
|
|
67
|
-
}
|
|
68
|
-
else if (typeof component.on === 'function') {
|
|
69
|
-
const changeEvent = new CustomEvent('change', {
|
|
70
|
-
detail: {
|
|
71
|
-
value: option.value,
|
|
72
|
-
originalEvent: e,
|
|
73
|
-
option
|
|
74
|
-
}
|
|
75
|
-
});
|
|
76
|
-
component.element.dispatchEvent(changeEvent);
|
|
77
|
-
}
|
|
61
|
+
component.emit?.('change', {
|
|
62
|
+
value: option.value,
|
|
63
|
+
originalEvent: e,
|
|
64
|
+
option
|
|
65
|
+
});
|
|
78
66
|
}
|
|
79
67
|
};
|
|
80
68
|
input.addEventListener('change', handleChange);
|
|
@@ -112,28 +100,15 @@ export const withRadio = (config) => (component) => {
|
|
|
112
100
|
if (config.options && Array.isArray(config.options)) {
|
|
113
101
|
config.options.forEach(option => addOption(option));
|
|
114
102
|
}
|
|
115
|
-
if (!component.events) {
|
|
116
|
-
component.events = {
|
|
117
|
-
on: (event, handler) => {
|
|
118
|
-
component.element.addEventListener(event, (e) => handler(e.detail));
|
|
119
|
-
},
|
|
120
|
-
off: (event, handler) => {
|
|
121
|
-
component.element.removeEventListener(event, handler);
|
|
122
|
-
},
|
|
123
|
-
emit: (event, data) => {
|
|
124
|
-
const customEvent = new CustomEvent(event, { detail: data });
|
|
125
|
-
component.element.dispatchEvent(customEvent);
|
|
126
|
-
}
|
|
127
|
-
};
|
|
128
|
-
}
|
|
129
103
|
return {
|
|
130
104
|
...component,
|
|
131
105
|
radios,
|
|
132
106
|
getValue: () => selectedValue,
|
|
133
107
|
setValue: (value) => {
|
|
134
|
-
|
|
108
|
+
const known = radios.some(radio => radio.config.value === value);
|
|
109
|
+
selectedValue = known ? value : '';
|
|
135
110
|
radios.forEach(radio => {
|
|
136
|
-
radio.input.checked = radio.config.value === value;
|
|
111
|
+
radio.input.checked = known && radio.config.value === value;
|
|
137
112
|
});
|
|
138
113
|
return component;
|
|
139
114
|
},
|
|
@@ -38,6 +38,7 @@ export const withStates = (config) => (component) => {
|
|
|
38
38
|
viewMode: currentViewMode,
|
|
39
39
|
});
|
|
40
40
|
}
|
|
41
|
+
config.onExpand?.();
|
|
41
42
|
};
|
|
42
43
|
const collapseToBar = () => {
|
|
43
44
|
if (currentState === SEARCH_STATES.BAR) {
|
|
@@ -67,6 +68,7 @@ export const withStates = (config) => (component) => {
|
|
|
67
68
|
viewMode: currentViewMode,
|
|
68
69
|
});
|
|
69
70
|
}
|
|
71
|
+
config.onCollapse?.();
|
|
70
72
|
};
|
|
71
73
|
const setViewMode = (mode) => {
|
|
72
74
|
if (mode === currentViewMode) {
|
|
@@ -207,7 +207,10 @@ export const withSuggestions = () => (component) => {
|
|
|
207
207
|
};
|
|
208
208
|
setTimeout(() => {
|
|
209
209
|
setupKeyboardNavigation();
|
|
210
|
+
if (getSuggestions().length > 0)
|
|
211
|
+
renderSuggestions();
|
|
210
212
|
}, 0);
|
|
213
|
+
component.on?.("expand", () => renderSuggestions());
|
|
211
214
|
return {
|
|
212
215
|
...component,
|
|
213
216
|
suggestions: {
|
|
@@ -20,6 +20,8 @@ export const withAPI = (options) => (component) => ({
|
|
|
20
20
|
return this;
|
|
21
21
|
},
|
|
22
22
|
open(interactionType = "mouse") {
|
|
23
|
+
if (component.textfield?.input?.disabled)
|
|
24
|
+
return this;
|
|
23
25
|
if (component.menu && typeof component.menu.open === "function") {
|
|
24
26
|
component.menu.open(undefined, interactionType);
|
|
25
27
|
}
|
|
@@ -75,6 +75,217 @@ const processMenuItems = (options) => {
|
|
|
75
75
|
return menuItem;
|
|
76
76
|
});
|
|
77
77
|
};
|
|
78
|
+
const setupCombobox = (component, menu, state, choose, prefix) => {
|
|
79
|
+
const input = component.textfield.input;
|
|
80
|
+
const field = component.textfield.element;
|
|
81
|
+
const activeClass = `${prefix}-menu-item--active`;
|
|
82
|
+
const TYPEAHEAD_DELAY = 500;
|
|
83
|
+
const PAGE = 10;
|
|
84
|
+
input.setAttribute("role", "combobox");
|
|
85
|
+
input.setAttribute("aria-haspopup", "listbox");
|
|
86
|
+
input.setAttribute("aria-expanded", "false");
|
|
87
|
+
let active = null;
|
|
88
|
+
let pending = "selected";
|
|
89
|
+
let buffer = "";
|
|
90
|
+
let bufferTimer = null;
|
|
91
|
+
const options = () => Array.from(menu.element.querySelectorAll('[role="option"]'));
|
|
92
|
+
const enabled = () => options().filter((option) => option.getAttribute("aria-disabled") !== "true");
|
|
93
|
+
const optionFor = (element) => element
|
|
94
|
+
? state.options.find((option) => String(option.id) === element.getAttribute("data-id"))
|
|
95
|
+
: undefined;
|
|
96
|
+
const setActive = (element) => {
|
|
97
|
+
active = element;
|
|
98
|
+
options().forEach((option) => option.classList.toggle(activeClass, option === element));
|
|
99
|
+
if (element) {
|
|
100
|
+
input.setAttribute("aria-activedescendant", element.id);
|
|
101
|
+
element.scrollIntoView?.({ block: "nearest" });
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
input.removeAttribute("aria-activedescendant");
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
const placeActive = () => {
|
|
108
|
+
const list = enabled();
|
|
109
|
+
if (!list.length)
|
|
110
|
+
return setActive(null);
|
|
111
|
+
if (pending === "first")
|
|
112
|
+
return setActive(list[0]);
|
|
113
|
+
if (pending === "last")
|
|
114
|
+
return setActive(list[list.length - 1]);
|
|
115
|
+
const selectedId = state.selectedOption ? String(state.selectedOption.id) : null;
|
|
116
|
+
setActive(list.find((option) => option.getAttribute("data-id") === selectedId) ?? list[0]);
|
|
117
|
+
};
|
|
118
|
+
const open = (start, event) => {
|
|
119
|
+
pending = start;
|
|
120
|
+
menu.open(event, "keyboard");
|
|
121
|
+
};
|
|
122
|
+
const close = (event) => {
|
|
123
|
+
menu.close(event, false);
|
|
124
|
+
};
|
|
125
|
+
const commit = (event) => {
|
|
126
|
+
const option = optionFor(active);
|
|
127
|
+
if (option)
|
|
128
|
+
choose(option, event);
|
|
129
|
+
close(event);
|
|
130
|
+
};
|
|
131
|
+
const move = (index) => {
|
|
132
|
+
const list = enabled();
|
|
133
|
+
if (!list.length)
|
|
134
|
+
return;
|
|
135
|
+
setActive(list[Math.max(0, Math.min(index, list.length - 1))]);
|
|
136
|
+
};
|
|
137
|
+
const typeahead = (key) => {
|
|
138
|
+
if (bufferTimer)
|
|
139
|
+
clearTimeout(bufferTimer);
|
|
140
|
+
bufferTimer = setTimeout(() => {
|
|
141
|
+
buffer = "";
|
|
142
|
+
bufferTimer = null;
|
|
143
|
+
}, TYPEAHEAD_DELAY);
|
|
144
|
+
buffer += key.toLowerCase();
|
|
145
|
+
const list = enabled();
|
|
146
|
+
const start = active ? list.indexOf(active) + (buffer.length === 1 ? 1 : 0) : 0;
|
|
147
|
+
const ordered = [...list.slice(start), ...list.slice(0, start)];
|
|
148
|
+
const match = ordered.find((option) => (option.textContent || "").trim().toLowerCase().startsWith(buffer));
|
|
149
|
+
if (match)
|
|
150
|
+
setActive(match);
|
|
151
|
+
return !!match;
|
|
152
|
+
};
|
|
153
|
+
menu.on("open", () => {
|
|
154
|
+
input.setAttribute("aria-expanded", "true");
|
|
155
|
+
const connect = () => {
|
|
156
|
+
const list = menu.element.querySelector('[role="listbox"]');
|
|
157
|
+
if (list?.id)
|
|
158
|
+
input.setAttribute("aria-controls", list.id);
|
|
159
|
+
placeActive();
|
|
160
|
+
};
|
|
161
|
+
if (options().length)
|
|
162
|
+
connect();
|
|
163
|
+
else
|
|
164
|
+
setTimeout(connect, 0);
|
|
165
|
+
});
|
|
166
|
+
menu.on("close", () => {
|
|
167
|
+
input.setAttribute("aria-expanded", "false");
|
|
168
|
+
input.removeAttribute("aria-controls");
|
|
169
|
+
setActive(null);
|
|
170
|
+
pending = "selected";
|
|
171
|
+
});
|
|
172
|
+
input.addEventListener("keydown", (e) => {
|
|
173
|
+
if (input.disabled)
|
|
174
|
+
return;
|
|
175
|
+
const isOpen = menu.isOpen();
|
|
176
|
+
const list = enabled();
|
|
177
|
+
const index = active ? list.indexOf(active) : -1;
|
|
178
|
+
if (e.altKey && e.key === "ArrowDown") {
|
|
179
|
+
e.preventDefault();
|
|
180
|
+
if (!isOpen)
|
|
181
|
+
open("selected", e);
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (e.altKey && e.key === "ArrowUp") {
|
|
185
|
+
e.preventDefault();
|
|
186
|
+
if (isOpen)
|
|
187
|
+
commit(e);
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
switch (e.key) {
|
|
191
|
+
case "ArrowDown":
|
|
192
|
+
e.preventDefault();
|
|
193
|
+
if (!isOpen)
|
|
194
|
+
open("selected", e);
|
|
195
|
+
else
|
|
196
|
+
move(index + 1);
|
|
197
|
+
return;
|
|
198
|
+
case "ArrowUp":
|
|
199
|
+
e.preventDefault();
|
|
200
|
+
if (!isOpen)
|
|
201
|
+
open("selected", e);
|
|
202
|
+
else
|
|
203
|
+
move(index - 1);
|
|
204
|
+
return;
|
|
205
|
+
case "Home":
|
|
206
|
+
e.preventDefault();
|
|
207
|
+
if (!isOpen)
|
|
208
|
+
open("first", e);
|
|
209
|
+
else
|
|
210
|
+
move(0);
|
|
211
|
+
return;
|
|
212
|
+
case "End":
|
|
213
|
+
e.preventDefault();
|
|
214
|
+
if (!isOpen)
|
|
215
|
+
open("last", e);
|
|
216
|
+
else
|
|
217
|
+
move(list.length - 1);
|
|
218
|
+
return;
|
|
219
|
+
case "PageDown":
|
|
220
|
+
if (!isOpen)
|
|
221
|
+
return;
|
|
222
|
+
e.preventDefault();
|
|
223
|
+
move(index + PAGE);
|
|
224
|
+
return;
|
|
225
|
+
case "PageUp":
|
|
226
|
+
if (!isOpen)
|
|
227
|
+
return;
|
|
228
|
+
e.preventDefault();
|
|
229
|
+
move(index - PAGE);
|
|
230
|
+
return;
|
|
231
|
+
case "Enter":
|
|
232
|
+
e.preventDefault();
|
|
233
|
+
if (isOpen)
|
|
234
|
+
commit(e);
|
|
235
|
+
else
|
|
236
|
+
open("selected", e);
|
|
237
|
+
return;
|
|
238
|
+
case "Escape":
|
|
239
|
+
if (!isOpen)
|
|
240
|
+
return;
|
|
241
|
+
e.preventDefault();
|
|
242
|
+
close(e);
|
|
243
|
+
return;
|
|
244
|
+
case "Tab":
|
|
245
|
+
if (isOpen)
|
|
246
|
+
commit(e);
|
|
247
|
+
return;
|
|
248
|
+
default:
|
|
249
|
+
break;
|
|
250
|
+
}
|
|
251
|
+
if (e.key === " " && !buffer) {
|
|
252
|
+
e.preventDefault();
|
|
253
|
+
if (isOpen)
|
|
254
|
+
commit(e);
|
|
255
|
+
else
|
|
256
|
+
open("selected", e);
|
|
257
|
+
return;
|
|
258
|
+
}
|
|
259
|
+
if (e.key.length === 1 && !e.ctrlKey && !e.metaKey && !e.altKey) {
|
|
260
|
+
e.preventDefault();
|
|
261
|
+
if (!isOpen) {
|
|
262
|
+
open("selected", e);
|
|
263
|
+
setTimeout(() => typeahead(e.key), 0);
|
|
264
|
+
}
|
|
265
|
+
else {
|
|
266
|
+
typeahead(e.key);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
field.addEventListener("mousedown", (e) => {
|
|
271
|
+
if (input.disabled || menu.element.contains(e.target) || e.target === input)
|
|
272
|
+
return;
|
|
273
|
+
e.preventDefault();
|
|
274
|
+
input.focus();
|
|
275
|
+
});
|
|
276
|
+
field.addEventListener("click", (e) => {
|
|
277
|
+
if (input.disabled || menu.element.contains(e.target))
|
|
278
|
+
return;
|
|
279
|
+
if (menu.isOpen())
|
|
280
|
+
close(e);
|
|
281
|
+
else
|
|
282
|
+
open("selected", e);
|
|
283
|
+
});
|
|
284
|
+
input.addEventListener("blur", (e) => {
|
|
285
|
+
if (menu.isOpen() && !field.contains(e.relatedTarget))
|
|
286
|
+
close(e);
|
|
287
|
+
});
|
|
288
|
+
};
|
|
78
289
|
export const withMenu = (config) => (component) => {
|
|
79
290
|
if (!component.textfield) {
|
|
80
291
|
console.warn("Cannot add menu: textfield not found");
|
|
@@ -88,6 +299,7 @@ export const withMenu = (config) => (component) => {
|
|
|
88
299
|
state.selectedOption = state.options.find((opt) => opt.id === config.value);
|
|
89
300
|
}
|
|
90
301
|
const menuItems = processMenuItems(state.options);
|
|
302
|
+
const listbox = !state.options.some((option) => option && option.hasSubmenu);
|
|
91
303
|
const menu = createMenu({
|
|
92
304
|
opener: component.textfield,
|
|
93
305
|
items: menuItems,
|
|
@@ -100,56 +312,65 @@ export const withMenu = (config) => (component) => {
|
|
|
100
312
|
closeOnResize: true,
|
|
101
313
|
offset: 0,
|
|
102
314
|
container: component.element,
|
|
315
|
+
...(listbox ? { manualOpen: true, listbox: true, closeOnEscape: false } : {}),
|
|
103
316
|
...(config.menu ?? {}),
|
|
104
317
|
});
|
|
105
|
-
|
|
106
|
-
if (!event.item || event.item.hasSubmenu) {
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
109
|
-
const option = event.item.data;
|
|
110
|
-
if (!isSelectOption(option)) {
|
|
111
|
-
console.warn("Invalid menu selection: missing required data properties");
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
318
|
+
const choose = (option, originalEvent) => {
|
|
114
319
|
state.selectedOption = option;
|
|
115
320
|
component.textfield.setValue(option.text);
|
|
116
321
|
menu.setSelected(option.id);
|
|
117
322
|
if (component.emit) {
|
|
118
|
-
|
|
323
|
+
const changeEvent = {
|
|
119
324
|
select: component,
|
|
120
325
|
value: option.id,
|
|
121
326
|
text: option.text,
|
|
122
327
|
option,
|
|
123
|
-
originalEvent
|
|
328
|
+
originalEvent,
|
|
124
329
|
preventDefault: () => {
|
|
125
|
-
|
|
330
|
+
changeEvent.defaultPrevented = true;
|
|
126
331
|
},
|
|
127
332
|
defaultPrevented: false,
|
|
128
|
-
}
|
|
333
|
+
};
|
|
334
|
+
component.emit("change", changeEvent);
|
|
129
335
|
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (
|
|
336
|
+
};
|
|
337
|
+
menu.on("select", (event) => {
|
|
338
|
+
if (!event.item || event.item.hasSubmenu) {
|
|
133
339
|
return;
|
|
134
|
-
if ((e.key === "Enter" || e.key === " " || e.key === "ArrowDown") &&
|
|
135
|
-
!menu.isOpen()) {
|
|
136
|
-
e.preventDefault();
|
|
137
|
-
menu.open(e, "keyboard");
|
|
138
|
-
if (component.emit) {
|
|
139
|
-
component.emit("open", {
|
|
140
|
-
select: component,
|
|
141
|
-
originalEvent: e,
|
|
142
|
-
preventDefault: () => { },
|
|
143
|
-
defaultPrevented: false,
|
|
144
|
-
});
|
|
145
|
-
}
|
|
146
340
|
}
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
341
|
+
const option = event.item.data;
|
|
342
|
+
if (!isSelectOption(option)) {
|
|
343
|
+
console.warn("Invalid menu selection: missing required data properties");
|
|
344
|
+
return;
|
|
150
345
|
}
|
|
346
|
+
choose(option, event.originalEvent);
|
|
151
347
|
});
|
|
348
|
+
if (listbox) {
|
|
349
|
+
setupCombobox(component, menu, state, choose, config.prefix || "mtrl");
|
|
350
|
+
}
|
|
351
|
+
if (!listbox)
|
|
352
|
+
component.textfield.element.addEventListener("keydown", (e) => {
|
|
353
|
+
if (component.textfield.input.disabled)
|
|
354
|
+
return;
|
|
355
|
+
if ((e.key === "Enter" || e.key === " " || e.key === "ArrowDown") &&
|
|
356
|
+
!menu.isOpen()) {
|
|
357
|
+
e.preventDefault();
|
|
358
|
+
menu.open(e, "keyboard");
|
|
359
|
+
}
|
|
360
|
+
else if (e.key === "Escape" && menu.isOpen()) {
|
|
361
|
+
e.preventDefault();
|
|
362
|
+
menu.close(e);
|
|
363
|
+
}
|
|
364
|
+
});
|
|
152
365
|
menu.on("open", () => {
|
|
366
|
+
if (component.emit) {
|
|
367
|
+
component.emit("open", {
|
|
368
|
+
select: component,
|
|
369
|
+
originalEvent: null,
|
|
370
|
+
preventDefault: () => { },
|
|
371
|
+
defaultPrevented: false,
|
|
372
|
+
});
|
|
373
|
+
}
|
|
153
374
|
component.textfield.element.classList.add(`${config.prefix || "mtrl"}-select--open`);
|
|
154
375
|
const PREFIX = config.prefix || "mtrl";
|
|
155
376
|
component.textfield.element.classList.add(`${PREFIX}-textfield--focused`);
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SLIDER_EVENTS } from "../types.js";
|
|
2
2
|
import { SLIDER_MEASUREMENTS } from "../constants.js";
|
|
3
|
+
import { defaultConfig } from "../config.js";
|
|
3
4
|
import { createHandlers } from "./handlers.js";
|
|
4
5
|
export const withController = (config) => (component) => {
|
|
5
6
|
if (!component.element) {
|
|
@@ -105,8 +106,16 @@ export const withController = (config) => (component) => {
|
|
|
105
106
|
secondValueBubble.style.transform = "translateX(-50%)";
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
updateHandleAria(handle, state.value);
|
|
110
|
+
if (config.range && secondHandle && state.secondValue !== null) {
|
|
111
|
+
updateHandleAria(secondHandle, state.secondValue);
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
const updateHandleAria = (handleElement, value) => {
|
|
115
|
+
handleElement.setAttribute("aria-valuenow", String(value));
|
|
116
|
+
if (config.valueFormatter &&
|
|
117
|
+
config.valueFormatter !== defaultConfig.valueFormatter) {
|
|
118
|
+
handleElement.setAttribute("aria-valuetext", config.valueFormatter(value));
|
|
110
119
|
}
|
|
111
120
|
};
|
|
112
121
|
const updateValueBubbles = () => {
|
|
@@ -7,7 +7,7 @@ import { createBaseConfig, getElementConfig, getApiConfig } from "./config.js";
|
|
|
7
7
|
const createSwitch = (config = {}) => {
|
|
8
8
|
const baseConfig = createBaseConfig(config);
|
|
9
9
|
try {
|
|
10
|
-
const switchComponent = pipe(createBase, withEvents(), withElement(getElementConfig(baseConfig)),
|
|
10
|
+
const switchComponent = pipe(createBase, withEvents(), withElement(getElementConfig(baseConfig)), withInput(baseConfig), withTextLabel(baseConfig), withTrack(baseConfig), withSupportingText(baseConfig), withCheckable(baseConfig), withDisabled(baseConfig), withLifecycle(), (comp) => withAPI(getApiConfig(comp))(comp))(baseConfig);
|
|
11
11
|
return switchComponent;
|
|
12
12
|
}
|
|
13
13
|
catch (error) {
|
|
@@ -18,9 +18,11 @@ export const withAPI = ({ lifecycle }) => (component) => ({
|
|
|
18
18
|
if (tab.on && typeof tab.on === 'function') {
|
|
19
19
|
tab.on('click', (event) => component.handleTabClick(event, tab));
|
|
20
20
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
else {
|
|
22
|
+
tab.element.addEventListener('click', (event) => {
|
|
23
|
+
component.handleTabClick(event, tab);
|
|
24
|
+
});
|
|
25
|
+
}
|
|
24
26
|
return tab;
|
|
25
27
|
},
|
|
26
28
|
add(tab) {
|
|
@@ -30,9 +32,11 @@ export const withAPI = ({ lifecycle }) => (component) => ({
|
|
|
30
32
|
if (tab.on && typeof tab.on === 'function') {
|
|
31
33
|
tab.on('click', (event) => component.handleTabClick(event, tab));
|
|
32
34
|
}
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
35
|
+
else {
|
|
36
|
+
tab.element.addEventListener('click', (event) => {
|
|
37
|
+
component.handleTabClick(event, tab);
|
|
38
|
+
});
|
|
39
|
+
}
|
|
36
40
|
return this;
|
|
37
41
|
},
|
|
38
42
|
getTabs() {
|
|
@@ -47,6 +51,8 @@ export const withAPI = ({ lifecycle }) => (component) => ({
|
|
|
47
51
|
: tabOrValue;
|
|
48
52
|
if (!targetTab)
|
|
49
53
|
return this;
|
|
54
|
+
if (targetTab.element.disabled)
|
|
55
|
+
return this;
|
|
50
56
|
component.tabs.forEach(tab => tab.deactivate());
|
|
51
57
|
targetTab.activate();
|
|
52
58
|
if (component.emit) {
|
|
@@ -27,9 +27,7 @@ export const withTabsManagement = (config) => (component) => {
|
|
|
27
27
|
if (isCancelable(event)) {
|
|
28
28
|
event.preventDefault();
|
|
29
29
|
}
|
|
30
|
-
if (tab.disabled
|
|
31
|
-
tab.disabled.isDisabled &&
|
|
32
|
-
tab.disabled.isDisabled()) {
|
|
30
|
+
if (tab.element.disabled) {
|
|
33
31
|
return;
|
|
34
32
|
}
|
|
35
33
|
tabs.forEach((t) => t.deactivate());
|
|
@@ -50,7 +48,9 @@ export const withTabsManagement = (config) => (component) => {
|
|
|
50
48
|
if (tab.on && typeof tab.on === "function") {
|
|
51
49
|
tab.on("click", (event) => handleTabClick(event, tab));
|
|
52
50
|
}
|
|
53
|
-
|
|
51
|
+
else {
|
|
52
|
+
tab.element.addEventListener("click", (event) => handleTabClick(event, tab));
|
|
53
|
+
}
|
|
54
54
|
});
|
|
55
55
|
return {
|
|
56
56
|
...component,
|
|
@@ -108,9 +108,7 @@ export const withIndicator = (config) => (component) => {
|
|
|
108
108
|
scrollContainer.appendChild(indicator.element);
|
|
109
109
|
const originalHandleTabClick = component.handleTabClick;
|
|
110
110
|
component.handleTabClick = function (event, tab) {
|
|
111
|
-
if (tab.disabled
|
|
112
|
-
tab.disabled.isDisabled &&
|
|
113
|
-
tab.disabled.isDisabled()) {
|
|
111
|
+
if (tab.element.disabled) {
|
|
114
112
|
return;
|
|
115
113
|
}
|
|
116
114
|
originalHandleTabClick.call(this, event, tab);
|