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.
Files changed (176) hide show
  1. package/README.md +20 -0
  2. package/dist/README.md +20 -0
  3. package/dist/components/bottom-app-bar/bottom-app-bar.js +2 -2
  4. package/dist/components/bottom-sheet/features/structure.js +4 -3
  5. package/dist/components/button/api.js +3 -2
  6. package/dist/components/button/features/progress.js +26 -21
  7. package/dist/components/button-group/config.d.ts +11 -3
  8. package/dist/components/button-group/config.js +1 -1
  9. package/dist/components/card/content.js +5 -4
  10. package/dist/components/carousel/features/slides.js +2 -1
  11. package/dist/components/checkbox/checkbox.js +22 -0
  12. package/dist/components/checkbox/config.d.ts +3 -3
  13. package/dist/components/checkbox/config.js +10 -9
  14. package/dist/components/checkbox/types.d.ts +19 -4
  15. package/dist/components/chips/api.js +3 -1
  16. package/dist/components/chips/chip/api.js +3 -2
  17. package/dist/components/chips/chip/chip.js +6 -4
  18. package/dist/components/chips/config.js +1 -1
  19. package/dist/components/datepicker/api.d.ts +8 -2
  20. package/dist/components/datepicker/api.js +1 -0
  21. package/dist/components/datepicker/config.d.ts +14 -4
  22. package/dist/components/datepicker/datepicker.js +5 -2
  23. package/dist/components/datepicker/types.d.ts +14 -0
  24. package/dist/components/datepicker/utils.js +4 -6
  25. package/dist/components/dialog/config.js +0 -1
  26. package/dist/components/dialog/features.js +10 -8
  27. package/dist/components/dialog/types.d.ts +3 -2
  28. package/dist/components/drawer/features/items.js +3 -2
  29. package/dist/components/list/features/renderer.js +4 -3
  30. package/dist/components/menu/api.d.ts +1 -1
  31. package/dist/components/menu/config.js +2 -2
  32. package/dist/components/menu/features/controller.js +41 -26
  33. package/dist/components/menu/features/keyboard.js +7 -5
  34. package/dist/components/menu/features/opener.js +19 -12
  35. package/dist/components/menu/features/position.js +1 -1
  36. package/dist/components/menu/features/submenu.js +7 -1
  37. package/dist/components/menu/types.d.ts +13 -2
  38. package/dist/components/navigation/nav-item.js +4 -3
  39. package/dist/components/navigation/system/mobile.js +3 -2
  40. package/dist/components/navigation-rail/navigation-rail.js +3 -2
  41. package/dist/components/progress/features/canvas.js +0 -2
  42. package/dist/components/search/features/states.js +3 -3
  43. package/dist/components/search/features/structure.js +3 -0
  44. package/dist/components/search/features/suggestions.js +2 -2
  45. package/dist/components/search/types.d.ts +6 -0
  46. package/dist/components/segmented-button/config.d.ts +4 -1
  47. package/dist/components/segmented-button/config.js +1 -1
  48. package/dist/components/segmented-button/segment.d.ts +2 -5
  49. package/dist/components/segmented-button/segment.js +7 -3
  50. package/dist/components/select/features.js +267 -44
  51. package/dist/components/side-sheet/features/structure.js +5 -4
  52. package/dist/components/slider/features/controller.js +7 -0
  53. package/dist/components/slider/features/dom.d.ts +1 -0
  54. package/dist/components/slider/features/dom.js +11 -0
  55. package/dist/components/slider/features/handlers.js +8 -3
  56. package/dist/components/slider/features/states.js +8 -0
  57. package/dist/components/slider/types.d.ts +7 -0
  58. package/dist/components/switch/config.d.ts +3 -3
  59. package/dist/components/switch/config.js +7 -7
  60. package/dist/components/switch/features.js +5 -5
  61. package/dist/components/switch/switch.js +1 -1
  62. package/dist/components/switch/types.d.ts +10 -0
  63. package/dist/components/tabs/features.d.ts +1 -0
  64. package/dist/components/tabs/scroll-indicators.js +3 -2
  65. package/dist/components/tabs/tab.d.ts +0 -5
  66. package/dist/components/tabs/tab.js +12 -3
  67. package/dist/components/tabs/tabs.js +10 -1
  68. package/dist/components/tabs/utils.d.ts +8 -0
  69. package/dist/components/tabs/utils.js +63 -18
  70. package/dist/components/textfield/api.js +12 -4
  71. package/dist/components/textfield/features/error.d.ts +1 -0
  72. package/dist/components/textfield/features/error.js +10 -0
  73. package/dist/components/textfield/features/leading-icon.js +32 -33
  74. package/dist/components/textfield/features/prefix-text.js +30 -18
  75. package/dist/components/textfield/features/suffix-text.js +30 -14
  76. package/dist/components/textfield/features/supporting-text.d.ts +1 -0
  77. package/dist/components/textfield/features/supporting-text.js +25 -0
  78. package/dist/components/textfield/features/trailing-icon.js +36 -19
  79. package/dist/components/timepicker/api.d.ts +1 -1
  80. package/dist/components/timepicker/api.js +16 -11
  81. package/dist/components/timepicker/render.js +27 -24
  82. package/dist/components/timepicker/timepicker.js +4 -2
  83. package/dist/components/timepicker/types.d.ts +7 -0
  84. package/dist/components/timepicker/utils.d.ts +14 -0
  85. package/dist/components/timepicker/utils.js +4 -0
  86. package/dist/components/tooltip/api.js +27 -2
  87. package/dist/components/top-app-bar/top-app-bar.js +3 -2
  88. package/dist/core/compose/component.d.ts +1 -1
  89. package/dist/core/compose/component.js +1 -1
  90. package/dist/core/compose/features/icon.js +3 -2
  91. package/dist/core/compose/features/input.js +1 -1
  92. package/dist/core/compose/features/lifecycle.js +10 -11
  93. package/dist/core/compose/features/textlabel.js +3 -0
  94. package/dist/core/compose/features/track.js +2 -1
  95. package/dist/core/config/component.d.ts +1 -1
  96. package/dist/core/config/component.js +1 -1
  97. package/dist/core/dom/create.js +5 -4
  98. package/dist/core/dom/form-value.d.ts +24 -0
  99. package/dist/core/dom/form-value.js +14 -0
  100. package/dist/core/dom/html.d.ts +34 -0
  101. package/dist/core/dom/html.js +13 -0
  102. package/dist/core/dom/index.d.ts +2 -0
  103. package/dist/core/dom/index.js +1 -0
  104. package/dist/core/dom/utils.js +1 -1
  105. package/dist/core/state/emitter.js +8 -1
  106. package/dist/core/state/index.d.ts +1 -1
  107. package/dist/core/state/store.d.ts +10 -2
  108. package/dist/core/state/store.js +2 -1
  109. package/dist/index.cjs +0 -0
  110. package/dist/package.json +5 -2
  111. package/dist/styles/badge.css +1 -1
  112. package/dist/styles/base.css +2 -2
  113. package/dist/styles/bottom-app-bar.css +1 -1
  114. package/dist/styles/bottom-sheet.css +2 -2
  115. package/dist/styles/button-group.css +1 -1
  116. package/dist/styles/button.css +2 -2
  117. package/dist/styles/card.css +2 -2
  118. package/dist/styles/carousel.css +1 -1
  119. package/dist/styles/checkbox.css +2 -2
  120. package/dist/styles/chips.css +2 -2
  121. package/dist/styles/dialog.css +2 -2
  122. package/dist/styles/divider.css +1 -1
  123. package/dist/styles/drawer.css +2 -2
  124. package/dist/styles/extended-fab.css +2 -2
  125. package/dist/styles/fab.css +2 -2
  126. package/dist/styles/icon-button.css +2 -2
  127. package/dist/styles/list.css +2 -2
  128. package/dist/styles/loading-indicator.css +1 -1
  129. package/dist/styles/menu.css +2 -2
  130. package/dist/styles/navigation-rail.css +1 -1
  131. package/dist/styles/navigation.css +2 -2
  132. package/dist/styles/progress.css +2 -2
  133. package/dist/styles/radios.css +1 -1
  134. package/dist/styles/search.css +1 -1
  135. package/dist/styles/segmented-button.css +2 -2
  136. package/dist/styles/select.css +2 -2
  137. package/dist/styles/side-sheet.css +2 -2
  138. package/dist/styles/slider.css +1 -1
  139. package/dist/styles/snackbar.css +1 -1
  140. package/dist/styles/split-button.css +2 -2
  141. package/dist/styles/switch.css +2 -2
  142. package/dist/styles/tabs.css +1 -1
  143. package/dist/styles/textfield.css +2 -2
  144. package/dist/styles/timepicker.css +1 -1
  145. package/dist/styles/tooltip.css +2 -2
  146. package/dist/styles/top-app-bar.css +1 -1
  147. package/dist/styles/utilities.css +1 -1
  148. package/dist/styles.css +2 -2
  149. package/dist/styles.d.ts +1 -0
  150. package/dist/themes/autumn.css +1 -1
  151. package/dist/themes/baseline.css +1 -1
  152. package/dist/themes/brownbeige.css +1 -1
  153. package/dist/themes/browngreen.css +1 -1
  154. package/dist/themes/desert.css +1 -1
  155. package/dist/themes/forest.css +1 -1
  156. package/dist/themes/highcontrast.css +1 -1
  157. package/dist/themes/legacy.css +1 -1
  158. package/dist/themes/material.css +1 -1
  159. package/dist/themes/ocean.css +1 -1
  160. package/dist/themes/sageivory.css +1 -1
  161. package/dist/themes/spring.css +1 -1
  162. package/dist/themes/summer.css +1 -1
  163. package/dist/themes/sunset.css +1 -1
  164. package/dist/themes/tealcaramel.css +1 -1
  165. package/dist/themes/winter.css +1 -1
  166. package/package.json +7 -3
  167. package/src/styles/abstract/_mixins.scss +26 -0
  168. package/src/styles/abstract/_variables.scss +25 -11
  169. package/src/styles/base/_reset.scss +9 -2
  170. package/src/styles/components/_bottom-sheet.scss +23 -7
  171. package/src/styles/components/_button.scss +1 -12
  172. package/src/styles/components/_dialog.scss +59 -26
  173. package/src/styles/components/_drawer.scss +60 -7
  174. package/src/styles/components/_menu.scss +12 -7
  175. package/src/styles/components/_side-sheet.scss +33 -4
  176. package/src/styles/components/_tooltip.scss +3 -0
