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
|
@@ -2,7 +2,7 @@ import { PREFIX } from "../../core/config.js";
|
|
|
2
2
|
import { pipe } from "../../core/compose/index.js";
|
|
3
3
|
import { createBase, withElement } from "../../core/compose/component.js";
|
|
4
4
|
import { withEvents, withDisabled, withLifecycle, } from "../../core/compose/features/index.js";
|
|
5
|
-
import { withAPI } from "./api.js";
|
|
5
|
+
import { withAPI, isReadyForApi } from "./api.js";
|
|
6
6
|
import { createBaseConfig, getContainerConfig, getInputConfig, getCalendarConfig, getApiConfig, } from "./config.js";
|
|
7
7
|
import { formatDate, parseDate } from "./utils.js";
|
|
8
8
|
import { renderCalendar } from "./render.js";
|
|
@@ -45,7 +45,7 @@ const createDatePicker = (config = {}) => {
|
|
|
45
45
|
updateCalendar() {
|
|
46
46
|
if (!this.isOpen || !this.calendarElement)
|
|
47
47
|
return;
|
|
48
|
-
this.calendarElement.
|
|
48
|
+
this.calendarElement.replaceChildren();
|
|
49
49
|
const calendar = renderCalendar(this, (...[event, data]) => {
|
|
50
50
|
switch (event) {
|
|
51
51
|
case "dateSelected":
|
|
@@ -234,6 +234,9 @@ const createDatePicker = (config = {}) => {
|
|
|
234
234
|
}
|
|
235
235
|
};
|
|
236
236
|
document.addEventListener("click", state.outsideClickHandler);
|
|
237
|
+
if (!isReadyForApi(state)) {
|
|
238
|
+
throw new Error("DatePicker input or outside-click handler is missing");
|
|
239
|
+
}
|
|
237
240
|
return withAPI(state, getApiConfig(component))(component);
|
|
238
241
|
}
|
|
239
242
|
catch (error) {
|
|
@@ -349,6 +349,20 @@ export interface DatePickerState {
|
|
|
349
349
|
nextYear(): void;
|
|
350
350
|
render(): void;
|
|
351
351
|
}
|
|
352
|
+
/**
|
|
353
|
+
* State as it reaches the API step.
|
|
354
|
+
*
|
|
355
|
+
* `DatePickerState` marks these loose because the state object is built before
|
|
356
|
+
* the DOM is. By the time `withAPI` runs, the factory has created the input
|
|
357
|
+
* (throwing if it is not one) and installed the outside-click handler, so both
|
|
358
|
+
* are there — and the public `input` is declared non-optional.
|
|
359
|
+
* @category Components
|
|
360
|
+
* @internal
|
|
361
|
+
*/
|
|
362
|
+
export type DatePickerApiState = DatePickerState & {
|
|
363
|
+
input: HTMLInputElement;
|
|
364
|
+
outsideClickHandler: EventListener;
|
|
365
|
+
};
|
|
352
366
|
/**
|
|
353
367
|
* Events the calendar renderer emits, each with its payload
|
|
354
368
|
* @internal
|
|
@@ -75,13 +75,13 @@ export const isDateInRange = (date, startDate, endDate) => {
|
|
|
75
75
|
export const generateCalendarDates = (year, month, selectedDate = null, rangeEndDate = null, minDate = null, maxDate = null) => {
|
|
76
76
|
const today = new Date();
|
|
77
77
|
today.setHours(0, 0, 0, 0);
|
|
78
|
+
const outOfRange = (date) => Boolean((minDate && date < minDate) || (maxDate && date > maxDate));
|
|
78
79
|
const result = [];
|
|
79
80
|
const firstDay = getFirstDayOfMonth(year, month);
|
|
80
81
|
const daysInPrevMonth = getDaysInMonth(year, month - 1);
|
|
81
82
|
for (let i = firstDay - 1; i >= 0; i--) {
|
|
82
83
|
const date = new Date(year, month - 1, daysInPrevMonth - i);
|
|
83
|
-
const isDisabled = (
|
|
84
|
-
(maxDate && date > maxDate);
|
|
84
|
+
const isDisabled = outOfRange(date);
|
|
85
85
|
result.push({
|
|
86
86
|
date,
|
|
87
87
|
day: date.getDate(),
|
|
@@ -95,8 +95,7 @@ export const generateCalendarDates = (year, month, selectedDate = null, rangeEnd
|
|
|
95
95
|
for (let i = 1; i <= daysInMonth; i++) {
|
|
96
96
|
const date = new Date(year, month, i);
|
|
97
97
|
const isSelected = selectedDate ? isSameDay(date, selectedDate) : false;
|
|
98
|
-
const isDisabled = (
|
|
99
|
-
(maxDate && date > maxDate);
|
|
98
|
+
const isDisabled = outOfRange(date);
|
|
100
99
|
const calendarDate = {
|
|
101
100
|
date,
|
|
102
101
|
day: i,
|
|
@@ -117,8 +116,7 @@ export const generateCalendarDates = (year, month, selectedDate = null, rangeEnd
|
|
|
117
116
|
const remainingDays = totalDaysNeeded - result.length;
|
|
118
117
|
for (let i = 1; i <= remainingDays; i++) {
|
|
119
118
|
const date = new Date(year, month + 1, i);
|
|
120
|
-
const isDisabled = (
|
|
121
|
-
(maxDate && date > maxDate);
|
|
119
|
+
const isDisabled = outOfRange(date);
|
|
122
120
|
result.push({
|
|
123
121
|
date,
|
|
124
122
|
day: i,
|
|
@@ -2,6 +2,7 @@ import { getOverlayConfig } from "./config.js";
|
|
|
2
2
|
import createButton from "../button/index.js";
|
|
3
3
|
import { createDivider } from "../divider/index.js";
|
|
4
4
|
import { addClass, removeClass } from "../../core/dom/classes.js";
|
|
5
|
+
import { setHTML } from "../../core/dom/html.js";
|
|
5
6
|
const DIALOG_EVENTS = {
|
|
6
7
|
OPEN: "open",
|
|
7
8
|
CLOSE: "close",
|
|
@@ -53,12 +54,12 @@ export const withStructure = (config) => (component) => {
|
|
|
53
54
|
const closeButton = document.createElement("button");
|
|
54
55
|
closeButton.classList.add(component.getClass("dialog-header-close"));
|
|
55
56
|
closeButton.setAttribute("aria-label", "Close dialog");
|
|
56
|
-
closeButton
|
|
57
|
+
setHTML(closeButton, `
|
|
57
58
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
|
58
59
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
59
60
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
60
61
|
</svg>
|
|
61
|
-
|
|
62
|
+
`);
|
|
62
63
|
closeButton.addEventListener("click", (e) => {
|
|
63
64
|
e.preventDefault();
|
|
64
65
|
e.stopPropagation();
|
|
@@ -75,7 +76,7 @@ export const withStructure = (config) => (component) => {
|
|
|
75
76
|
content.classList.add(component.getClass("dialog-content"));
|
|
76
77
|
content.id = contentId;
|
|
77
78
|
if (config.content) {
|
|
78
|
-
content
|
|
79
|
+
setHTML(content, config.content);
|
|
79
80
|
}
|
|
80
81
|
return content;
|
|
81
82
|
};
|
|
@@ -488,7 +489,7 @@ export const withContent = () => (component) => {
|
|
|
488
489
|
return subtitleElement ? subtitleElement.textContent || "" : "";
|
|
489
490
|
},
|
|
490
491
|
setContent(content) {
|
|
491
|
-
contentElement
|
|
492
|
+
setHTML(contentElement, content);
|
|
492
493
|
},
|
|
493
494
|
getContent() {
|
|
494
495
|
return contentElement.innerHTML;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { DRAWER_EVENTS } from "../constants.js";
|
|
2
2
|
import { createRipple, } from "../../../core/compose/features/ripple.js";
|
|
3
|
+
import { setHTML } from "../../../core/dom/html.js";
|
|
3
4
|
const createItemElement = (item, getClass) => {
|
|
4
5
|
const el = document.createElement("button");
|
|
5
6
|
el.className = getClass("drawer__item");
|
|
@@ -18,7 +19,7 @@ const createItemElement = (item, getClass) => {
|
|
|
18
19
|
if (item.icon) {
|
|
19
20
|
const iconEl = document.createElement("span");
|
|
20
21
|
iconEl.className = getClass("drawer__item-icon");
|
|
21
|
-
iconEl
|
|
22
|
+
setHTML(iconEl, item.icon);
|
|
22
23
|
iconEl.setAttribute("aria-hidden", "true");
|
|
23
24
|
el.appendChild(iconEl);
|
|
24
25
|
}
|
|
@@ -70,7 +71,7 @@ export const withItems = (config) => (component) => {
|
|
|
70
71
|
if (!itemsContainer)
|
|
71
72
|
return;
|
|
72
73
|
unmountRipples();
|
|
73
|
-
itemsContainer.
|
|
74
|
+
itemsContainer.replaceChildren();
|
|
74
75
|
let navIndex = 0;
|
|
75
76
|
currentItems.forEach((item) => {
|
|
76
77
|
const type = item.type || "item";
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { LIST_CLASSES, LIST_EVENTS } from "../constants.js";
|
|
2
|
+
import { setHTML } from "../../../core/dom/html.js";
|
|
2
3
|
export const withRenderer = (config) => component => {
|
|
3
4
|
if (!component.element) {
|
|
4
5
|
console.warn('Cannot initialize list renderer: missing element');
|
|
@@ -24,9 +25,9 @@ export const withRenderer = (config) => component => {
|
|
|
24
25
|
return element;
|
|
25
26
|
}
|
|
26
27
|
const renderAllItems = () => {
|
|
27
|
-
listContainer.
|
|
28
|
+
listContainer.replaceChildren();
|
|
28
29
|
if (!items || items.length === 0) {
|
|
29
|
-
listContainer
|
|
30
|
+
setHTML(listContainer, '<div class="mtrl-list-empty">No items</div>');
|
|
30
31
|
return;
|
|
31
32
|
}
|
|
32
33
|
const fragment = document.createDocumentFragment();
|
|
@@ -84,7 +85,7 @@ export const withRenderer = (config) => component => {
|
|
|
84
85
|
if (component.lifecycle?.destroy) {
|
|
85
86
|
const originalDestroy = component.lifecycle.destroy;
|
|
86
87
|
component.lifecycle.destroy = () => {
|
|
87
|
-
listContainer.
|
|
88
|
+
listContainer.replaceChildren();
|
|
88
89
|
originalDestroy();
|
|
89
90
|
};
|
|
90
91
|
}
|
|
@@ -15,7 +15,7 @@ interface ApiOptions {
|
|
|
15
15
|
getItems: () => MenuContent[];
|
|
16
16
|
setPosition: (position: MenuPosition) => void;
|
|
17
17
|
getPosition: () => MenuPosition;
|
|
18
|
-
setSelected: (itemId: string) => void;
|
|
18
|
+
setSelected: (itemId: string | null) => void;
|
|
19
19
|
getSelected: () => string | null;
|
|
20
20
|
};
|
|
21
21
|
opener: {
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { createMenuTasks } from "./tasks.js";
|
|
2
2
|
import { menuOpened, menuClosed } from "./registry.js";
|
|
3
|
+
import { setHTML } from "../../../core/dom/html.js";
|
|
3
4
|
const withController = (config) => (component) => {
|
|
4
5
|
if (!component.element) {
|
|
5
6
|
console.warn("Cannot initialize menu controller: missing element");
|
|
@@ -90,7 +91,7 @@ const withController = (config) => (component) => {
|
|
|
90
91
|
if (item.icon) {
|
|
91
92
|
const iconElement = document.createElement("span");
|
|
92
93
|
iconElement.className = `${component.getClass("menu-item-icon")}`;
|
|
93
|
-
iconElement
|
|
94
|
+
setHTML(iconElement, item.icon);
|
|
94
95
|
contentContainer.appendChild(iconElement);
|
|
95
96
|
}
|
|
96
97
|
const textElement = document.createElement("span");
|
|
@@ -183,7 +184,7 @@ const withController = (config) => (component) => {
|
|
|
183
184
|
target.appendChild(createMenuItem(item, index));
|
|
184
185
|
}
|
|
185
186
|
});
|
|
186
|
-
component.element.
|
|
187
|
+
component.element.replaceChildren();
|
|
187
188
|
component.element.appendChild(menuList);
|
|
188
189
|
};
|
|
189
190
|
const findItemById = (id) => {
|
|
@@ -192,7 +193,7 @@ const withController = (config) => (component) => {
|
|
|
192
193
|
return item;
|
|
193
194
|
}
|
|
194
195
|
if ("submenu" in item && Array.isArray(item.submenu)) {
|
|
195
|
-
for (const subItem of item.submenu) {
|
|
196
|
+
for (const subItem of item.submenu ?? []) {
|
|
196
197
|
if ("id" in subItem && subItem.id === id) {
|
|
197
198
|
return subItem;
|
|
198
199
|
}
|
|
@@ -39,10 +39,12 @@ export const createKeyboardNavigation = (component) => {
|
|
|
39
39
|
if (!parentId)
|
|
40
40
|
return null;
|
|
41
41
|
const level = parseInt(menuElement.getAttribute("data-level") || "1", 10);
|
|
42
|
+
const owner = component.element.id;
|
|
43
|
+
const ownerFilter = owner ? `[data-owner="${owner}"]` : "";
|
|
42
44
|
const scope = level <= 1
|
|
43
45
|
? component.element
|
|
44
|
-
: document.querySelector(`.${component.getClass("menu--submenu")}[data-level="${level - 1}"]`);
|
|
45
|
-
return ((scope ??
|
|
46
|
+
: document.querySelector(`.${component.getClass("menu--submenu")}${ownerFilter}[data-level="${level - 1}"]`);
|
|
47
|
+
return ((scope ?? component.element).querySelector(`.${component.getClass("menu-item")}[data-id="${parentId}"]`) ?? null);
|
|
46
48
|
};
|
|
47
49
|
const menuItems = (menuElement) => Array.from(menuElement.querySelectorAll(`.${component.getClass("menu-item")}`));
|
|
48
50
|
const resetTypeahead = () => {
|
|
@@ -173,10 +175,10 @@ export const createKeyboardNavigation = (component) => {
|
|
|
173
175
|
if (focusedItemIndex >= 0 &&
|
|
174
176
|
items[focusedItemIndex].classList.contains(`${component.getClass("menu-item--submenu")}`)) {
|
|
175
177
|
const itemElement = items[focusedItemIndex];
|
|
176
|
-
const itemIndex = parseInt(itemElement.getAttribute("data-index"), 10);
|
|
178
|
+
const itemIndex = parseInt(itemElement.getAttribute("data-index") ?? "0", 10);
|
|
177
179
|
const parentMenu = itemElement.closest(`.${component.getClass("menu--submenu")}`);
|
|
178
180
|
const parentItemId = parentMenu?.getAttribute("data-parent-item");
|
|
179
|
-
const parentItem = actions.findItemById(parentItemId);
|
|
181
|
+
const parentItem = parentItemId ? actions.findItemById(parentItemId) : null;
|
|
180
182
|
if (parentItem && parentItem.submenu) {
|
|
181
183
|
const itemData = parentItem.submenu[itemIndex];
|
|
182
184
|
actions.handleNestedSubmenuClick(itemData, itemIndex, itemElement);
|
|
@@ -187,7 +189,7 @@ export const createKeyboardNavigation = (component) => {
|
|
|
187
189
|
if (focusedItemIndex >= 0 &&
|
|
188
190
|
items[focusedItemIndex].classList.contains(`${component.getClass("menu-item--submenu")}`)) {
|
|
189
191
|
const itemElement = items[focusedItemIndex];
|
|
190
|
-
const itemIndex = parseInt(itemElement.getAttribute("data-index"), 10);
|
|
192
|
+
const itemIndex = parseInt(itemElement.getAttribute("data-index") ?? "0", 10);
|
|
191
193
|
const itemData = state.items[itemIndex];
|
|
192
194
|
actions.handleSubmenuClick(itemData, itemIndex, itemElement);
|
|
193
195
|
}
|
|
@@ -128,23 +128,27 @@ const withOpener = (config) => (component) => {
|
|
|
128
128
|
const restoreFocusToOpener = () => {
|
|
129
129
|
if (!state.openerElement)
|
|
130
130
|
return;
|
|
131
|
-
|
|
132
|
-
|
|
131
|
+
const openerComponent = state.openerComponent;
|
|
132
|
+
if (openerComponent && typeof openerComponent.focus === "function") {
|
|
133
133
|
tasks.requestAnimationFrame(() => {
|
|
134
|
-
|
|
134
|
+
openerComponent.focus();
|
|
135
135
|
});
|
|
136
136
|
return;
|
|
137
137
|
}
|
|
138
|
-
if (
|
|
139
|
-
"input" in
|
|
140
|
-
|
|
138
|
+
if (openerComponent &&
|
|
139
|
+
"input" in openerComponent &&
|
|
140
|
+
openerComponent.input instanceof HTMLElement) {
|
|
141
|
+
const input = openerComponent.input;
|
|
141
142
|
tasks.requestAnimationFrame(() => {
|
|
142
|
-
|
|
143
|
+
input.focus();
|
|
143
144
|
});
|
|
144
145
|
return;
|
|
145
146
|
}
|
|
147
|
+
const openerElement = state.openerElement;
|
|
148
|
+
if (!openerElement)
|
|
149
|
+
return;
|
|
146
150
|
tasks.requestAnimationFrame(() => {
|
|
147
|
-
|
|
151
|
+
openerElement.focus();
|
|
148
152
|
});
|
|
149
153
|
};
|
|
150
154
|
const handleOpenerClick = (e) => {
|
|
@@ -245,7 +249,8 @@ const withOpener = (config) => (component) => {
|
|
|
245
249
|
state.activeClass = "";
|
|
246
250
|
};
|
|
247
251
|
const { element, component: openerComponent } = resolveOpener(config.opener);
|
|
248
|
-
|
|
252
|
+
if (element)
|
|
253
|
+
setupOpenerEvents({ element, component: openerComponent });
|
|
249
254
|
if (component.lifecycle) {
|
|
250
255
|
const originalDestroy = component.lifecycle.destroy || (() => { });
|
|
251
256
|
component.lifecycle.destroy = () => {
|
|
@@ -278,7 +283,7 @@ const withOpener = (config) => (component) => {
|
|
|
278
283
|
setOpener(opener) {
|
|
279
284
|
const resolved = resolveOpener(opener);
|
|
280
285
|
if (resolved.element) {
|
|
281
|
-
setupOpenerEvents(resolved);
|
|
286
|
+
setupOpenerEvents({ element: resolved.element, component: resolved.component });
|
|
282
287
|
}
|
|
283
288
|
return component;
|
|
284
289
|
},
|
|
@@ -248,7 +248,7 @@ export const createPositioner = (component, config) => {
|
|
|
248
248
|
const positionMenu = (openerElement) => {
|
|
249
249
|
if (!openerElement || !component.element)
|
|
250
250
|
return;
|
|
251
|
-
positionElement(component.element, openerElement, config.position, false);
|
|
251
|
+
positionElement(component.element, openerElement, config.position ?? "bottom-start", false);
|
|
252
252
|
};
|
|
253
253
|
const positionSubmenu = (submenuElement, parentItemElement, level = 1) => {
|
|
254
254
|
if (!submenuElement || !parentItemElement)
|
|
@@ -145,7 +145,7 @@ const withSubmenu = (config) => (component) => {
|
|
|
145
145
|
const currentLevel = itemElement.closest(`.${component.getClass("menu--submenu")}`)
|
|
146
146
|
? parseInt(itemElement
|
|
147
147
|
.closest(`.${component.getClass("menu--submenu")}`)
|
|
148
|
-
|
|
148
|
+
?.getAttribute("data-level") || "0", 10) + 1
|
|
149
149
|
: 1;
|
|
150
150
|
closeSubmenu(currentLevel);
|
|
151
151
|
const existingSubmenuIndex = state.activeSubmenus.findIndex((s) => s.menuItem === itemElement && s.isOpening);
|
|
@@ -165,6 +165,12 @@ const withSubmenu = (config) => (component) => {
|
|
|
165
165
|
submenuElement.setAttribute("tabindex", "-1");
|
|
166
166
|
submenuElement.setAttribute("data-level", currentLevel.toString());
|
|
167
167
|
submenuElement.setAttribute("data-parent-item", item.id);
|
|
168
|
+
let ownerId = component.element.id;
|
|
169
|
+
if (!ownerId) {
|
|
170
|
+
ownerId = `menu-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
|
|
171
|
+
component.element.id = ownerId;
|
|
172
|
+
}
|
|
173
|
+
submenuElement.setAttribute("data-owner", ownerId);
|
|
168
174
|
submenuElement.style.zIndex = `${1000 + currentLevel * 10}`;
|
|
169
175
|
const submenuList = document.createElement("ul");
|
|
170
176
|
submenuList.className = `${component.getClass("menu-list")}`;
|
|
@@ -380,7 +380,7 @@ export interface MenuComponent {
|
|
|
380
380
|
* @param itemId - ID of the menu item to mark as selected
|
|
381
381
|
* @returns The menu component for chaining
|
|
382
382
|
*/
|
|
383
|
-
setSelected: (itemId: string) => MenuComponent;
|
|
383
|
+
setSelected: (itemId: string | null) => MenuComponent;
|
|
384
384
|
/**
|
|
385
385
|
* Gets the currently selected menu item's ID
|
|
386
386
|
* @returns ID of the selected menu item or null if none is selected
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { NAV_CLASSES } from "./constants.js";
|
|
2
|
+
import { setHTML } from "../../core/dom/html.js";
|
|
2
3
|
export const createExpandIcon = (prefix) => {
|
|
3
4
|
const icon = document.createElement('span');
|
|
4
5
|
icon.className = `${prefix}-${NAV_CLASSES.EXPAND_ICON}`;
|
|
5
|
-
icon
|
|
6
|
+
setHTML(icon, `
|
|
6
7
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor">
|
|
7
8
|
<polyline points="9 18 15 12 9 6"></polyline>
|
|
8
9
|
</svg>
|
|
9
|
-
|
|
10
|
+
`);
|
|
10
11
|
return icon;
|
|
11
12
|
};
|
|
12
13
|
export const createNestedContainer = (items, prefix, createItem) => {
|
|
@@ -56,7 +57,7 @@ export const createNavItem = (config, container, prefix) => {
|
|
|
56
57
|
if (config.icon) {
|
|
57
58
|
const icon = document.createElement('span');
|
|
58
59
|
icon.className = `${prefix}-${NAV_CLASSES.ICON}`;
|
|
59
|
-
icon
|
|
60
|
+
setHTML(icon, config.icon);
|
|
60
61
|
itemElement.appendChild(icon);
|
|
61
62
|
}
|
|
62
63
|
if (config.label) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { hasTouchSupport, normalizeEvent, TOUCH_CONFIG, TOUCH_TARGETS } from "../../../core/utils/mobile.js";
|
|
2
|
+
import { setHTML } from "../../../core/dom/html.js";
|
|
2
3
|
export const createOverlay = (state, mobileConfig, hideDrawer) => {
|
|
3
4
|
if (state.overlayElement)
|
|
4
5
|
return state.overlayElement;
|
|
@@ -19,12 +20,12 @@ export const createCloseButton = (state, mobileConfig, hideDrawer) => {
|
|
|
19
20
|
state.closeButtonElement = document.createElement('button');
|
|
20
21
|
state.closeButtonElement.className = mobileConfig.closeButtonClass;
|
|
21
22
|
state.closeButtonElement.setAttribute('aria-label', 'Close navigation');
|
|
22
|
-
state.closeButtonElement
|
|
23
|
+
setHTML(state.closeButtonElement, `
|
|
23
24
|
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
|
24
25
|
<line x1="18" y1="6" x2="6" y2="18"></line>
|
|
25
26
|
<line x1="6" y1="6" x2="18" y2="18"></line>
|
|
26
27
|
</svg>
|
|
27
|
-
|
|
28
|
+
`);
|
|
28
29
|
state.closeButtonElement.addEventListener('click', () => {
|
|
29
30
|
hideDrawer();
|
|
30
31
|
});
|
|
@@ -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
|
}
|