mtrl 0.9.6 → 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 +24 -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/features.js +5 -4
- 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/features/controller.js +4 -3
- package/dist/components/menu/features/keyboard.js +7 -5
- package/dist/components/menu/features/opener.js +15 -10
- 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 +1 -1
- 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 +29 -27
- 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/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/utils.js +21 -1
- package/dist/components/textfield/api.js +12 -4
- 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/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 +8 -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/lifecycle.js +10 -11
- 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 +1 -1
- package/dist/styles/button-group.css +1 -1
- package/dist/styles/button.css +2 -2
- package/dist/styles/card.css +1 -1
- package/dist/styles/carousel.css +1 -1
- package/dist/styles/checkbox.css +1 -1
- package/dist/styles/chips.css +1 -1
- package/dist/styles/dialog.css +1 -1
- package/dist/styles/divider.css +1 -1
- package/dist/styles/drawer.css +1 -1
- package/dist/styles/extended-fab.css +1 -1
- package/dist/styles/fab.css +1 -1
- package/dist/styles/icon-button.css +1 -1
- package/dist/styles/list.css +1 -1
- package/dist/styles/loading-indicator.css +1 -1
- package/dist/styles/menu.css +1 -1
- package/dist/styles/navigation-rail.css +1 -1
- package/dist/styles/navigation.css +1 -1
- package/dist/styles/progress.css +1 -1
- package/dist/styles/radios.css +1 -1
- package/dist/styles/search.css +1 -1
- package/dist/styles/segmented-button.css +1 -1
- package/dist/styles/select.css +1 -1
- package/dist/styles/side-sheet.css +1 -1
- package/dist/styles/slider.css +1 -1
- package/dist/styles/snackbar.css +1 -1
- package/dist/styles/split-button.css +1 -1
- package/dist/styles/switch.css +1 -1
- package/dist/styles/tabs.css +1 -1
- package/dist/styles/textfield.css +1 -1
- package/dist/styles/timepicker.css +1 -1
- package/dist/styles/tooltip.css +1 -1
- 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/base/_reset.scss +9 -2
- package/src/styles/components/_button.scss +1 -12
|
@@ -75,9 +75,9 @@ const processMenuItems = (options) => {
|
|
|
75
75
|
return menuItem;
|
|
76
76
|
});
|
|
77
77
|
};
|
|
78
|
-
const setupCombobox = (component, menu, state, choose, prefix) => {
|
|
79
|
-
const input =
|
|
80
|
-
const field =
|
|
78
|
+
const setupCombobox = (component, textfield, menu, state, choose, prefix) => {
|
|
79
|
+
const input = textfield.input;
|
|
80
|
+
const field = textfield.element;
|
|
81
81
|
const activeClass = `${prefix}-menu-item--active`;
|
|
82
82
|
const TYPEAHEAD_DELAY = 500;
|
|
83
83
|
const PAGE = 10;
|
|
@@ -291,12 +291,13 @@ export const withMenu = (config) => (component) => {
|
|
|
291
291
|
console.warn("Cannot add menu: textfield not found");
|
|
292
292
|
return component;
|
|
293
293
|
}
|
|
294
|
+
const textfield = component.textfield;
|
|
294
295
|
const state = {
|
|
295
296
|
options: config.options || [],
|
|
296
297
|
selectedOption: null,
|
|
297
298
|
};
|
|
298
299
|
if (config.value) {
|
|
299
|
-
state.selectedOption = state.options.find((opt) => opt.id === config.value);
|
|
300
|
+
state.selectedOption = state.options.find((opt) => opt.id === config.value) ?? null;
|
|
300
301
|
}
|
|
301
302
|
const menuItems = processMenuItems(state.options);
|
|
302
303
|
const listbox = !state.options.some((option) => option && option.hasSubmenu);
|
|
@@ -317,7 +318,7 @@ export const withMenu = (config) => (component) => {
|
|
|
317
318
|
});
|
|
318
319
|
const choose = (option, originalEvent) => {
|
|
319
320
|
state.selectedOption = option;
|
|
320
|
-
|
|
321
|
+
textfield.setValue(option.text);
|
|
321
322
|
menu.setSelected(option.id);
|
|
322
323
|
if (component.emit) {
|
|
323
324
|
const changeEvent = {
|
|
@@ -346,11 +347,11 @@ export const withMenu = (config) => (component) => {
|
|
|
346
347
|
choose(option, event.originalEvent);
|
|
347
348
|
});
|
|
348
349
|
if (listbox) {
|
|
349
|
-
setupCombobox(component, menu, state, choose, config.prefix || "mtrl");
|
|
350
|
+
setupCombobox(component, textfield, menu, state, choose, config.prefix || "mtrl");
|
|
350
351
|
}
|
|
351
352
|
if (!listbox)
|
|
352
|
-
|
|
353
|
-
if (
|
|
353
|
+
textfield.element.addEventListener("keydown", (e) => {
|
|
354
|
+
if (textfield.input.disabled)
|
|
354
355
|
return;
|
|
355
356
|
if ((e.key === "Enter" || e.key === " " || e.key === "ArrowDown") &&
|
|
356
357
|
!menu.isOpen()) {
|
|
@@ -371,29 +372,29 @@ export const withMenu = (config) => (component) => {
|
|
|
371
372
|
defaultPrevented: false,
|
|
372
373
|
});
|
|
373
374
|
}
|
|
374
|
-
|
|
375
|
+
textfield.element.classList.add(`${config.prefix || "mtrl"}-select--open`);
|
|
375
376
|
const PREFIX = config.prefix || "mtrl";
|
|
376
|
-
|
|
377
|
-
if (
|
|
378
|
-
|
|
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`);
|
|
379
380
|
}
|
|
380
381
|
});
|
|
381
382
|
menu.on("close", (event) => {
|
|
382
|
-
|
|
383
|
+
textfield.element.classList.remove(`${config.prefix || "mtrl"}-select--open`);
|
|
383
384
|
setTimeout(() => {
|
|
384
385
|
const PREFIX = config.prefix || "mtrl";
|
|
385
|
-
const isFocused = document.activeElement ===
|
|
386
|
-
|
|
386
|
+
const isFocused = document.activeElement === textfield.input ||
|
|
387
|
+
textfield.element.contains(document.activeElement);
|
|
387
388
|
if (isFocused) {
|
|
388
|
-
|
|
389
|
-
if (
|
|
390
|
-
|
|
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`);
|
|
391
392
|
}
|
|
392
393
|
}
|
|
393
394
|
else {
|
|
394
|
-
|
|
395
|
-
if (
|
|
396
|
-
|
|
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`);
|
|
397
398
|
}
|
|
398
399
|
}
|
|
399
400
|
}, 10);
|
|
@@ -422,21 +423,21 @@ export const withMenu = (config) => (component) => {
|
|
|
422
423
|
setValue: (value) => {
|
|
423
424
|
if (value === null || value === undefined || value === "") {
|
|
424
425
|
state.selectedOption = null;
|
|
425
|
-
|
|
426
|
+
textfield.setValue("");
|
|
426
427
|
menu.setSelected(null);
|
|
427
428
|
return component;
|
|
428
429
|
}
|
|
429
430
|
const option = state.options.find((opt) => "id" in opt && opt.id === value);
|
|
430
431
|
if (option && "text" in option) {
|
|
431
432
|
state.selectedOption = option;
|
|
432
|
-
|
|
433
|
+
textfield.setValue(option.text);
|
|
433
434
|
menu.setSelected(option.id);
|
|
434
435
|
}
|
|
435
436
|
return component;
|
|
436
437
|
},
|
|
437
438
|
clear: () => {
|
|
438
439
|
state.selectedOption = null;
|
|
439
|
-
|
|
440
|
+
textfield.setValue("");
|
|
440
441
|
menu.setSelected(null);
|
|
441
442
|
return component;
|
|
442
443
|
},
|
|
@@ -447,10 +448,11 @@ export const withMenu = (config) => (component) => {
|
|
|
447
448
|
state.options = options;
|
|
448
449
|
const menuItems = processMenuItems(options);
|
|
449
450
|
menu.setItems(menuItems);
|
|
450
|
-
|
|
451
|
-
|
|
451
|
+
const selected = state.selectedOption;
|
|
452
|
+
if (selected &&
|
|
453
|
+
!options.find((opt) => "id" in opt && opt.id === selected.id)) {
|
|
452
454
|
state.selectedOption = null;
|
|
453
|
-
|
|
455
|
+
textfield.setValue("");
|
|
454
456
|
}
|
|
455
457
|
return component;
|
|
456
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;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createElement } from "../../../core/dom/create.js";
|
|
2
2
|
import { setStyles } from "../../../core/dom/utils.js";
|
|
3
|
+
import { createFormValue } from "../../../core/dom/form-value.js";
|
|
3
4
|
export const withDom = (config) => (component) => {
|
|
4
5
|
const getClass = (className) => component.getClass(className);
|
|
5
6
|
const min = config.min || 0;
|
|
@@ -97,6 +98,15 @@ export const withDom = (config) => (component) => {
|
|
|
97
98
|
left: `${secondValuePercent}%`,
|
|
98
99
|
});
|
|
99
100
|
}
|
|
101
|
+
const name = config.name;
|
|
102
|
+
const formFields = name
|
|
103
|
+
? [
|
|
104
|
+
createFormValue(component.element, name, String(value), isDisabled),
|
|
105
|
+
config.range
|
|
106
|
+
? createFormValue(component.element, `${name}-end`, String(config.secondValue ?? max), isDisabled)
|
|
107
|
+
: null,
|
|
108
|
+
]
|
|
109
|
+
: null;
|
|
100
110
|
return {
|
|
101
111
|
...component,
|
|
102
112
|
container,
|
|
@@ -104,6 +114,7 @@ export const withDom = (config) => (component) => {
|
|
|
104
114
|
valueBubble,
|
|
105
115
|
secondHandle,
|
|
106
116
|
secondValueBubble,
|
|
117
|
+
formFields,
|
|
107
118
|
getContainer: () => container,
|
|
108
119
|
getHandle: () => handle,
|
|
109
120
|
getValueBubble: () => valueBubble,
|
|
@@ -51,11 +51,16 @@ export const createHandlers = (config, state, uiRenderer, eventHelpers) => {
|
|
|
51
51
|
secondHandle.classList.remove(`${state.component.getClass("slider-handle")}--focused`);
|
|
52
52
|
try {
|
|
53
53
|
const focusClass = state.component.getClass("slider-handle--focused");
|
|
54
|
-
|
|
54
|
+
state.component.element
|
|
55
|
+
.querySelectorAll(`.${focusClass}`)
|
|
56
|
+
.forEach((el) => {
|
|
55
57
|
el.classList.remove(focusClass);
|
|
56
58
|
});
|
|
57
|
-
|
|
58
|
-
|
|
59
|
+
const active = document.activeElement;
|
|
60
|
+
if (active &&
|
|
61
|
+
state.component.element.contains(active) &&
|
|
62
|
+
active.classList.contains(state.component.getClass("slider-handle"))) {
|
|
63
|
+
active.blur();
|
|
59
64
|
}
|
|
60
65
|
}
|
|
61
66
|
catch (error) {
|
|
@@ -10,6 +10,12 @@ export const withStates = (config) => (component) => {
|
|
|
10
10
|
disableComponent();
|
|
11
11
|
}, 0);
|
|
12
12
|
}
|
|
13
|
+
const setFieldsDisabled = (disabled) => {
|
|
14
|
+
for (const field of component.formFields ?? []) {
|
|
15
|
+
if (field)
|
|
16
|
+
field.disabled = disabled;
|
|
17
|
+
}
|
|
18
|
+
};
|
|
13
19
|
function disableComponent() {
|
|
14
20
|
component.element.classList.add(`${component.getClass("slider")}--disabled`);
|
|
15
21
|
component.element.setAttribute("aria-disabled", "true");
|
|
@@ -21,6 +27,7 @@ export const withStates = (config) => (component) => {
|
|
|
21
27
|
secondHandle.tabIndex = -1;
|
|
22
28
|
secondHandle.setAttribute("aria-disabled", "true");
|
|
23
29
|
}
|
|
30
|
+
setFieldsDisabled(true);
|
|
24
31
|
if (component.renderTracks) {
|
|
25
32
|
component.renderTracks();
|
|
26
33
|
}
|
|
@@ -36,6 +43,7 @@ export const withStates = (config) => (component) => {
|
|
|
36
43
|
secondHandle.tabIndex = 0;
|
|
37
44
|
secondHandle.setAttribute("aria-disabled", "false");
|
|
38
45
|
}
|
|
46
|
+
setFieldsDisabled(false);
|
|
39
47
|
if (component.renderTracks) {
|
|
40
48
|
component.renderTracks();
|
|
41
49
|
}
|
|
@@ -26,6 +26,13 @@ export interface SliderConfig extends BaseComponentConfig {
|
|
|
26
26
|
step?: number;
|
|
27
27
|
/** Whether the slider is disabled */
|
|
28
28
|
disabled?: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Form field name. A slider renders no form control of its own, so it
|
|
31
|
+
* submits nothing unless this is set; with it, a hidden input inside the
|
|
32
|
+
* slider carries the current value. A range slider submits two fields,
|
|
33
|
+
* `name` and `name-end`. A disabled slider submits neither.
|
|
34
|
+
*/
|
|
35
|
+
name?: string;
|
|
29
36
|
/** Color variant of the slider *
|
|
30
37
|
* A stylesheet can override the drawn colours per slider with the custom
|
|
31
38
|
* properties `--<prefix>-slider-color` and `--<prefix>-slider-on-color`,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { SwitchConfig,
|
|
1
|
+
import { SwitchConfig, ApiComponent, ApiOptions } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Default configuration for the Switch component
|
|
4
4
|
*/
|
|
@@ -38,8 +38,8 @@ export declare const getElementConfig: (config: SwitchConfig) => {
|
|
|
38
38
|
};
|
|
39
39
|
/**
|
|
40
40
|
* Creates API configuration for the Switch component
|
|
41
|
-
* @param {
|
|
41
|
+
* @param {ApiComponent} comp - Component with disabled, lifecycle, and checkable features
|
|
42
42
|
* @returns {ApiOptions} API configuration object
|
|
43
43
|
*/
|
|
44
|
-
export declare const getApiConfig: (comp:
|
|
44
|
+
export declare const getApiConfig: (comp: ApiComponent) => ApiOptions;
|
|
45
45
|
export default defaultConfig;
|
|
@@ -13,17 +13,17 @@ export const getElementConfig = (config) => createElementConfig(config, {
|
|
|
13
13
|
});
|
|
14
14
|
export const getApiConfig = (comp) => ({
|
|
15
15
|
disabled: {
|
|
16
|
-
enable: comp.disabled
|
|
17
|
-
disable: comp.disabled
|
|
16
|
+
enable: comp.disabled.enable,
|
|
17
|
+
disable: comp.disabled.disable,
|
|
18
18
|
},
|
|
19
19
|
lifecycle: {
|
|
20
|
-
destroy: comp.lifecycle
|
|
20
|
+
destroy: comp.lifecycle.destroy,
|
|
21
21
|
},
|
|
22
22
|
checkable: {
|
|
23
|
-
check: comp.checkable
|
|
24
|
-
uncheck: comp.checkable
|
|
25
|
-
toggle: comp.checkable
|
|
26
|
-
isChecked: comp.checkable
|
|
23
|
+
check: comp.checkable.check,
|
|
24
|
+
uncheck: comp.checkable.uncheck,
|
|
25
|
+
toggle: comp.checkable.toggle,
|
|
26
|
+
isChecked: comp.checkable.isChecked,
|
|
27
27
|
},
|
|
28
28
|
});
|
|
29
29
|
export default defaultConfig;
|
|
@@ -2,9 +2,9 @@ import { withTrack as withTrackCore, } from "../../core/compose/features/index.j
|
|
|
2
2
|
const ensureSwitchStructure = (component, prefix, componentName) => {
|
|
3
3
|
const PREFIX = prefix || "mtrl";
|
|
4
4
|
const COMPONENT = componentName || "switch";
|
|
5
|
-
|
|
6
|
-
if (!
|
|
7
|
-
container = document.createElement("div");
|
|
5
|
+
const existingContainer = component.element.querySelector(`.${PREFIX}-${COMPONENT}-container`);
|
|
6
|
+
if (!existingContainer) {
|
|
7
|
+
const container = document.createElement("div");
|
|
8
8
|
container.className = `${PREFIX}-${COMPONENT}-container`;
|
|
9
9
|
const input = component.element.querySelector(`.${PREFIX}-${COMPONENT}-input`);
|
|
10
10
|
const track = component.element.querySelector(`.${PREFIX}-${COMPONENT}-track`);
|
|
@@ -32,9 +32,9 @@ const ensureSwitchStructure = (component, prefix, componentName) => {
|
|
|
32
32
|
if (label) {
|
|
33
33
|
contentWrapper.appendChild(label);
|
|
34
34
|
}
|
|
35
|
-
|
|
35
|
+
existingContainer.insertBefore(contentWrapper, existingContainer.firstChild);
|
|
36
36
|
}
|
|
37
|
-
return { container, contentWrapper };
|
|
37
|
+
return { container: existingContainer, contentWrapper };
|
|
38
38
|
};
|
|
39
39
|
export const withSupportingText = (config) => (component) => {
|
|
40
40
|
const PREFIX = config.prefix || "mtrl";
|
|
@@ -130,3 +130,13 @@ export interface BaseComponent {
|
|
|
130
130
|
setSupportingText?: (text: string, isError?: boolean) => void;
|
|
131
131
|
removeSupportingText?: () => void;
|
|
132
132
|
}
|
|
133
|
+
/**
|
|
134
|
+
* Component as it reaches the API step of the pipeline.
|
|
135
|
+
*
|
|
136
|
+
* `BaseComponent` marks the feature controllers optional because a component
|
|
137
|
+
* part-way through the pipeline has not got them yet. By the time
|
|
138
|
+
* `getApiConfig` runs, withCheckable, withDisabled and withLifecycle have all
|
|
139
|
+
* been applied, so they are there.
|
|
140
|
+
* @internal
|
|
141
|
+
*/
|
|
142
|
+
export type ApiComponent = BaseComponent & Required<Pick<BaseComponent, "checkable" | "disabled" | "lifecycle">>;
|
|
@@ -125,5 +125,6 @@ export interface IndicatorComponent {
|
|
|
125
125
|
*/
|
|
126
126
|
export declare const withIndicator: <T extends IndicatorFeatureConfig>(config: T) => <C extends ComponentBase & {
|
|
127
127
|
tabs: TabComponent[];
|
|
128
|
+
handleTabClick: (event: unknown, tab: TabComponent) => void;
|
|
128
129
|
}>(component: C) => C & IndicatorComponent;
|
|
129
130
|
export {};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { setHTML } from "../../core/dom/html.js";
|
|
1
2
|
export const addScrollIndicators = (tabs, config = {}) => {
|
|
2
3
|
const { enabled = true, showButtons = false, appearance = 'fade' } = config;
|
|
3
4
|
if (!enabled)
|
|
@@ -17,11 +18,11 @@ export const addScrollIndicators = (tabs, config = {}) => {
|
|
|
17
18
|
const leftButton = document.createElement('button');
|
|
18
19
|
leftButton.className = `${tabs.getClass('tabs')}-scroll-button ${tabs.getClass('tabs')}-scroll-button--left`;
|
|
19
20
|
leftButton.setAttribute('aria-label', 'Scroll tabs left');
|
|
20
|
-
leftButton
|
|
21
|
+
setHTML(leftButton, '<svg viewBox="0 0 24 24"><path d="M15.41 7.41L14 6l-6 6 6 6 1.41-1.41L10.83 12z"/></svg>');
|
|
21
22
|
const rightButton = document.createElement('button');
|
|
22
23
|
rightButton.className = `${tabs.getClass('tabs')}-scroll-button ${tabs.getClass('tabs')}-scroll-button--right`;
|
|
23
24
|
rightButton.setAttribute('aria-label', 'Scroll tabs right');
|
|
24
|
-
rightButton
|
|
25
|
+
setHTML(rightButton, '<svg viewBox="0 0 24 24"><path d="M10 6L8.59 7.41 13.17 12l-4.58 4.59L10 18l6-6z"/></svg>');
|
|
25
26
|
tabs.element.appendChild(leftButton);
|
|
26
27
|
tabs.element.appendChild(rightButton);
|
|
27
28
|
leftButton.addEventListener('click', () => {
|
|
@@ -1,7 +1,2 @@
|
|
|
1
1
|
import { TabConfig, TabComponent } from "./types.js";
|
|
2
|
-
/**
|
|
3
|
-
* Creates a new Tab component following MD3 guidelines
|
|
4
|
-
* @param {TabConfig} config - Tab configuration object
|
|
5
|
-
* @returns {TabComponent} Tab component instance
|
|
6
|
-
*/
|
|
7
2
|
export declare const createTab: (config?: TabConfig) => TabComponent;
|
|
@@ -5,6 +5,15 @@ import { TAB_LAYOUT } from "./constants.js";
|
|
|
5
5
|
import { createTabConfig } from "./config.js";
|
|
6
6
|
import createButton from "../button/index.js";
|
|
7
7
|
import createBadge from "../badge/index.js";
|
|
8
|
+
function linkPanel(element, value) {
|
|
9
|
+
const panelId = `tabpanel-${value}`;
|
|
10
|
+
if (typeof document !== "undefined" && document.getElementById(panelId)) {
|
|
11
|
+
element.setAttribute("aria-controls", panelId);
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
element.removeAttribute("aria-controls");
|
|
15
|
+
}
|
|
16
|
+
}
|
|
8
17
|
export const createTab = (config = {}) => {
|
|
9
18
|
const baseConfig = createTabConfig(config);
|
|
10
19
|
try {
|
|
@@ -31,7 +40,7 @@ export const createTab = (config = {}) => {
|
|
|
31
40
|
baseComponent.element.setAttribute("aria-selected", baseConfig.state === "active" ? "true" : "false");
|
|
32
41
|
if (baseConfig.value) {
|
|
33
42
|
baseComponent.element.setAttribute("id", `tab-${baseConfig.value}`);
|
|
34
|
-
baseComponent.element
|
|
43
|
+
linkPanel(baseComponent.element, baseConfig.value);
|
|
35
44
|
}
|
|
36
45
|
if (baseConfig.state === "active") {
|
|
37
46
|
baseComponent.element.classList.add(`${baseComponent.getClass("tab")}--active`);
|
|
@@ -53,7 +62,7 @@ export const createTab = (config = {}) => {
|
|
|
53
62
|
baseComponent.off(event, handler);
|
|
54
63
|
return this;
|
|
55
64
|
},
|
|
56
|
-
badge:
|
|
65
|
+
badge: undefined,
|
|
57
66
|
getValue() {
|
|
58
67
|
return button.getValue();
|
|
59
68
|
},
|
|
@@ -61,7 +70,7 @@ export const createTab = (config = {}) => {
|
|
|
61
70
|
const safeValue = value || "";
|
|
62
71
|
button.setValue(safeValue);
|
|
63
72
|
this.element.setAttribute("id", `tab-${safeValue}`);
|
|
64
|
-
this.element
|
|
73
|
+
linkPanel(this.element, safeValue);
|
|
65
74
|
return this;
|
|
66
75
|
},
|
|
67
76
|
activate() {
|
|
@@ -3,7 +3,7 @@ export function getActiveTab(component) {
|
|
|
3
3
|
return component.getActiveTab();
|
|
4
4
|
}
|
|
5
5
|
if (Array.isArray(component.tabs)) {
|
|
6
|
-
return component.tabs.find((tab) => tab.isActive && tab.isActive());
|
|
6
|
+
return component.tabs.find((tab) => tab.isActive && tab.isActive()) ?? null;
|
|
7
7
|
}
|
|
8
8
|
return null;
|
|
9
9
|
}
|
|
@@ -15,8 +15,11 @@ export function updateTabPanels(component) {
|
|
|
15
15
|
return;
|
|
16
16
|
const activeValue = activeTab.getValue();
|
|
17
17
|
const tabPanels = document.querySelectorAll(`[role="tabpanel"]`);
|
|
18
|
+
const linked = new Set();
|
|
18
19
|
tabPanels.forEach((panel) => {
|
|
19
20
|
const forTab = panel.getAttribute("aria-labelledby")?.replace("tab-", "");
|
|
21
|
+
if (forTab)
|
|
22
|
+
linked.add(forTab);
|
|
20
23
|
if (forTab === activeValue) {
|
|
21
24
|
panel.removeAttribute("hidden");
|
|
22
25
|
panel.setAttribute("tabindex", "0");
|
|
@@ -26,6 +29,23 @@ export function updateTabPanels(component) {
|
|
|
26
29
|
panel.setAttribute("tabindex", "-1");
|
|
27
30
|
}
|
|
28
31
|
});
|
|
32
|
+
const allTabs = typeof component.getTabs === "function"
|
|
33
|
+
? component.getTabs()
|
|
34
|
+
: component.tabs ?? [];
|
|
35
|
+
{
|
|
36
|
+
for (const tab of allTabs) {
|
|
37
|
+
if (typeof tab?.getValue !== "function" || !tab.element)
|
|
38
|
+
continue;
|
|
39
|
+
const value = tab.getValue();
|
|
40
|
+
const panelId = `tabpanel-${value}`;
|
|
41
|
+
if (linked.has(value) && document.getElementById(panelId)) {
|
|
42
|
+
tab.element.setAttribute("aria-controls", panelId);
|
|
43
|
+
}
|
|
44
|
+
else {
|
|
45
|
+
tab.element.removeAttribute("aria-controls");
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
29
49
|
}
|
|
30
50
|
export function syncTabStops(component) {
|
|
31
51
|
if (typeof component.getTabs !== "function")
|
|
@@ -45,7 +45,9 @@ export const withAPI = ({ disabled, lifecycle }) => (component) => ({
|
|
|
45
45
|
getLabel() {
|
|
46
46
|
return component.label?.getText() || "";
|
|
47
47
|
},
|
|
48
|
-
|
|
48
|
+
get leadingIcon() {
|
|
49
|
+
return component.leadingIcon ?? null;
|
|
50
|
+
},
|
|
49
51
|
setLeadingIcon(html) {
|
|
50
52
|
if (component.setLeadingIcon) {
|
|
51
53
|
component.setLeadingIcon(html);
|
|
@@ -64,7 +66,9 @@ export const withAPI = ({ disabled, lifecycle }) => (component) => ({
|
|
|
64
66
|
}
|
|
65
67
|
return this;
|
|
66
68
|
},
|
|
67
|
-
|
|
69
|
+
get trailingIcon() {
|
|
70
|
+
return component.trailingIcon ?? null;
|
|
71
|
+
},
|
|
68
72
|
setTrailingIcon(html) {
|
|
69
73
|
if (component.setTrailingIcon) {
|
|
70
74
|
component.setTrailingIcon(html);
|
|
@@ -96,7 +100,9 @@ export const withAPI = ({ disabled, lifecycle }) => (component) => ({
|
|
|
96
100
|
}
|
|
97
101
|
return this;
|
|
98
102
|
},
|
|
99
|
-
|
|
103
|
+
get prefixTextElement() {
|
|
104
|
+
return component.prefixTextElement ?? null;
|
|
105
|
+
},
|
|
100
106
|
setPrefixText(text) {
|
|
101
107
|
if (component.setPrefixText) {
|
|
102
108
|
component.setPrefixText(text);
|
|
@@ -115,7 +121,9 @@ export const withAPI = ({ disabled, lifecycle }) => (component) => ({
|
|
|
115
121
|
}
|
|
116
122
|
return this;
|
|
117
123
|
},
|
|
118
|
-
|
|
124
|
+
get suffixTextElement() {
|
|
125
|
+
return component.suffixTextElement ?? null;
|
|
126
|
+
},
|
|
119
127
|
setSuffixText(text) {
|
|
120
128
|
if (component.setSuffixText) {
|
|
121
129
|
component.setSuffixText(text);
|
|
@@ -1,52 +1,51 @@
|
|
|
1
|
+
import { setHTML } from "../../../core/dom/html.js";
|
|
1
2
|
export const withLeadingIcon = (config) => (component) => {
|
|
2
|
-
if (!config.leadingIcon) {
|
|
3
|
-
return component;
|
|
4
|
-
}
|
|
5
3
|
const PREFIX = config.prefix || 'mtrl';
|
|
6
|
-
const
|
|
7
|
-
iconElement
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
4
|
+
const NAME = config.componentName || 'textfield';
|
|
5
|
+
let iconElement = null;
|
|
6
|
+
const ensureIcon = () => {
|
|
7
|
+
if (iconElement && iconElement.parentNode)
|
|
8
|
+
return iconElement;
|
|
9
|
+
const element = document.createElement('span');
|
|
10
|
+
element.className = `${PREFIX}-${NAME}-leading-icon`;
|
|
11
|
+
component.element.appendChild(element);
|
|
12
|
+
component.element.classList.add(`${PREFIX}-${NAME}--with-leading-icon`);
|
|
13
|
+
if (component.input) {
|
|
14
|
+
component.input.classList.add(`${PREFIX}-${NAME}-input--with-leading-icon`);
|
|
15
|
+
}
|
|
16
|
+
iconElement = element;
|
|
17
|
+
return element;
|
|
18
|
+
};
|
|
19
|
+
const detachIcon = () => {
|
|
20
|
+
iconElement?.remove();
|
|
21
|
+
iconElement = null;
|
|
22
|
+
component.element.classList.remove(`${PREFIX}-${NAME}--with-leading-icon`);
|
|
23
|
+
if (component.input) {
|
|
24
|
+
component.input.classList.remove(`${PREFIX}-${NAME}-input--with-leading-icon`);
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
if (config.leadingIcon) {
|
|
28
|
+
setHTML(ensureIcon(), config.leadingIcon);
|
|
13
29
|
}
|
|
14
30
|
if ('lifecycle' in component && component.lifecycle?.destroy) {
|
|
15
31
|
const originalDestroy = component.lifecycle.destroy;
|
|
16
32
|
component.lifecycle.destroy = () => {
|
|
17
|
-
|
|
18
|
-
iconElement
|
|
33
|
+
iconElement?.remove();
|
|
34
|
+
iconElement = null;
|
|
19
35
|
originalDestroy.call(component.lifecycle);
|
|
20
36
|
};
|
|
21
37
|
}
|
|
22
|
-
const initialPosition = setTimeout(() => {
|
|
23
|
-
const labelEl = component.element.querySelector(`.${PREFIX}-${config.componentName || 'textfield'}-label`);
|
|
24
|
-
if (labelEl) {
|
|
25
|
-
if (!component.element.classList.contains(`${PREFIX}-${config.componentName || 'textfield'}--with-prefix`)) {
|
|
26
|
-
labelEl.style.left = '44px';
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
}, 10);
|
|
30
38
|
return {
|
|
31
39
|
...component,
|
|
32
40
|
leadingIcon: iconElement,
|
|
33
41
|
setLeadingIcon(html) {
|
|
34
|
-
|
|
42
|
+
this.leadingIcon = ensureIcon();
|
|
43
|
+
setHTML(this.leadingIcon, html);
|
|
35
44
|
return this;
|
|
36
45
|
},
|
|
37
46
|
removeLeadingIcon() {
|
|
38
|
-
if (iconElement
|
|
39
|
-
|
|
40
|
-
component.element.classList.remove(`${PREFIX}-${config.componentName || 'textfield'}--with-leading-icon`);
|
|
41
|
-
if (component.input) {
|
|
42
|
-
component.input.classList.remove(`${PREFIX}-${config.componentName || 'textfield'}-input--with-leading-icon`);
|
|
43
|
-
}
|
|
44
|
-
if (!component.element.classList.contains(`${PREFIX}-${config.componentName || 'textfield'}--with-prefix`)) {
|
|
45
|
-
const labelEl = component.element.querySelector(`.${PREFIX}-${config.componentName || 'textfield'}-label`);
|
|
46
|
-
if (labelEl) {
|
|
47
|
-
labelEl.style.left = '';
|
|
48
|
-
}
|
|
49
|
-
}
|
|
47
|
+
if (iconElement) {
|
|
48
|
+
detachIcon();
|
|
50
49
|
this.leadingIcon = null;
|
|
51
50
|
}
|
|
52
51
|
return this;
|