@@ -1,35 +1,47 @@
1
1
  export const withPrefixText = (config) => (component) => {
2
- if (!config.prefixText) {
3
- return component;
4
- }
5
2
  const PREFIX = config.prefix || 'mtrl';
6
- const prefixElement = document.createElement('span');
7
- prefixElement.className = `${PREFIX}-${config.componentName || 'textfield'}-prefix`;
8
- prefixElement.textContent = config.prefixText;
9
- component.element.appendChild(prefixElement);
10
- component.element.classList.add(`${PREFIX}-${config.componentName || 'textfield'}--with-prefix`);
3
+ const NAME = config.componentName || 'textfield';
4
+ let slot = null;
5
+ const ensureSlot = () => {
6
+ if (slot && slot.parentNode)
7
+ return slot;
8
+ const element = document.createElement('span');
9
+ element.className = `${PREFIX}-${NAME}-prefix`;
10
+ component.element.appendChild(element);
11
+ component.element.classList.add(`${PREFIX}-${NAME}--with-prefix`);
12
+ slot = element;
13
+ return element;
14
+ };
15
+ const detachSlot = () => {
16
+ slot?.remove();
17
+ slot = null;
18
+ component.element.classList.remove(`${PREFIX}-${NAME}--with-prefix`);
19
+ };
20
+ const write = (element, value) => {
21
+ element.textContent = value;
22
+ };
23
+ if (config.prefixText) {
24
+ write(ensureSlot(), config.prefixText);
25
+ }
11
26
  if ('lifecycle' in component && component.lifecycle?.destroy) {
12
27
  const originalDestroy = component.lifecycle.destroy;
13
28
  component.lifecycle.destroy = () => {
14
- prefixElement.remove();
29
+ slot?.remove();
30
+ slot = null;
15
31
  originalDestroy.call(component.lifecycle);
16
32
  };
17
33
  }
18
34
  return {
19
35
  ...component,
20
- prefixTextElement: prefixElement,
36
+ prefixTextElement: slot,
21
37
  setPrefixText(text) {
22
- prefixElement.textContent = text;
38
+ this.prefixTextElement = ensureSlot();
39
+ write(this.prefixTextElement, text);
23
40
  return this;
24
41
  },
25
42
  removePrefixText() {
26
- if (prefixElement.parentNode) {
27
- prefixElement.remove();
28
- component.element.classList.remove(`${PREFIX}-${config.componentName || 'textfield'}--with-prefix`);
29
- const labelEl = component.element.querySelector(`.${PREFIX}-${config.componentName || 'textfield'}-label`);
30
- if (labelEl) {
31
- labelEl.style.left = '';
32
- }
43
+ if (slot) {
44
+ detachSlot();
33
45
  this.prefixTextElement = null;
34
46
  }
35
47
  return this;
@@ -1,31 +1,47 @@
1
1
  export const withSuffixText = (config) => (component) => {
2
- if (!config.suffixText) {
3
- return component;
4
- }
5
2
  const PREFIX = config.prefix || 'mtrl';
6
- const suffixElement = document.createElement('span');
7
- suffixElement.className = `${PREFIX}-${config.componentName || 'textfield'}-suffix`;
8
- suffixElement.textContent = config.suffixText;
9
- component.element.appendChild(suffixElement);
10
- component.element.classList.add(`${PREFIX}-${config.componentName || 'textfield'}--with-suffix`);
3
+ const NAME = config.componentName || 'textfield';
4
+ let slot = null;
5
+ const ensureSlot = () => {
6
+ if (slot && slot.parentNode)
7
+ return slot;
8
+ const element = document.createElement('span');
9
+ element.className = `${PREFIX}-${NAME}-suffix`;
10
+ component.element.appendChild(element);
11
+ component.element.classList.add(`${PREFIX}-${NAME}--with-suffix`);
12
+ slot = element;
13
+ return element;
14
+ };
15
+ const detachSlot = () => {
16
+ slot?.remove();
17
+ slot = null;
18
+ component.element.classList.remove(`${PREFIX}-${NAME}--with-suffix`);
19
+ };
20
+ const write = (element, value) => {
21
+ element.textContent = value;
22
+ };
23
+ if (config.suffixText) {
24
+ write(ensureSlot(), config.suffixText);
25
+ }
11
26
  if ('lifecycle' in component && component.lifecycle?.destroy) {
12
27
  const originalDestroy = component.lifecycle.destroy;
13
28
  component.lifecycle.destroy = () => {
14
- suffixElement.remove();
29
+ slot?.remove();
30
+ slot = null;
15
31
  originalDestroy.call(component.lifecycle);
16
32
  };
17
33
  }
18
34
  return {
19
35
  ...component,
20
- suffixTextElement: suffixElement,
36
+ suffixTextElement: slot,
21
37
  setSuffixText(text) {
22
- suffixElement.textContent = text;
38
+ this.suffixTextElement = ensureSlot();
39
+ write(this.suffixTextElement, text);
23
40
  return this;
24
41
  },
25
42
  removeSuffixText() {
26
- if (suffixElement.parentNode) {
27
- suffixElement.remove();
28
- component.element.classList.remove(`${PREFIX}-${config.componentName || 'textfield'}--with-suffix`);
43
+ if (slot) {
44
+ detachSlot();
29
45
  this.suffixTextElement = null;
30
46
  }
31
47
  return this;
@@ -3,6 +3,7 @@ import { BaseComponent, ElementComponent } from "../../../core/compose/component
3
3
  * Extended element component with lifecycle
4
4
  */
5
5
  interface LifecycleElementComponent extends ElementComponent {
6
+ input?: HTMLInputElement | HTMLTextAreaElement;
6
7
  lifecycle?: {
7
8
  destroy: () => void;
8
9
  };
@@ -1,10 +1,32 @@
1
+ const addIdRef = (element, attribute, id) => {
2
+ const ids = (element.getAttribute(attribute) || "").split(/\s+/).filter(Boolean);
3
+ if (!ids.includes(id))
4
+ element.setAttribute(attribute, [...ids, id].join(" "));
5
+ };
6
+ const removeIdRef = (element, attribute, id) => {
7
+ const ids = (element.getAttribute(attribute) || "").split(/\s+/).filter((ref) => ref && ref !== id);
8
+ if (ids.length)
9
+ element.setAttribute(attribute, ids.join(" "));
10
+ else
11
+ element.removeAttribute(attribute);
12
+ };
1
13
  export const withSupportingText = (config) => (component) => {
2
14
  const PREFIX = config.prefix || "mtrl";
3
15
  const COMPONENT = config.componentName || "textfield";
4
16
  let supportingElement = null;
17
+ const supportingId = `${PREFIX}-${COMPONENT}-supporting-${Math.random().toString(36).slice(2, 9)}`;
18
+ const describe = (element) => {
19
+ if (!component.input)
20
+ return;
21
+ if (element)
22
+ addIdRef(component.input, "aria-describedby", supportingId);
23
+ else
24
+ removeIdRef(component.input, "aria-describedby", supportingId);
25
+ };
5
26
  const createSupportingElement = (text, isError = false) => {
6
27
  const element = document.createElement("div");
7
28
  element.className = `${PREFIX}-${COMPONENT}-helper`;
29
+ element.id = supportingId;
8
30
  element.textContent = text;
9
31
  if (isError) {
10
32
  element.classList.add(`${PREFIX}-${COMPONENT}-helper--error`);
@@ -15,6 +37,7 @@ export const withSupportingText = (config) => (component) => {
15
37
  if (config.supportingText) {
16
38
  supportingElement = createSupportingElement(config.supportingText, config.error);
17
39
  component.element.appendChild(supportingElement);
40
+ describe(supportingElement);
18
41
  }
19
42
  if ("lifecycle" in component && component.lifecycle?.destroy) {
20
43
  const originalDestroy = component.lifecycle.destroy;
@@ -43,6 +66,7 @@ export const withSupportingText = (config) => (component) => {
43
66
  this.supportingTextElement = null;
44
67
  component.element.classList.remove(`${PREFIX}-${COMPONENT}--error`);
45
68
  }
69
+ describe(supportingElement);
46
70
  return this;
47
71
  },
48
72
  removeSupportingText() {
@@ -51,6 +75,7 @@ export const withSupportingText = (config) => (component) => {
51
75
  supportingElement = null;
52
76
  this.supportingTextElement = null;
53
77
  component.element.classList.remove(`${PREFIX}-${COMPONENT}--error`);
78
+ describe(null);
54
79
  }
55
80
  return this;
56
81
  },
@@ -1,37 +1,54 @@
1
+ import { setHTML } from "../../../core/dom/html.js";
1
2
  export const withTrailingIcon = (config) => (component) => {
2
- if (!config.trailingIcon) {
3
- return component;
4
- }
5
3
  const PREFIX = config.prefix || 'mtrl';
6
- const iconElement = document.createElement('span');
7
- iconElement.className = `${PREFIX}-${config.componentName || 'textfield'}-trailing-icon`;
8
- iconElement.innerHTML = config.trailingIcon;
9
- component.element.appendChild(iconElement);
10
- component.element.classList.add(`${PREFIX}-${config.componentName || 'textfield'}--with-trailing-icon`);
11
- if (component.input) {
12
- component.input.classList.add(`${PREFIX}-${config.componentName || 'textfield'}-input--with-trailing-icon`);
4
+ const NAME = config.componentName || 'textfield';
5
+ let slot = null;
6
+ const ensureSlot = () => {
7
+ if (slot && slot.parentNode)
8
+ return slot;
9
+ const element = document.createElement('span');
10
+ element.className = `${PREFIX}-${NAME}-trailing-icon`;
11
+ component.element.appendChild(element);
12
+ component.element.classList.add(`${PREFIX}-${NAME}--with-trailing-icon`);
13
+ if (component.input) {
14
+ component.input.classList.add(`${PREFIX}-${NAME}-input--with-trailing-icon`);
15
+ }
16
+ slot = element;
17
+ return element;
18
+ };
19
+ const detachSlot = () => {
20
+ slot?.remove();
21
+ slot = null;
22
+ component.element.classList.remove(`${PREFIX}-${NAME}--with-trailing-icon`);
23
+ if (component.input) {
24
+ component.input.classList.remove(`${PREFIX}-${NAME}-input--with-trailing-icon`);
25
+ }
26
+ };
27
+ const write = (element, value) => {
28
+ setHTML(element, value);
29
+ };
30
+ if (config.trailingIcon) {
31
+ write(ensureSlot(), config.trailingIcon);
13
32
  }
14
33
  if ('lifecycle' in component && component.lifecycle?.destroy) {
15
34
  const originalDestroy = component.lifecycle.destroy;
16
35
  component.lifecycle.destroy = () => {
17
- iconElement.remove();
36
+ slot?.remove();
37
+ slot = null;
18
38
  originalDestroy.call(component.lifecycle);
19
39
  };
20
40
  }
21
41
  return {
22
42
  ...component,
23
- trailingIcon: iconElement,
43
+ trailingIcon: slot,
24
44
  setTrailingIcon(html) {
25
- iconElement.innerHTML = html;
45
+ this.trailingIcon = ensureSlot();
46
+ write(this.trailingIcon, html);
26
47
  return this;
27
48
  },
28
49
  removeTrailingIcon() {
29
- if (iconElement.parentNode) {
30
- iconElement.remove();
31
- component.element.classList.remove(`${PREFIX}-${config.componentName || 'textfield'}--with-trailing-icon`);
32
- if (component.input) {
33
- component.input.classList.remove(`${PREFIX}-${config.componentName || 'textfield'}-input--with-trailing-icon`);
34
- }
50
+ if (slot) {
51
+ detachSlot();
35
52
  this.trailingIcon = null;
36
53
  }
37
54
  return this;
@@ -21,5 +21,5 @@ interface ApiOptions {
21
21
  * @param options - API options with events and lifecycle methods
22
22
  * @returns TimePicker component API
23
23
  */
24
- export declare const createTimePickerAPI: (baseComponent: ElementComponent, modalElement: HTMLElement, dialogElement: HTMLElement, timeValue: TimeValue, config: TimePickerConfig, options: ApiOptions) => TimePickerComponent;
24
+ export declare const createTimePickerAPI: (baseComponent: ElementComponent, modalElement: HTMLElement, dialogElement: HTMLElement, timeValue: TimeValue, config: TimePickerConfig, options: ApiOptions, formValue?: HTMLInputElement | null) => TimePickerComponent;
25
25
  export {};
@@ -1,9 +1,14 @@
1
1
  import { TIME_PICKER_TYPE, TIME_PICKER_ORIENTATION, TIME_FORMAT, TIME_PERIOD } from "./types.js";
2
2
  import { TIMEPICKER_EVENTS as EVENTS, TIMEPICKER_SELECTORS as SELECTORS } from "./constants.js";
3
- import { formatTime, padZero } from "./utils.js";
3
+ import { formatTime, padZero, formatFormValue } from "./utils.js";
4
4
  import { renderTimePicker } from "./render.js";
5
5
  import { renderClockDial, getTimeValueFromClick } from "./clockdial.js";
6
- export const createTimePickerAPI = (baseComponent, modalElement, dialogElement, timeValue, config, options) => {
6
+ import { setFormValue } from "../../core/dom/form-value.js";
7
+ export const createTimePickerAPI = (baseComponent, modalElement, dialogElement, timeValue, config, options, formValue = null) => {
8
+ const render = () => {
9
+ renderTimePicker(dialogElement, timeValue, config);
10
+ setFormValue(formValue, formatFormValue(timeValue, config.showSeconds === true));
11
+ };
7
12
  let isOpen = !!config.isOpen;
8
13
  const handleKeydown = (event) => {
9
14
  if (event.key === 'Escape') {
@@ -33,7 +38,7 @@ export const createTimePickerAPI = (baseComponent, modalElement, dialogElement,
33
38
  isOpen = true;
34
39
  baseComponent.element.classList.add(`${config.prefix}-time-picker--open`);
35
40
  setTimeout(() => {
36
- renderTimePicker(dialogElement, timeValue, config);
41
+ render();
37
42
  }, 50);
38
43
  options.events.emit(EVENTS.OPEN);
39
44
  if (config.onOpen) {
@@ -83,7 +88,7 @@ export const createTimePickerAPI = (baseComponent, modalElement, dialogElement,
83
88
  timeValue.minutes = minutes;
84
89
  timeValue.seconds = seconds;
85
90
  timeValue.period = hours >= 12 ? TIME_PERIOD.PM : TIME_PERIOD.AM;
86
- renderTimePicker(dialogElement, timeValue, config);
91
+ render();
87
92
  options.events.emit(EVENTS.CHANGE, this.getValue());
88
93
  if (config.onChange) {
89
94
  config.onChange(this.getValue());
@@ -100,7 +105,7 @@ export const createTimePickerAPI = (baseComponent, modalElement, dialogElement,
100
105
  config.type = type;
101
106
  dialogElement.classList.remove(`${config.prefix}-time-picker-dialog--${TIME_PICKER_TYPE.DIAL}`, `${config.prefix}-time-picker-dialog--${TIME_PICKER_TYPE.INPUT}`);
102
107
  dialogElement.classList.add(`${config.prefix}-time-picker-dialog--${type}`);
103
- renderTimePicker(dialogElement, timeValue, config);
108
+ render();
104
109
  return this;
105
110
  },
106
111
  getType() {
@@ -118,7 +123,7 @@ export const createTimePickerAPI = (baseComponent, modalElement, dialogElement,
118
123
  else {
119
124
  timeValue.period = timeValue.hours >= 12 ? TIME_PERIOD.PM : TIME_PERIOD.AM;
120
125
  }
121
- renderTimePicker(dialogElement, timeValue, config);
126
+ render();
122
127
  options.events.emit(EVENTS.CHANGE, this.getValue());
123
128
  return this;
124
129
  },
@@ -131,7 +136,7 @@ export const createTimePickerAPI = (baseComponent, modalElement, dialogElement,
131
136
  config.orientation = orientation;
132
137
  dialogElement.classList.remove(`${config.prefix}-time-picker-dialog--${TIME_PICKER_ORIENTATION.VERTICAL}`, `${config.prefix}-time-picker-dialog--${TIME_PICKER_ORIENTATION.HORIZONTAL}`);
133
138
  dialogElement.classList.add(`${config.prefix}-time-picker-dialog--${orientation}`);
134
- renderTimePicker(dialogElement, timeValue, config);
139
+ render();
135
140
  return this;
136
141
  },
137
142
  getOrientation() {
@@ -144,7 +149,7 @@ export const createTimePickerAPI = (baseComponent, modalElement, dialogElement,
144
149
  titleElement.textContent = title;
145
150
  }
146
151
  else {
147
- renderTimePicker(dialogElement, timeValue, config);
152
+ render();
148
153
  }
149
154
  return this;
150
155
  },
@@ -197,7 +202,7 @@ export const createTimePickerAPI = (baseComponent, modalElement, dialogElement,
197
202
  if (timeValue.hours >= 12) {
198
203
  timeValue.hours -= 12;
199
204
  }
200
- renderTimePicker(dialogElement, timeValue, config);
205
+ render();
201
206
  options.events.emit(EVENTS.CHANGE, timePickerAPI.getValue());
202
207
  if (config.onChange) {
203
208
  config.onChange(timePickerAPI.getValue());
@@ -210,7 +215,7 @@ export const createTimePickerAPI = (baseComponent, modalElement, dialogElement,
210
215
  if (timeValue.hours < 12) {
211
216
  timeValue.hours += 12;
212
217
  }
213
- renderTimePicker(dialogElement, timeValue, config);
218
+ render();
214
219
  options.events.emit(EVENTS.CHANGE, timePickerAPI.getValue());
215
220
  if (config.onChange) {
216
221
  config.onChange(timePickerAPI.getValue());
@@ -372,7 +377,7 @@ export const createTimePickerAPI = (baseComponent, modalElement, dialogElement,
372
377
  newSeconds = 59;
373
378
  timeValue.seconds = newSeconds;
374
379
  }
375
- renderTimePicker(dialogElement, timeValue, config);
380
+ render();
376
381
  options.events.emit(EVENTS.CHANGE, timePickerAPI.getValue());
377
382
  if (config.onChange) {
378
383
  config.onChange(timePickerAPI.getValue());
@@ -2,8 +2,9 @@ import { TIME_PICKER_TYPE, TIME_FORMAT, TIME_PERIOD, } from "./types.js";
2
2
  import { TIMEPICKER_DIAL, TIMEPICKER_ICONS } from "./constants.js";
3
3
  import { padZero, convertTo12Hour } from "./utils.js";
4
4
  import { renderClockDial, getTimeValueFromClick } from "./clockdial.js";
5
+ import { setHTML } from "../../core/dom/html.js";
5
6
  export const renderTimePicker = (container, timeValue, config, onTimeChange) => {
6
- container.innerHTML = "";
7
+ container.replaceChildren();
7
8
  if (config.title) {
8
9
  const title = document.createElement("div");
9
10
  title.className = `${config.prefix}-time-picker-title`;
@@ -117,10 +118,9 @@ export const renderTimePicker = (container, timeValue, config, onTimeChange) =>
117
118
  toggleTypeButton.setAttribute("aria-label", config.type === TIME_PICKER_TYPE.DIAL
118
119
  ? "Switch to keyboard input"
119
120
  : "Switch to dial selector");
120
- toggleTypeButton.innerHTML =
121
- config.type === TIME_PICKER_TYPE.DIAL
122
- ? config.keyboardIcon || TIMEPICKER_ICONS.KEYBOARD
123
- : config.clockIcon || TIMEPICKER_ICONS.CLOCK;
121
+ setHTML(toggleTypeButton, config.type === TIME_PICKER_TYPE.DIAL
122
+ ? config.keyboardIcon || TIMEPICKER_ICONS.KEYBOARD
123
+ : config.clockIcon || TIMEPICKER_ICONS.CLOCK);
124
124
  actions.appendChild(toggleTypeButton);
125
125
  const actionButtons = document.createElement("div");
126
126
  actionButtons.className = `${config.prefix}-time-picker-action-buttons`;
@@ -150,16 +150,15 @@ export const renderTimePicker = (container, timeValue, config, onTimeChange) =>
150
150
  toggleTypeButton.addEventListener("click", () => {
151
151
  if (dialContainer.style.display === "none") {
152
152
  dialContainer.style.display = "block";
153
- toggleTypeButton.innerHTML =
154
- config.keyboardIcon || TIMEPICKER_ICONS.KEYBOARD;
153
+ setHTML(toggleTypeButton, config.keyboardIcon || TIMEPICKER_ICONS.KEYBOARD);
155
154
  toggleTypeButton.setAttribute("aria-label", "Switch to keyboard input");
156
155
  setTimeout(() => {
157
156
  canvas.focus();
158
- const hourElements = document.querySelectorAll(`.${config.prefix}-time-picker-hours`);
157
+ const hourElements = container.querySelectorAll(`.${config.prefix}-time-picker-hours`);
159
158
  hourElements.forEach((el) => el.setAttribute("data-active", "true"));
160
- const minuteElements = document.querySelectorAll(`.${config.prefix}-time-picker-minutes`);
159
+ const minuteElements = container.querySelectorAll(`.${config.prefix}-time-picker-minutes`);
161
160
  minuteElements.forEach((el) => el.setAttribute("data-active", "false"));
162
- const secondElements = document.querySelectorAll(`.${config.prefix}-time-picker-seconds`);
161
+ const secondElements = container.querySelectorAll(`.${config.prefix}-time-picker-seconds`);
163
162
  secondElements.forEach((el) => el.setAttribute("data-active", "false"));
164
163
  renderClockDial(canvas, timeValue, {
165
164
  type: TIME_PICKER_TYPE.DIAL,
@@ -172,7 +171,7 @@ export const renderTimePicker = (container, timeValue, config, onTimeChange) =>
172
171
  }
173
172
  else {
174
173
  dialContainer.style.display = "none";
175
- toggleTypeButton.innerHTML = config.clockIcon || TIMEPICKER_ICONS.CLOCK;
174
+ setHTML(toggleTypeButton, config.clockIcon || TIMEPICKER_ICONS.CLOCK);
176
175
  toggleTypeButton.setAttribute("aria-label", "Switch to dial selector");
177
176
  setTimeout(() => {
178
177
  hoursInput.focus();
@@ -324,13 +323,13 @@ export const renderTimePicker = (container, timeValue, config, onTimeChange) =>
324
323
  : timeValue.hours;
325
324
  hoursInput.value = padZero(displayHours);
326
325
  }
327
- document
326
+ container
328
327
  .querySelectorAll(`.${config.prefix}-time-picker-period-am, .${config.prefix}-time-picker-period-pm`)
329
328
  .forEach((el) => {
330
329
  el.classList.remove(`${config.prefix}-time-picker-period--selected`);
331
330
  el.setAttribute("aria-pressed", "false");
332
331
  });
333
- const selectedPeriod = document.querySelector(`.${config.prefix}-time-picker-period-${period.toLowerCase()}`);
332
+ const selectedPeriod = container.querySelector(`.${config.prefix}-time-picker-period-${period.toLowerCase()}`);
334
333
  if (selectedPeriod) {
335
334
  selectedPeriod.classList.add(`${config.prefix}-time-picker-period--selected`);
336
335
  selectedPeriod.setAttribute("aria-pressed", "true");
@@ -350,18 +349,22 @@ export const renderTimePicker = (container, timeValue, config, onTimeChange) =>
350
349
  }
351
350
  };
352
351
  if (config.format === TIME_FORMAT.AMPM) {
353
- const amPeriodElement = document.querySelector(`.${config.prefix}-time-picker-period-am`);
354
- const pmPeriodElement = document.querySelector(`.${config.prefix}-time-picker-period-pm`);
355
- if (amPeriodElement) {
356
- amPeriodElement.addEventListener("click", () => {
357
- handlePeriodChange(TIME_PERIOD.AM);
352
+ const amPeriodElement = container.querySelector(`.${config.prefix}-time-picker-period-am`);
353
+ const pmPeriodElement = container.querySelector(`.${config.prefix}-time-picker-period-pm`);
354
+ const activateOn = (element, period) => {
355
+ if (!element)
356
+ return;
357
+ element.addEventListener("click", () => handlePeriodChange(period));
358
+ element.addEventListener("keydown", (event) => {
359
+ const key = event.key;
360
+ if (key !== "Enter" && key !== " " && key !== "Spacebar")
361
+ return;
362
+ event.preventDefault();
363
+ handlePeriodChange(period);
358
364
  });
359
- }
360
- if (pmPeriodElement) {
361
- pmPeriodElement.addEventListener("click", () => {
362
- handlePeriodChange(TIME_PERIOD.PM);
363
- });
364
- }
365
+ };
366
+ activateOn(amPeriodElement, TIME_PERIOD.AM);
367
+ activateOn(pmPeriodElement, TIME_PERIOD.PM);
365
368
  }
366
369
  if (config.type === TIME_PICKER_TYPE.DIAL ||
367
370
  dialContainer.style.display === "block") {
@@ -7,7 +7,8 @@ import { createBaseConfig, getContainerConfig, getApiConfig } from "./config.js"
7
7
  import { TIMEPICKER_Z_INDEX as Z_INDEX } from "./constants.js";
8
8
  import { createTimePickerAPI } from "./api.js";
9
9
  import { renderTimePicker } from "./render.js";
10
- import { parseTime } from "./utils.js";
10
+ import { parseTime, formatFormValue } from "./utils.js";
11
+ import { createFormValue } from "../../core/dom/form-value.js";
11
12
  const createTimePicker = (config = {}) => {
12
13
  const baseConfig = createBaseConfig(config);
13
14
  try {
@@ -54,7 +55,8 @@ const createTimePicker = (config = {}) => {
54
55
  };
55
56
  }
56
57
  renderTimePicker(dialogElement, timeValue, baseConfig);
57
- const timePicker = createTimePickerAPI(baseComponent, modalElement, dialogElement, timeValue, baseConfig, getApiConfig(baseComponent));
58
+ const formValue = createFormValue(baseComponent.element, baseConfig.name, formatFormValue(timeValue, baseConfig.showSeconds === true));
59
+ const timePicker = createTimePickerAPI(baseComponent, modalElement, dialogElement, timeValue, baseConfig, getApiConfig(baseComponent), formValue);
58
60
  if (baseConfig.isOpen) {
59
61
  setTimeout(() => timePicker.open(), 0);
60
62
  }
@@ -48,6 +48,13 @@ export interface TimePickerConfig {
48
48
  * @example '14:30'
49
49
  */
50
50
  value?: string;
51
+ /**
52
+ * Form field name. A time picker renders no form control of its own, so it
53
+ * submits nothing unless this is set; with it, a hidden input on the
54
+ * picker's element carries the current time as 24-hour `HH:mm` — or
55
+ * `HH:mm:ss` when `showSeconds` is on — whatever format the picker displays.
56
+ */
57
+ name?: string;
51
58
  /**
52
59
  * Type of time picker to display
53
60
  * @default TIME_PICKER_TYPE.DIAL
@@ -72,3 +72,17 @@ export declare const getTimeFromPosition: (centerX: number, centerY: number, cli
72
72
  * @returns {boolean} Whether the time is within constraints
73
73
  */
74
74
  export declare const isTimeWithinConstraints: (time: TimeValue, minTime?: string, maxTime?: string) => boolean;
75
+ /**
76
+ * Formats a time for submission in a form: 24-hour `HH:mm`, or `HH:mm:ss`
77
+ * when the picker shows seconds.
78
+ *
79
+ * Separate from `formatTime` on purpose. What a form submits should not change
80
+ * because the picker happens to display 12-hour time, and `formatTime` appends
81
+ * seconds whenever the value carries them — which it always does, since the
82
+ * picker stores 0 when seconds are hidden.
83
+ *
84
+ * @param timeValue - Current time value, whose hours are 24-hour
85
+ * @param showSeconds - Whether the picker shows seconds
86
+ * @returns The value to submit
87
+ */
88
+ export declare const formatFormValue: (timeValue: TimeValue, showSeconds: boolean) => string;
@@ -129,3 +129,7 @@ export const isTimeWithinConstraints = (time, minTime, maxTime) => {
129
129
  }
130
130
  return true;
131
131
  };
132
+ export const formatFormValue = (timeValue, showSeconds) => {
133
+ const base = `${padZero(timeValue.hours)}:${padZero(timeValue.minutes)}`;
134
+ return showSeconds ? `${base}:${padZero(timeValue.seconds ?? 0)}` : base;
135
+ };
@@ -159,6 +159,20 @@ export const withAPI = ({ lifecycle }) => (component) => {
159
159
  };
160
160
  const handleTargetMouseEnter = () => api.show();
161
161
  const handleTargetMouseLeave = () => api.hide();
162
+ const handleTooltipMouseEnter = () => {
163
+ if (isVisible && hideTimer !== null)
164
+ api.show(true);
165
+ };
166
+ const handleTooltipMouseLeave = () => {
167
+ if (isVisible)
168
+ api.hide();
169
+ };
170
+ component.element.addEventListener("mouseenter", handleTooltipMouseEnter);
171
+ component.element.addEventListener("mouseleave", handleTooltipMouseLeave);
172
+ const handleDocumentKeydown = (event) => {
173
+ if (event.key === "Escape" && isVisible)
174
+ api.hide(true);
175
+ };
162
176
  const handleTargetFocus = () => api.show();
163
177
  const handleTargetBlur = () => api.hide();
164
178
  const api = {
@@ -168,10 +182,16 @@ export const withAPI = ({ lifecycle }) => (component) => {
168
182
  getClass: component.getClass,
169
183
  setText(text) {
170
184
  const contentNode = document.createTextNode(text);
171
- while (component.element.firstChild !== arrowElement) {
185
+ while (component.element.firstChild &&
186
+ component.element.firstChild !== arrowElement) {
172
187
  component.element.removeChild(component.element.firstChild);
173
188
  }
174
- component.element.insertBefore(contentNode, arrowElement);
189
+ if (arrowElement.parentNode === component.element) {
190
+ component.element.insertBefore(contentNode, arrowElement);
191
+ }
192
+ else {
193
+ component.element.appendChild(contentNode);
194
+ }
175
195
  if (isVisible) {
176
196
  this.updatePosition();
177
197
  }
@@ -231,6 +251,7 @@ export const withAPI = ({ lifecycle }) => (component) => {
231
251
  this.updatePosition();
232
252
  window.addEventListener("resize", handleWindowResize);
233
253
  window.addEventListener("scroll", handleWindowScroll);
254
+ document.addEventListener("keydown", handleDocumentKeydown);
234
255
  };
235
256
  if (immediate) {
236
257
  showTooltip();
@@ -255,6 +276,7 @@ export const withAPI = ({ lifecycle }) => (component) => {
255
276
  isVisible = false;
256
277
  window.removeEventListener("resize", handleWindowResize);
257
278
  window.removeEventListener("scroll", handleWindowScroll);
279
+ document.removeEventListener("keydown", handleDocumentKeydown);
258
280
  };
259
281
  if (immediate) {
260
282
  hideTooltip();
@@ -292,6 +314,9 @@ export const withAPI = ({ lifecycle }) => (component) => {
292
314
  }
293
315
  window.removeEventListener("resize", handleWindowResize);
294
316
  window.removeEventListener("scroll", handleWindowScroll);
317
+ document.removeEventListener("keydown", handleDocumentKeydown);
318
+ component.element.removeEventListener("mouseenter", handleTooltipMouseEnter);
319
+ component.element.removeEventListener("mouseleave", handleTooltipMouseLeave);
295
320
  if (component.element.parentNode) {
296
321
  component.element.parentNode.removeChild(component.element);
297
322
  }
@@ -1,5 +1,6 @@
1
1
  import { createBase, withElement, withEvents, withLifecycle, } from "../../core/compose/index.js";
2
2
  import { createConfig } from "./config.js";
3
+ import { TOP_APP_BAR_DEFAULTS } from "./constants.js";
3
4
  export const createTopAppBar = (config = {}) => {
4
5
  const componentConfig = createConfig(config);
5
6
  const component = createBase(componentConfig);
@@ -25,7 +26,7 @@ export const createTopAppBar = (config = {}) => {
25
26
  (componentConfig.type === "medium" || componentConfig.type === "large")) {
26
27
  classes.push(`${component.getClass("top-app-bar")}--compressible`);
27
28
  }
28
- return classes.filter(Boolean);
29
+ return classes.filter((name) => Boolean(name));
29
30
  };
30
31
  const enhancedComponent = withElement({
31
32
  tag: componentConfig.tag,
@@ -61,7 +62,7 @@ export const createTopAppBar = (config = {}) => {
61
62
  let isScrolled = false;
62
63
  if (componentConfig.scrollable) {
63
64
  const handleScroll = () => {
64
- const shouldBeScrolled = window.scrollY > componentConfig.scrollThreshold;
65
+ const shouldBeScrolled = window.scrollY > (componentConfig.scrollThreshold ?? TOP_APP_BAR_DEFAULTS.SCROLL_THRESHOLD);
65
66
  if (isScrolled !== shouldBeScrolled) {
66
67
  isScrolled = shouldBeScrolled;
67
68
  if (isScrolled) {
@@ -22,7 +22,7 @@ export interface TouchState {
22
22
  export interface BaseConfig {
23
23
  componentName?: string;
24
24
  prefix?: string;
25
- parent?: HTMLElement | string;
25
+ parent?: HTMLElement | string | null;
26
26
  }
27
27
  /**
28
28
  * Base component interface with prefix utilities
@@ -102,7 +102,7 @@ export const withElement = (options = {}) => (component) => {
102
102
  ...(Array.isArray(options.className)
103
103
  ? options.className
104
104
  : [options.className]),
105
- ].filter(Boolean),
105
+ ].filter((name) => Boolean(name)),
106
106
  attributes: options.attributes || {},
107
107
  id: options.id,
108
108
  name: options.name,