mtrl 0.9.5 → 0.9.8
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 +20 -0
- package/dist/README.md +20 -0
- package/dist/components/bottom-app-bar/bottom-app-bar.js +2 -2
- package/dist/components/bottom-sheet/features/structure.js +4 -3
- package/dist/components/button/api.js +3 -2
- package/dist/components/button/features/progress.js +26 -21
- package/dist/components/button-group/config.d.ts +11 -3
- package/dist/components/button-group/config.js +1 -1
- package/dist/components/card/content.js +5 -4
- package/dist/components/carousel/features/slides.js +2 -1
- package/dist/components/checkbox/checkbox.js +22 -0
- package/dist/components/checkbox/config.d.ts +3 -3
- package/dist/components/checkbox/config.js +10 -9
- package/dist/components/checkbox/types.d.ts +19 -4
- package/dist/components/chips/api.js +3 -1
- package/dist/components/chips/chip/api.js +3 -2
- package/dist/components/chips/chip/chip.js +6 -4
- package/dist/components/chips/config.js +1 -1
- package/dist/components/datepicker/api.d.ts +8 -2
- package/dist/components/datepicker/api.js +1 -0
- package/dist/components/datepicker/config.d.ts +14 -4
- package/dist/components/datepicker/datepicker.js +5 -2
- package/dist/components/datepicker/types.d.ts +14 -0
- package/dist/components/datepicker/utils.js +4 -6
- package/dist/components/dialog/config.js +0 -1
- package/dist/components/dialog/features.js +10 -8
- package/dist/components/dialog/types.d.ts +3 -2
- package/dist/components/drawer/features/items.js +3 -2
- package/dist/components/list/features/renderer.js +4 -3
- package/dist/components/menu/api.d.ts +1 -1
- package/dist/components/menu/config.js +2 -2
- package/dist/components/menu/features/controller.js +41 -26
- package/dist/components/menu/features/keyboard.js +7 -5
- package/dist/components/menu/features/opener.js +19 -12
- package/dist/components/menu/features/position.js +1 -1
- package/dist/components/menu/features/submenu.js +7 -1
- package/dist/components/menu/types.d.ts +13 -2
- package/dist/components/navigation/nav-item.js +4 -3
- package/dist/components/navigation/system/mobile.js +3 -2
- package/dist/components/navigation-rail/navigation-rail.js +3 -2
- package/dist/components/progress/features/canvas.js +0 -2
- package/dist/components/search/features/states.js +3 -3
- package/dist/components/search/features/structure.js +3 -0
- package/dist/components/search/features/suggestions.js +2 -2
- package/dist/components/search/types.d.ts +6 -0
- package/dist/components/segmented-button/config.d.ts +4 -1
- package/dist/components/segmented-button/config.js +1 -1
- package/dist/components/segmented-button/segment.d.ts +2 -5
- package/dist/components/segmented-button/segment.js +7 -3
- package/dist/components/select/features.js +267 -44
- package/dist/components/side-sheet/features/structure.js +5 -4
- package/dist/components/slider/features/controller.js +7 -0
- package/dist/components/slider/features/dom.d.ts +1 -0
- package/dist/components/slider/features/dom.js +11 -0
- package/dist/components/slider/features/handlers.js +8 -3
- package/dist/components/slider/features/states.js +8 -0
- package/dist/components/slider/types.d.ts +7 -0
- package/dist/components/switch/config.d.ts +3 -3
- package/dist/components/switch/config.js +7 -7
- package/dist/components/switch/features.js +5 -5
- package/dist/components/switch/switch.js +1 -1
- package/dist/components/switch/types.d.ts +10 -0
- package/dist/components/tabs/features.d.ts +1 -0
- package/dist/components/tabs/scroll-indicators.js +3 -2
- package/dist/components/tabs/tab.d.ts +0 -5
- package/dist/components/tabs/tab.js +12 -3
- package/dist/components/tabs/tabs.js +10 -1
- package/dist/components/tabs/utils.d.ts +8 -0
- package/dist/components/tabs/utils.js +63 -18
- package/dist/components/textfield/api.js +12 -4
- package/dist/components/textfield/features/error.d.ts +1 -0
- package/dist/components/textfield/features/error.js +10 -0
- package/dist/components/textfield/features/leading-icon.js +32 -33
- package/dist/components/textfield/features/prefix-text.js +30 -18
- package/dist/components/textfield/features/suffix-text.js +30 -14
- package/dist/components/textfield/features/supporting-text.d.ts +1 -0
- package/dist/components/textfield/features/supporting-text.js +25 -0
- package/dist/components/textfield/features/trailing-icon.js +36 -19
- package/dist/components/timepicker/api.d.ts +1 -1
- package/dist/components/timepicker/api.js +16 -11
- package/dist/components/timepicker/render.js +27 -24
- package/dist/components/timepicker/timepicker.js +4 -2
- package/dist/components/timepicker/types.d.ts +7 -0
- package/dist/components/timepicker/utils.d.ts +14 -0
- package/dist/components/timepicker/utils.js +4 -0
- package/dist/components/tooltip/api.js +27 -2
- package/dist/components/top-app-bar/top-app-bar.js +3 -2
- package/dist/core/compose/component.d.ts +1 -1
- package/dist/core/compose/component.js +1 -1
- package/dist/core/compose/features/icon.js +3 -2
- package/dist/core/compose/features/input.js +1 -1
- package/dist/core/compose/features/lifecycle.js +10 -11
- package/dist/core/compose/features/textlabel.js +3 -0
- package/dist/core/compose/features/track.js +2 -1
- package/dist/core/config/component.d.ts +1 -1
- package/dist/core/config/component.js +1 -1
- package/dist/core/dom/create.js +5 -4
- package/dist/core/dom/form-value.d.ts +24 -0
- package/dist/core/dom/form-value.js +14 -0
- package/dist/core/dom/html.d.ts +34 -0
- package/dist/core/dom/html.js +13 -0
- package/dist/core/dom/index.d.ts +2 -0
- package/dist/core/dom/index.js +1 -0
- package/dist/core/dom/utils.js +1 -1
- package/dist/core/state/emitter.js +8 -1
- package/dist/core/state/index.d.ts +1 -1
- package/dist/core/state/store.d.ts +10 -2
- package/dist/core/state/store.js +2 -1
- package/dist/index.cjs +0 -0
- package/dist/package.json +5 -2
- 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/styles.d.ts +1 -0
- 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 +7 -3
- package/src/styles/abstract/_mixins.scss +26 -0
- package/src/styles/abstract/_variables.scss +25 -11
- package/src/styles/base/_reset.scss +9 -2
- package/src/styles/components/_bottom-sheet.scss +23 -7
- package/src/styles/components/_button.scss +1 -12
- 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
|
@@ -6,6 +6,7 @@ import { createEmitter } from "../../core/state/emitter.js";
|
|
|
6
6
|
import { mountRailRipple } from "./ripple.js";
|
|
7
7
|
import { createBaseConfig } from "./config.js";
|
|
8
8
|
import { safeUrl } from "../../core/utils/url.js";
|
|
9
|
+
import { setHTML } from "../../core/dom/html.js";
|
|
9
10
|
const copyItems = (items) => {
|
|
10
11
|
const ids = new Set();
|
|
11
12
|
let selected = false;
|
|
@@ -94,7 +95,7 @@ export default function createNavigationRail(config = {}) {
|
|
|
94
95
|
else
|
|
95
96
|
element.removeAttribute('aria-current');
|
|
96
97
|
const icon = element.querySelector(`.${cls('__icon')}`);
|
|
97
|
-
icon
|
|
98
|
+
setHTML(icon, item.active && item.activeIcon ? item.activeIcon : item.icon);
|
|
98
99
|
}
|
|
99
100
|
};
|
|
100
101
|
const render = () => {
|
|
@@ -163,7 +164,7 @@ export default function createNavigationRail(config = {}) {
|
|
|
163
164
|
toggle?.setAttribute('aria-expanded', String(expanded));
|
|
164
165
|
toggle?.setAttribute('aria-label', (expanded ? options.collapseLabel : options.expandLabel));
|
|
165
166
|
if (toggle)
|
|
166
|
-
toggle
|
|
167
|
+
setHTML(toggle, (expanded ? options.collapseIcon : options.expandIcon));
|
|
167
168
|
if (dialog) {
|
|
168
169
|
if (expanded)
|
|
169
170
|
showModal();
|
|
@@ -78,7 +78,6 @@ export const withCanvas = (config) => (component) => {
|
|
|
78
78
|
return false;
|
|
79
79
|
context = { canvas, ctx, width: 0, height: 0, pixelRatio: view?.devicePixelRatio || 1 };
|
|
80
80
|
measure();
|
|
81
|
-
component.ctx = ctx;
|
|
82
81
|
return true;
|
|
83
82
|
};
|
|
84
83
|
const currentAmplitude = () => {
|
|
@@ -293,7 +292,6 @@ export const withCanvas = (config) => (component) => {
|
|
|
293
292
|
return {
|
|
294
293
|
...component,
|
|
295
294
|
canvas,
|
|
296
|
-
ctx: context?.ctx,
|
|
297
295
|
draw,
|
|
298
296
|
resize,
|
|
299
297
|
};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SEARCH_STATES, SEARCH_VIEW_MODES, SEARCH_CLASSES, SEARCH_ICONS, } from "../constants.js";
|
|
2
|
+
import { setHTML } from "../../../core/dom/html.js";
|
|
2
3
|
export const withStates = (config) => (component) => {
|
|
3
4
|
let currentState = config.initialState || SEARCH_STATES.BAR;
|
|
4
5
|
let currentViewMode = config.viewMode || SEARCH_VIEW_MODES.DOCKED;
|
|
@@ -16,7 +17,7 @@ export const withStates = (config) => (component) => {
|
|
|
16
17
|
element.classList.remove(getClass(SEARCH_CLASSES.STATE_BAR));
|
|
17
18
|
element.classList.add(getClass(SEARCH_CLASSES.STATE_VIEW));
|
|
18
19
|
if (structure?.leadingIcon) {
|
|
19
|
-
structure.leadingIcon
|
|
20
|
+
setHTML(structure.leadingIcon, SEARCH_ICONS.BACK);
|
|
20
21
|
structure.leadingIcon.setAttribute("aria-label", "Go back");
|
|
21
22
|
}
|
|
22
23
|
if (structure?.divider && !element.contains(structure.divider)) {
|
|
@@ -50,8 +51,7 @@ export const withStates = (config) => (component) => {
|
|
|
50
51
|
element.classList.remove(getClass(SEARCH_CLASSES.STATE_VIEW));
|
|
51
52
|
element.classList.add(getClass(SEARCH_CLASSES.STATE_BAR));
|
|
52
53
|
if (structure?.leadingIcon) {
|
|
53
|
-
structure.leadingIcon.
|
|
54
|
-
config.leadingIcon || SEARCH_ICONS.SEARCH;
|
|
54
|
+
setHTML(structure.leadingIcon, config.leadingIcon || SEARCH_ICONS.SEARCH);
|
|
55
55
|
structure.leadingIcon.setAttribute("aria-label", "Search");
|
|
56
56
|
}
|
|
57
57
|
if (structure?.divider && element.contains(structure.divider)) {
|
|
@@ -83,7 +83,7 @@ export const withSuggestions = () => (component) => {
|
|
|
83
83
|
return;
|
|
84
84
|
const suggestions = getSuggestions();
|
|
85
85
|
const query = component.input?.getValue?.() || "";
|
|
86
|
-
suggestionsList.
|
|
86
|
+
suggestionsList.replaceChildren();
|
|
87
87
|
if (suggestions.length === 0) {
|
|
88
88
|
return;
|
|
89
89
|
}
|
|
@@ -112,7 +112,7 @@ export const withSuggestions = () => (component) => {
|
|
|
112
112
|
const clearRenderedSuggestions = () => {
|
|
113
113
|
const suggestionsList = component.structure?.suggestionsList;
|
|
114
114
|
if (suggestionsList) {
|
|
115
|
-
suggestionsList.
|
|
115
|
+
suggestionsList.replaceChildren();
|
|
116
116
|
}
|
|
117
117
|
highlightedIndex = -1;
|
|
118
118
|
};
|
|
@@ -74,6 +74,12 @@ export interface SearchConfig {
|
|
|
74
74
|
placeholder?: string;
|
|
75
75
|
/** Initial input value */
|
|
76
76
|
value?: string;
|
|
77
|
+
/**
|
|
78
|
+
* Form field name. Set on the text input itself, so the search takes part
|
|
79
|
+
* in a surrounding form and its value appears in `FormData`. Without it the
|
|
80
|
+
* search submits nothing.
|
|
81
|
+
*/
|
|
82
|
+
name?: string;
|
|
77
83
|
/** Custom leading icon HTML (replaces default search icon) */
|
|
78
84
|
leadingIcon?: string;
|
|
79
85
|
/** Trailing content items (icons, avatar) */
|
|
@@ -10,7 +10,10 @@ export declare const DEFAULT_CONFIG: SegmentedButtonConfig;
|
|
|
10
10
|
* @returns {SegmentedButtonConfig} Complete configuration with defaults applied
|
|
11
11
|
* @internal
|
|
12
12
|
*/
|
|
13
|
-
export declare const createBaseConfig: (config?: SegmentedButtonConfig) => SegmentedButtonConfig
|
|
13
|
+
export declare const createBaseConfig: (config?: SegmentedButtonConfig) => SegmentedButtonConfig & {
|
|
14
|
+
componentName: string;
|
|
15
|
+
prefix: string;
|
|
16
|
+
};
|
|
14
17
|
/**
|
|
15
18
|
* Generates element configuration for the Segmented Button container
|
|
16
19
|
* @param {SegmentedButtonConfig} config - Segmented Button configuration
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SegmentConfig, Segment } from "./types.js";
|
|
1
|
+
import { SegmentConfig, Segment, SegmentedButtonConfig } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Creates a segment for the segmented button using the button component
|
|
4
4
|
*
|
|
@@ -16,7 +16,4 @@ import { SegmentConfig, Segment } from "./types.js";
|
|
|
16
16
|
* @returns {Segment} Segment object
|
|
17
17
|
* @internal
|
|
18
18
|
*/
|
|
19
|
-
export declare const createSegment: (config: SegmentConfig, container: HTMLElement, prefix: string, groupDisabled?: boolean, options?:
|
|
20
|
-
ripple: boolean;
|
|
21
|
-
rippleConfig: {};
|
|
22
|
-
}) => Segment;
|
|
19
|
+
export declare const createSegment: (config: SegmentConfig, container: HTMLElement, prefix: string, groupDisabled?: boolean, options?: Pick<SegmentedButtonConfig, "ripple" | "rippleConfig">) => Segment;
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import createButton from "../button/index.js";
|
|
2
2
|
import { DEFAULT_CHECKMARK_ICON } from "./constants.js";
|
|
3
|
+
import { setHTML } from "../../core/dom/html.js";
|
|
3
4
|
const createCheckmarkElement = (prefix, icon) => {
|
|
4
5
|
const checkmark = document.createElement("span");
|
|
5
6
|
checkmark.className = `${prefix}-segment-checkmark`;
|
|
6
|
-
checkmark
|
|
7
|
+
setHTML(checkmark, icon || DEFAULT_CHECKMARK_ICON);
|
|
7
8
|
return checkmark;
|
|
8
9
|
};
|
|
9
|
-
export const createSegment = (config, container, prefix, groupDisabled = false, options = {
|
|
10
|
+
export const createSegment = (config, container, prefix, groupDisabled = false, options = {
|
|
11
|
+
ripple: true,
|
|
12
|
+
rippleConfig: {},
|
|
13
|
+
}) => {
|
|
10
14
|
const isDisabled = groupDisabled || config.disabled;
|
|
11
15
|
const originalIcon = config.icon;
|
|
12
16
|
const hasText = Boolean(config.text);
|
|
@@ -53,7 +57,7 @@ export const createSegment = (config, container, prefix, groupDisabled = false,
|
|
|
53
57
|
button.element.setAttribute("aria-pressed", selected ? "true" : "false");
|
|
54
58
|
if (isIconOnly) {
|
|
55
59
|
}
|
|
56
|
-
else if (
|
|
60
|
+
else if (originalIcon && hasText) {
|
|
57
61
|
if (selected) {
|
|
58
62
|
button.setIcon(checkmarkIcon);
|
|
59
63
|
}
|
|
@@ -75,19 +75,232 @@ const processMenuItems = (options) => {
|
|
|
75
75
|
return menuItem;
|
|
76
76
|
});
|
|
77
77
|
};
|
|
78
|
+
const setupCombobox = (component, textfield, menu, state, choose, prefix) => {
|
|
79
|
+
const input = textfield.input;
|
|
80
|
+
const field = 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");
|
|
81
292
|
return component;
|
|
82
293
|
}
|
|
294
|
+
const textfield = component.textfield;
|
|
83
295
|
const state = {
|
|
84
296
|
options: config.options || [],
|
|
85
297
|
selectedOption: null,
|
|
86
298
|
};
|
|
87
299
|
if (config.value) {
|
|
88
|
-
state.selectedOption = state.options.find((opt) => opt.id === config.value);
|
|
300
|
+
state.selectedOption = state.options.find((opt) => opt.id === config.value) ?? null;
|
|
89
301
|
}
|
|
90
302
|
const menuItems = processMenuItems(state.options);
|
|
303
|
+
const listbox = !state.options.some((option) => option && option.hasSubmenu);
|
|
91
304
|
const menu = createMenu({
|
|
92
305
|
opener: component.textfield,
|
|
93
306
|
items: menuItems,
|
|
@@ -100,47 +313,56 @@ export const withMenu = (config) => (component) => {
|
|
|
100
313
|
closeOnResize: true,
|
|
101
314
|
offset: 0,
|
|
102
315
|
container: component.element,
|
|
316
|
+
...(listbox ? { manualOpen: true, listbox: true, closeOnEscape: false } : {}),
|
|
103
317
|
...(config.menu ?? {}),
|
|
104
318
|
});
|
|
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
|
-
}
|
|
319
|
+
const choose = (option, originalEvent) => {
|
|
114
320
|
state.selectedOption = option;
|
|
115
|
-
|
|
321
|
+
textfield.setValue(option.text);
|
|
116
322
|
menu.setSelected(option.id);
|
|
117
323
|
if (component.emit) {
|
|
118
|
-
|
|
324
|
+
const changeEvent = {
|
|
119
325
|
select: component,
|
|
120
326
|
value: option.id,
|
|
121
327
|
text: option.text,
|
|
122
328
|
option,
|
|
123
|
-
originalEvent
|
|
329
|
+
originalEvent,
|
|
124
330
|
preventDefault: () => {
|
|
125
|
-
|
|
331
|
+
changeEvent.defaultPrevented = true;
|
|
126
332
|
},
|
|
127
333
|
defaultPrevented: false,
|
|
128
|
-
}
|
|
334
|
+
};
|
|
335
|
+
component.emit("change", changeEvent);
|
|
129
336
|
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
if (
|
|
337
|
+
};
|
|
338
|
+
menu.on("select", (event) => {
|
|
339
|
+
if (!event.item || event.item.hasSubmenu) {
|
|
133
340
|
return;
|
|
134
|
-
if ((e.key === "Enter" || e.key === " " || e.key === "ArrowDown") &&
|
|
135
|
-
!menu.isOpen()) {
|
|
136
|
-
e.preventDefault();
|
|
137
|
-
menu.open(e, "keyboard");
|
|
138
341
|
}
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
342
|
+
const option = event.item.data;
|
|
343
|
+
if (!isSelectOption(option)) {
|
|
344
|
+
console.warn("Invalid menu selection: missing required data properties");
|
|
345
|
+
return;
|
|
142
346
|
}
|
|
347
|
+
choose(option, event.originalEvent);
|
|
143
348
|
});
|
|
349
|
+
if (listbox) {
|
|
350
|
+
setupCombobox(component, textfield, menu, state, choose, config.prefix || "mtrl");
|
|
351
|
+
}
|
|
352
|
+
if (!listbox)
|
|
353
|
+
textfield.element.addEventListener("keydown", (e) => {
|
|
354
|
+
if (textfield.input.disabled)
|
|
355
|
+
return;
|
|
356
|
+
if ((e.key === "Enter" || e.key === " " || e.key === "ArrowDown") &&
|
|
357
|
+
!menu.isOpen()) {
|
|
358
|
+
e.preventDefault();
|
|
359
|
+
menu.open(e, "keyboard");
|
|
360
|
+
}
|
|
361
|
+
else if (e.key === "Escape" && menu.isOpen()) {
|
|
362
|
+
e.preventDefault();
|
|
363
|
+
menu.close(e);
|
|
364
|
+
}
|
|
365
|
+
});
|
|
144
366
|
menu.on("open", () => {
|
|
145
367
|
if (component.emit) {
|
|
146
368
|
component.emit("open", {
|
|
@@ -150,29 +372,29 @@ export const withMenu = (config) => (component) => {
|
|
|
150
372
|
defaultPrevented: false,
|
|
151
373
|
});
|
|
152
374
|
}
|
|
153
|
-
|
|
375
|
+
textfield.element.classList.add(`${config.prefix || "mtrl"}-select--open`);
|
|
154
376
|
const PREFIX = config.prefix || "mtrl";
|
|
155
|
-
|
|
156
|
-
if (
|
|
157
|
-
|
|
377
|
+
textfield.element.classList.add(`${PREFIX}-textfield--focused`);
|
|
378
|
+
if (textfield.element.classList.contains(`${PREFIX}-textfield--filled`)) {
|
|
379
|
+
textfield.element.classList.add(`${PREFIX}-textfield--filled-focused`);
|
|
158
380
|
}
|
|
159
381
|
});
|
|
160
382
|
menu.on("close", (event) => {
|
|
161
|
-
|
|
383
|
+
textfield.element.classList.remove(`${config.prefix || "mtrl"}-select--open`);
|
|
162
384
|
setTimeout(() => {
|
|
163
385
|
const PREFIX = config.prefix || "mtrl";
|
|
164
|
-
const isFocused = document.activeElement ===
|
|
165
|
-
|
|
386
|
+
const isFocused = document.activeElement === textfield.input ||
|
|
387
|
+
textfield.element.contains(document.activeElement);
|
|
166
388
|
if (isFocused) {
|
|
167
|
-
|
|
168
|
-
if (
|
|
169
|
-
|
|
389
|
+
textfield.element.classList.add(`${PREFIX}-textfield--focused`);
|
|
390
|
+
if (textfield.element.classList.contains(`${PREFIX}-textfield--filled`)) {
|
|
391
|
+
textfield.element.classList.add(`${PREFIX}-textfield--filled-focused`);
|
|
170
392
|
}
|
|
171
393
|
}
|
|
172
394
|
else {
|
|
173
|
-
|
|
174
|
-
if (
|
|
175
|
-
|
|
395
|
+
textfield.element.classList.remove(`${PREFIX}-textfield--focused`);
|
|
396
|
+
if (textfield.element.classList.contains(`${PREFIX}-textfield--filled`)) {
|
|
397
|
+
textfield.element.classList.remove(`${PREFIX}-textfield--filled-focused`);
|
|
176
398
|
}
|
|
177
399
|
}
|
|
178
400
|
}, 10);
|
|
@@ -201,21 +423,21 @@ export const withMenu = (config) => (component) => {
|
|
|
201
423
|
setValue: (value) => {
|
|
202
424
|
if (value === null || value === undefined || value === "") {
|
|
203
425
|
state.selectedOption = null;
|
|
204
|
-
|
|
426
|
+
textfield.setValue("");
|
|
205
427
|
menu.setSelected(null);
|
|
206
428
|
return component;
|
|
207
429
|
}
|
|
208
430
|
const option = state.options.find((opt) => "id" in opt && opt.id === value);
|
|
209
431
|
if (option && "text" in option) {
|
|
210
432
|
state.selectedOption = option;
|
|
211
|
-
|
|
433
|
+
textfield.setValue(option.text);
|
|
212
434
|
menu.setSelected(option.id);
|
|
213
435
|
}
|
|
214
436
|
return component;
|
|
215
437
|
},
|
|
216
438
|
clear: () => {
|
|
217
439
|
state.selectedOption = null;
|
|
218
|
-
|
|
440
|
+
textfield.setValue("");
|
|
219
441
|
menu.setSelected(null);
|
|
220
442
|
return component;
|
|
221
443
|
},
|
|
@@ -226,10 +448,11 @@ export const withMenu = (config) => (component) => {
|
|
|
226
448
|
state.options = options;
|
|
227
449
|
const menuItems = processMenuItems(options);
|
|
228
450
|
menu.setItems(menuItems);
|
|
229
|
-
|
|
230
|
-
|
|
451
|
+
const selected = state.selectedOption;
|
|
452
|
+
if (selected &&
|
|
453
|
+
!options.find((opt) => "id" in opt && opt.id === selected.id)) {
|
|
231
454
|
state.selectedOption = null;
|
|
232
|
-
|
|
455
|
+
textfield.setValue("");
|
|
233
456
|
}
|
|
234
457
|
return component;
|
|
235
458
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { SIDE_SHEET_CLASSES, SIDE_SHEET_VARIANTS } from "../constants.js";
|
|
2
|
+
import { setHTML } from "../../../core/dom/html.js";
|
|
2
3
|
const CLOSE_ICON = `<svg viewBox="0 -960 960 960" width="24" height="24" fill="currentColor" aria-hidden="true"><path d="m256-200-56-56 224-224-224-224 56-56 224 224 224-224 56 56-224 224 224 224-56 56-224-224-224 224Z"/></svg>`;
|
|
3
4
|
export const withStructure = (config) => (component) => {
|
|
4
5
|
const { element, getClass } = component;
|
|
@@ -42,7 +43,7 @@ export const withStructure = (config) => (component) => {
|
|
|
42
43
|
closeButton = document.createElement("button");
|
|
43
44
|
closeButton.type = "button";
|
|
44
45
|
closeButton.className = getClass(SIDE_SHEET_CLASSES.CLOSE);
|
|
45
|
-
closeButton
|
|
46
|
+
setHTML(closeButton, CLOSE_ICON);
|
|
46
47
|
closeButton.setAttribute("aria-label", "Close");
|
|
47
48
|
header.appendChild(closeButton);
|
|
48
49
|
}
|
|
@@ -53,7 +54,7 @@ export const withStructure = (config) => (component) => {
|
|
|
53
54
|
if (config.content instanceof HTMLElement)
|
|
54
55
|
content.appendChild(config.content);
|
|
55
56
|
else if (typeof config.content === "string")
|
|
56
|
-
content
|
|
57
|
+
setHTML(content, config.content);
|
|
57
58
|
container.appendChild(content);
|
|
58
59
|
element.appendChild(container);
|
|
59
60
|
(config.container || document.body).appendChild(element);
|
|
@@ -66,11 +67,11 @@ export const withStructure = (config) => (component) => {
|
|
|
66
67
|
closeButton,
|
|
67
68
|
content,
|
|
68
69
|
setContent(next) {
|
|
69
|
-
content.
|
|
70
|
+
content.replaceChildren();
|
|
70
71
|
if (next instanceof HTMLElement)
|
|
71
72
|
content.appendChild(next);
|
|
72
73
|
else
|
|
73
|
-
content
|
|
74
|
+
setHTML(content, next);
|
|
74
75
|
},
|
|
75
76
|
setTitle(next) {
|
|
76
77
|
if (title) {
|
|
@@ -2,6 +2,7 @@ import { SLIDER_EVENTS } from "../types.js";
|
|
|
2
2
|
import { SLIDER_MEASUREMENTS } from "../constants.js";
|
|
3
3
|
import { defaultConfig } from "../config.js";
|
|
4
4
|
import { createHandlers } from "./handlers.js";
|
|
5
|
+
import { setFormValue } from "../../../core/dom/form-value.js";
|
|
5
6
|
export const withController = (config) => (component) => {
|
|
6
7
|
if (!component.element) {
|
|
7
8
|
console.warn("Cannot initialize slider controller: missing element");
|
|
@@ -143,6 +144,12 @@ export const withController = (config) => (component) => {
|
|
|
143
144
|
try {
|
|
144
145
|
updateHandlePositions();
|
|
145
146
|
updateValueBubbles();
|
|
147
|
+
if (component.formFields) {
|
|
148
|
+
setFormValue(component.formFields[0], String(state.value));
|
|
149
|
+
if (state.secondValue !== null) {
|
|
150
|
+
setFormValue(component.formFields[1], String(state.secondValue));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
146
153
|
if (component.renderTracks) {
|
|
147
154
|
component.renderTracks(state);
|
|
148
155
|
}
|
|
@@ -13,6 +13,7 @@ export declare const withDom: (config: SliderConfig) => <T extends ElementCompon
|
|
|
13
13
|
valueBubble: HTMLElement;
|
|
14
14
|
secondHandle?: HTMLElement;
|
|
15
15
|
secondValueBubble?: HTMLElement;
|
|
16
|
+
formFields: (HTMLInputElement | null)[] | null;
|
|
16
17
|
getContainer: () => HTMLElement;
|
|
17
18
|
getHandle: () => HTMLElement;
|
|
18
19
|
getValueBubble: () => HTMLElement;
|