mtrl 0.9.4 → 0.9.5

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 (84) hide show
  1. package/dist/components/badge/api.d.ts +0 -1
  2. package/dist/components/badge/api.js +3 -1
  3. package/dist/components/bottom-app-bar/bottom-app-bar.js +9 -2
  4. package/dist/components/button/features/progress.d.ts +5 -0
  5. package/dist/components/button/features/progress.js +14 -12
  6. package/dist/components/card/api.js +17 -14
  7. package/dist/components/card/config.js +1 -1
  8. package/dist/components/card/content.js +2 -5
  9. package/dist/components/divider/features.d.ts +2 -46
  10. package/dist/components/divider/features.js +54 -115
  11. package/dist/components/radios/api.d.ts +4 -6
  12. package/dist/components/radios/api.js +5 -25
  13. package/dist/components/radios/radio.js +8 -33
  14. package/dist/components/search/features/states.js +2 -0
  15. package/dist/components/search/features/suggestions.js +3 -0
  16. package/dist/components/select/api.js +2 -0
  17. package/dist/components/select/features.js +8 -8
  18. package/dist/components/slider/features/controller.js +11 -2
  19. package/dist/components/tabs/api.js +12 -6
  20. package/dist/components/tabs/features.js +5 -7
  21. package/dist/components/tabs/tabs.js +6 -0
  22. package/dist/components/textfield/features/error.d.ts +1 -0
  23. package/dist/components/textfield/features/error.js +12 -0
  24. package/dist/components/tooltip/api.js +23 -7
  25. package/dist/components/top-app-bar/top-app-bar.js +3 -0
  26. package/dist/core/compose/features/textinput.d.ts +4 -0
  27. package/dist/core/compose/features/textinput.js +1 -0
  28. package/dist/index.cjs +0 -0
  29. package/dist/package.json +1 -1
  30. package/dist/styles/badge.css +1 -1
  31. package/dist/styles/base.css +1 -1
  32. package/dist/styles/bottom-app-bar.css +1 -1
  33. package/dist/styles/bottom-sheet.css +1 -1
  34. package/dist/styles/button-group.css +1 -1
  35. package/dist/styles/button.css +1 -1
  36. package/dist/styles/card.css +1 -1
  37. package/dist/styles/carousel.css +1 -1
  38. package/dist/styles/checkbox.css +1 -1
  39. package/dist/styles/chips.css +1 -1
  40. package/dist/styles/dialog.css +1 -1
  41. package/dist/styles/divider.css +1 -1
  42. package/dist/styles/drawer.css +1 -1
  43. package/dist/styles/extended-fab.css +1 -1
  44. package/dist/styles/fab.css +1 -1
  45. package/dist/styles/icon-button.css +1 -1
  46. package/dist/styles/list.css +1 -1
  47. package/dist/styles/loading-indicator.css +1 -1
  48. package/dist/styles/menu.css +1 -1
  49. package/dist/styles/navigation-rail.css +1 -1
  50. package/dist/styles/navigation.css +1 -1
  51. package/dist/styles/progress.css +1 -1
  52. package/dist/styles/radios.css +1 -1
  53. package/dist/styles/search.css +1 -1
  54. package/dist/styles/segmented-button.css +1 -1
  55. package/dist/styles/select.css +1 -1
  56. package/dist/styles/side-sheet.css +1 -1
  57. package/dist/styles/slider.css +1 -1
  58. package/dist/styles/snackbar.css +1 -1
  59. package/dist/styles/split-button.css +1 -1
  60. package/dist/styles/switch.css +1 -1
  61. package/dist/styles/tabs.css +1 -1
  62. package/dist/styles/textfield.css +1 -1
  63. package/dist/styles/timepicker.css +1 -1
  64. package/dist/styles/tooltip.css +1 -1
  65. package/dist/styles/top-app-bar.css +1 -1
  66. package/dist/styles/utilities.css +1 -1
  67. package/dist/styles.css +1 -1
  68. package/dist/themes/autumn.css +1 -1
  69. package/dist/themes/baseline.css +1 -1
  70. package/dist/themes/brownbeige.css +1 -1
  71. package/dist/themes/browngreen.css +1 -1
  72. package/dist/themes/desert.css +1 -1
  73. package/dist/themes/forest.css +1 -1
  74. package/dist/themes/highcontrast.css +1 -1
  75. package/dist/themes/legacy.css +1 -1
  76. package/dist/themes/material.css +1 -1
  77. package/dist/themes/ocean.css +1 -1
  78. package/dist/themes/sageivory.css +1 -1
  79. package/dist/themes/spring.css +1 -1
  80. package/dist/themes/summer.css +1 -1
  81. package/dist/themes/sunset.css +1 -1
  82. package/dist/themes/tealcaramel.css +1 -1
  83. package/dist/themes/winter.css +1 -1
  84. package/package.json +1 -1
@@ -30,7 +30,6 @@ interface ComponentWithElements {
30
30
  };
31
31
  getClass: (name: string) => string;
32
32
  addClass: (...classes: string[]) => ComponentWithElements;
33
- removeClass: (...classes: string[]) => ComponentWithElements;
34
33
  on: (event: string, handler: Function) => ComponentWithElements;
35
34
  off: (event: string, handler: Function) => ComponentWithElements;
36
35
  }
@@ -84,9 +84,11 @@ export const withAPI = ({ visibility, lifecycle }) => (component) => {
84
84
  component.config.variant = variant;
85
85
  if (variant === VARIANT_SMALL) {
86
86
  component.element.textContent = "";
87
+ component.element.removeAttribute("role");
87
88
  component.element.setAttribute("aria-hidden", "true");
88
89
  }
89
90
  else {
91
+ component.element.removeAttribute("aria-hidden");
90
92
  component.element.setAttribute("role", "status");
91
93
  if (component.config.label !== undefined) {
92
94
  badgeComponent.setLabel(component.config.label);
@@ -146,7 +148,7 @@ export const withAPI = ({ visibility, lifecycle }) => (component) => {
146
148
  return badgeComponent;
147
149
  },
148
150
  removeClass: (...classes) => {
149
- component.removeClass(...classes);
151
+ component.element.classList.remove(...classes.filter(Boolean));
150
152
  return badgeComponent;
151
153
  },
152
154
  on: (event, handler) => {
@@ -37,13 +37,11 @@ export const createBottomAppBar = (config = {}) => {
37
37
  if (currentScrollY > prevScrollY + 10) {
38
38
  if (isVisible) {
39
39
  bottomBar.hide();
40
- componentConfig.onVisibilityChange?.(false);
41
40
  }
42
41
  }
43
42
  else if (currentScrollY < prevScrollY - 10) {
44
43
  if (!isVisible) {
45
44
  bottomBar.show();
46
- componentConfig.onVisibilityChange?.(true);
47
45
  }
48
46
  }
49
47
  prevScrollY = currentScrollY;
@@ -57,6 +55,9 @@ export const createBottomAppBar = (config = {}) => {
57
55
  }
58
56
  const bottomBar = {
59
57
  ...withLifecycleComponent,
58
+ destroy() {
59
+ withLifecycleComponent.lifecycle.destroy();
60
+ },
60
61
  addAction(button) {
61
62
  actionsContainer.appendChild(button);
62
63
  return this;
@@ -69,12 +70,18 @@ export const createBottomAppBar = (config = {}) => {
69
70
  },
70
71
  show() {
71
72
  this.element.classList.remove(`${component.getClass("bottom-app-bar")}--hidden`);
73
+ const changed = !isVisible;
72
74
  isVisible = true;
75
+ if (changed)
76
+ componentConfig.onVisibilityChange?.(true);
73
77
  return this;
74
78
  },
75
79
  hide() {
76
80
  this.element.classList.add(`${component.getClass("bottom-app-bar")}--hidden`);
81
+ const changed = isVisible;
77
82
  isVisible = false;
83
+ if (changed)
84
+ componentConfig.onVisibilityChange?.(false);
78
85
  return this;
79
86
  },
80
87
  isVisible() {
@@ -17,6 +17,11 @@ interface ProgressEnhancedComponent {
17
17
  };
18
18
  setText?: (text: string) => unknown;
19
19
  getText?: () => string;
20
+ /** The label manager withText installs; the setText/getText above only exist after withAPI. */
21
+ text?: {
22
+ setText: (text: string) => unknown;
23
+ getText: () => string;
24
+ };
20
25
  showProgress?: () => Promise<ProgressEnhancedComponent>;
21
26
  showProgressSync?: () => ProgressEnhancedComponent;
22
27
  hideProgress?: () => Promise<ProgressEnhancedComponent>;
@@ -109,19 +109,23 @@ export const withProgress = (config) => (component) => {
109
109
  component.setIndeterminate(indeterminate);
110
110
  return component;
111
111
  };
112
+ const readLabel = () => component.text ? component.text.getText() : component.getText?.();
113
+ const writeLabel = (value) => {
114
+ if (component.text)
115
+ component.text.setText(value);
116
+ else
117
+ component.setText?.(value);
118
+ };
112
119
  component.setLoading = async function (loading, text) {
113
120
  if (loading && !isLoading) {
114
- if (component.setText && component.getText) {
115
- originalText = component.getText();
116
- }
121
+ originalText = readLabel() ?? "";
117
122
  isLoading = true;
118
123
  await component.showProgress();
119
124
  if (component.disabled?.disable) {
120
125
  component.disabled.disable();
121
126
  }
122
- if (text && component.setText) {
123
- component.setText(text);
124
- }
127
+ if (text)
128
+ writeLabel(text);
125
129
  }
126
130
  else if (!loading && isLoading) {
127
131
  isLoading = false;
@@ -129,12 +133,10 @@ export const withProgress = (config) => (component) => {
129
133
  if (component.disabled?.enable) {
130
134
  component.disabled.enable();
131
135
  }
132
- if (text && component.setText) {
133
- component.setText(text);
134
- }
135
- else if (originalText && component.setText) {
136
- component.setText(originalText);
137
- }
136
+ if (text)
137
+ writeLabel(text);
138
+ else if (originalText)
139
+ writeLabel(originalText);
138
140
  }
139
141
  return component;
140
142
  };
@@ -9,24 +9,27 @@ export const withAPI = ({ lifecycle, config }) => (component) => ({
9
9
  return this;
10
10
  },
11
11
  setHeader(headerElement) {
12
- if (headerElement && headerElement.classList.contains(`${component.getClass('card')}-header`)) {
13
- const existingHeader = component.element.querySelector(`.${component.getClass('card')}-header`);
12
+ const card = component.getClass('card');
13
+ if (headerElement && headerElement.classList.contains(`${card}-header`)) {
14
+ const title = (header) => header?.querySelector(`.${card}-header-title`)?.id || null;
15
+ const existingHeader = component.element.querySelector(`.${card}-header`);
14
16
  if (existingHeader) {
17
+ const previous = title(existingHeader);
18
+ if (previous && component.element.getAttribute('aria-labelledby') === previous) {
19
+ component.element.removeAttribute('aria-labelledby');
20
+ }
15
21
  existingHeader.remove();
16
22
  }
17
- const mediaElement = component.element.querySelector(`.${component.getClass('card')}-media`);
18
- if (mediaElement) {
19
- const mediaElements = component.element.querySelectorAll(`.${component.getClass('card')}-media`);
20
- const lastMedia = mediaElements[mediaElements.length - 1];
21
- if (lastMedia.nextSibling) {
22
- component.element.insertBefore(headerElement, lastMedia.nextSibling);
23
- }
24
- else {
25
- component.element.appendChild(headerElement);
26
- }
23
+ let before = component.element.firstElementChild;
24
+ while (before && before.classList.contains(`${card}-media`)) {
25
+ before = before.nextElementSibling;
27
26
  }
28
- else {
29
- component.element.insertBefore(headerElement, component.element.firstChild);
27
+ component.element.insertBefore(headerElement, before);
28
+ const titleId = title(headerElement);
29
+ if (titleId &&
30
+ !component.element.hasAttribute('aria-labelledby') &&
31
+ !component.element.hasAttribute('aria-label')) {
32
+ component.element.setAttribute('aria-labelledby', titleId);
30
33
  }
31
34
  }
32
35
  return this;
@@ -75,7 +75,7 @@ export const getElementConfig = (config) => {
75
75
  if (config.aria) {
76
76
  Object.entries(config.aria).forEach(([key, value]) => {
77
77
  if (value !== undefined) {
78
- const attrName = key.startsWith("aria-") ? key : `aria-${key}`;
78
+ const attrName = key === "role" || key.startsWith("aria-") ? key : `aria-${key}`;
79
79
  ariaAttributes[attrName] = value;
80
80
  }
81
81
  });
@@ -4,6 +4,7 @@ import { createBase, withElement } from "../../core/compose/component.js";
4
4
  import { createElement } from "../../core/dom/create.js";
5
5
  import { safeUrl } from "../../core/utils/url.js";
6
6
  export const CARD_CONTENT_PADDING = true;
7
+ let headerCount = 0;
7
8
  export const createCardContent = (config = {}) => {
8
9
  const baseConfig = {
9
10
  ...config,
@@ -73,13 +74,9 @@ export const createCardHeader = (config = {}) => {
73
74
  text: config.title,
74
75
  container: textContainer,
75
76
  attributes: {
76
- id: `${header.element.id || 'card-header'}-title`
77
+ id: `${header.element.id || `card-header-${++headerCount}`}-title`
77
78
  }
78
79
  });
79
- const parentCard = header.element.closest(`.${PREFIX}-card`);
80
- if (parentCard && !parentCard.hasAttribute('aria-labelledby')) {
81
- parentCard.setAttribute('aria-labelledby', `${header.element.id || 'card-header'}-title`);
82
- }
83
80
  }
84
81
  if (config.subtitle) {
85
82
  createElement({
@@ -1,50 +1,6 @@
1
1
  import { BaseComponent, ElementComponent } from "../../core/compose/index.js";
2
2
  import { DividerConfig } from "./config.js";
3
3
  import { DividerComponent } from "./types.js";
4
- /**
5
- * Adds orientation functionality to divider
6
- *
7
- * Higher-order function that enhances a component with the ability to be
8
- * oriented horizontally (default) or vertically. Controls the appropriate
9
- * CSS classes and dimensional styling based on orientation.
10
- *
11
- * @param config - Divider configuration
12
- * @returns Function that enhances a component with orientation capabilities
13
- *
14
- * @internal
15
- */
16
4
  export declare const withOrientation: (config: DividerConfig) => <C extends ElementComponent & BaseComponent>(component: C) => C & Partial<DividerComponent>;
17
- /**
18
- * Adds inset functionality to divider
19
- *
20
- * Higher-order function that enhances a component with the ability to have
21
- * customized inset spacing. Supports three variants (full-width, inset, middle-inset)
22
- * and allows fine-grained control over start and end insets.
23
- *
24
- * Insets are applied as margins in the appropriate direction based on orientation:
25
- * - For horizontal dividers: left/right margins
26
- * - For vertical dividers: top/bottom margins
27
- *
28
- * @param config - Divider configuration
29
- * @returns Function that enhances a component with inset capabilities
30
- *
31
- * @internal
32
- */
33
- export declare const withInset: (config: DividerConfig) => <C extends ElementComponent & Partial<DividerComponent>>(component: C) => C & Partial<DividerComponent>;
34
- /**
35
- * Adds style customization to divider
36
- *
37
- * Higher-order function that enhances a component with visual customization
38
- * capabilities, including:
39
- * - Custom thickness (height for horizontal, width for vertical dividers)
40
- * - Custom colors (background-color CSS property)
41
- *
42
- * These styling options allow dividers to be visually adapted to different
43
- * design requirements while maintaining Material Design principles.
44
- *
45
- * @param config - Divider configuration
46
- * @returns Function that enhances a component with style capabilities
47
- *
48
- * @internal
49
- */
50
- export declare const withStyle: (config: DividerConfig) => <C extends ElementComponent & Partial<DividerComponent>>(component: C) => C & Partial<DividerComponent>;
5
+ export declare const withInset: (config: DividerConfig) => <C extends ElementComponent & BaseComponent & Partial<DividerComponent>>(component: C) => C & Partial<DividerComponent>;
6
+ export declare const withStyle: (config: DividerConfig) => <C extends ElementComponent & BaseComponent & Partial<DividerComponent>>(component: C) => C & Partial<DividerComponent>;
@@ -1,138 +1,77 @@
1
- export const withOrientation = (config) => (component) => {
1
+ const ORIENTATIONS = ['horizontal', 'vertical'];
2
+ const VARIANTS = ['full-width', 'inset', 'middle-inset'];
3
+ const DEFAULT_INSET = 16;
4
+ const applyLayout = (component, config) => {
5
+ const { element } = component;
6
+ const base = component.getClass('divider');
2
7
  const orientation = config.orientation || 'horizontal';
3
- component.element.classList.add(`${component.getClass('divider')}--${orientation}`);
8
+ const variant = config.variant || 'full-width';
4
9
  const thickness = config.thickness || 1;
5
- if (orientation === 'horizontal') {
6
- component.element.style.height = `${thickness}px`;
7
- component.element.style.width = '100%';
8
- }
9
- else {
10
- component.element.style.width = `${thickness}px`;
11
- component.element.style.height = '100%';
10
+ const horizontal = orientation === 'horizontal';
11
+ ORIENTATIONS.forEach(name => element.classList.toggle(`${base}--${name}`, name === orientation));
12
+ VARIANTS.forEach(name => element.classList.toggle(`${base}--${name}`, name === variant));
13
+ if (horizontal)
14
+ element.removeAttribute('aria-orientation');
15
+ else
16
+ element.setAttribute('aria-orientation', 'vertical');
17
+ const style = element.style;
18
+ style.marginLeft = style.marginRight = style.marginTop = style.marginBottom = '';
19
+ const cross = horizontal ? 'height' : 'width';
20
+ const main = horizontal ? 'width' : 'height';
21
+ style[cross] = `${thickness}px`;
22
+ style[main] = '100%';
23
+ if (variant !== 'full-width') {
24
+ const insetStart = config.insetStart !== undefined ? config.insetStart : DEFAULT_INSET;
25
+ const insetEnd = config.insetEnd !== undefined
26
+ ? config.insetEnd
27
+ : (variant === 'middle-inset' ? DEFAULT_INSET : 0);
28
+ style[horizontal ? 'marginLeft' : 'marginTop'] = `${insetStart}px`;
29
+ style[horizontal ? 'marginRight' : 'marginBottom'] = `${insetEnd}px`;
30
+ style[main] = 'auto';
12
31
  }
32
+ };
33
+ export const withOrientation = (config) => (component) => {
34
+ applyLayout(component, config);
13
35
  return {
14
36
  ...component,
15
37
  getOrientation() {
16
- return orientation;
38
+ return config.orientation || 'horizontal';
17
39
  },
18
40
  setOrientation(newOrientation) {
19
- component.element.classList.remove(`${component.getClass('divider')}--${orientation}`);
20
- component.element.classList.add(`${component.getClass('divider')}--${newOrientation}`);
21
- if (newOrientation === 'horizontal') {
22
- component.element.style.height = `${thickness}px`;
23
- component.element.style.width = '100%';
24
- component.element.style.marginTop = '';
25
- component.element.style.marginBottom = '';
26
- }
27
- else {
28
- component.element.style.width = `${thickness}px`;
29
- component.element.style.height = '100%';
30
- component.element.style.marginLeft = '';
31
- component.element.style.marginRight = '';
32
- }
41
+ config.orientation = newOrientation;
42
+ applyLayout(component, config);
33
43
  return this;
34
44
  }
35
45
  };
36
46
  };
37
- export const withInset = (config) => (component) => {
38
- const variant = config.variant || 'full-width';
39
- const orientation = config.orientation || 'horizontal';
40
- if (variant === 'inset' || variant === 'middle-inset') {
41
- if (orientation === 'horizontal') {
42
- const insetStart = config.insetStart !== undefined ? config.insetStart : 16;
43
- const insetEnd = config.insetEnd !== undefined ? config.insetEnd : (variant === 'middle-inset' ? 16 : 0);
44
- component.element.style.marginLeft = `${insetStart}px`;
45
- component.element.style.marginRight = `${insetEnd}px`;
46
- component.element.style.width = "auto";
47
- }
48
- else {
49
- const insetStart = config.insetStart !== undefined ? config.insetStart : 16;
50
- const insetEnd = config.insetEnd !== undefined ? config.insetEnd : (variant === 'middle-inset' ? 16 : 0);
51
- component.element.style.marginTop = `${insetStart}px`;
52
- component.element.style.marginBottom = `${insetEnd}px`;
53
- component.element.style.height = "auto";
54
- }
47
+ export const withInset = (config) => (component) => ({
48
+ ...component,
49
+ getVariant() {
50
+ return config.variant || 'full-width';
51
+ },
52
+ setVariant(newVariant) {
53
+ config.variant = newVariant;
54
+ applyLayout(component, config);
55
+ return this;
56
+ },
57
+ setInset(insetStart, insetEnd) {
58
+ if (insetStart !== undefined)
59
+ config.insetStart = insetStart;
60
+ if (insetEnd !== undefined)
61
+ config.insetEnd = insetEnd;
62
+ applyLayout(component, config);
63
+ return this;
55
64
  }
56
- return {
57
- ...component,
58
- getVariant() {
59
- return variant;
60
- },
61
- setVariant(newVariant) {
62
- component.element.classList.remove(`${component.getClass('divider')}--${variant}`);
63
- component.element.classList.add(`${component.getClass('divider')}--${newVariant}`);
64
- const currentOrientation = component.getOrientation ? component.getOrientation() : orientation;
65
- if (newVariant === 'full-width') {
66
- if (currentOrientation === 'horizontal') {
67
- component.element.style.marginLeft = '';
68
- component.element.style.marginRight = '';
69
- }
70
- else {
71
- component.element.style.marginTop = '';
72
- component.element.style.marginBottom = '';
73
- }
74
- }
75
- else {
76
- const insetStart = config.insetStart !== undefined ? config.insetStart : 16;
77
- const insetEnd = config.insetEnd !== undefined ? config.insetEnd : (newVariant === 'middle-inset' ? 16 : 0);
78
- if (currentOrientation === 'horizontal') {
79
- component.element.style.marginLeft = `${insetStart}px`;
80
- component.element.style.marginRight = `${insetEnd}px`;
81
- }
82
- else {
83
- component.element.style.marginTop = `${insetStart}px`;
84
- component.element.style.marginBottom = `${insetEnd}px`;
85
- }
86
- }
87
- return this;
88
- },
89
- setInset(insetStart, insetEnd) {
90
- const currentOrientation = component.getOrientation ? component.getOrientation() : orientation;
91
- const currentVariant = component.getVariant ? component.getVariant() : variant;
92
- if (currentVariant !== 'full-width') {
93
- if (currentOrientation === 'horizontal') {
94
- if (insetStart !== undefined) {
95
- component.element.style.marginLeft = `${insetStart}px`;
96
- }
97
- if (insetEnd !== undefined) {
98
- component.element.style.marginRight = `${insetEnd}px`;
99
- }
100
- }
101
- else {
102
- if (insetStart !== undefined) {
103
- component.element.style.marginTop = `${insetStart}px`;
104
- }
105
- if (insetEnd !== undefined) {
106
- component.element.style.marginBottom = `${insetEnd}px`;
107
- }
108
- }
109
- }
110
- return this;
111
- }
112
- };
113
- };
65
+ });
114
66
  export const withStyle = (config) => (component) => {
115
67
  if (config.color) {
116
68
  component.element.style.backgroundColor = config.color;
117
69
  }
118
- const thickness = config.thickness || 1;
119
- const orientation = config.orientation || 'horizontal';
120
- if (orientation === 'horizontal') {
121
- component.element.style.height = `${thickness}px`;
122
- }
123
- else {
124
- component.element.style.width = `${thickness}px`;
125
- }
126
70
  return {
127
71
  ...component,
128
72
  setThickness(newThickness) {
129
- const currentOrientation = component.getOrientation ? component.getOrientation() : orientation;
130
- if (currentOrientation === 'horizontal') {
131
- component.element.style.height = `${newThickness}px`;
132
- }
133
- else {
134
- component.element.style.width = `${newThickness}px`;
135
- }
73
+ config.thickness = newThickness;
74
+ applyLayout(component, config);
136
75
  return this;
137
76
  },
138
77
  setColor(color) {
@@ -21,11 +21,9 @@ interface ComponentWithRadio {
21
21
  enableOption: (value: string) => void;
22
22
  disableOption: (value: string) => void;
23
23
  getClass: (name: string) => string;
24
- events: {
25
- on: (event: string, handler: Function) => void;
26
- off: (event: string, handler: Function) => void;
27
- emit?: (event: string, data: unknown) => void;
28
- };
24
+ on: (event: string, handler: Function) => void;
25
+ off: (event: string, handler: Function) => void;
26
+ emit?: (event: string, data: unknown) => void;
29
27
  }
30
28
  /**
31
29
  * Enhances a radios component with API methods
@@ -33,5 +31,5 @@ interface ComponentWithRadio {
33
31
  * @returns {Function} Higher-order function that adds API methods to component
34
32
  * @internal This is an internal utility for the Radios component
35
33
  */
36
- export declare const withAPI: ({ disabled, lifecycle }: ApiOptions) => (component: ComponentWithRadio) => RadiosComponent;
34
+ export declare const withAPI: ({ lifecycle }: ApiOptions) => (component: ComponentWithRadio) => RadiosComponent;
37
35
  export {};
@@ -1,24 +1,4 @@
1
- export const withAPI = ({ disabled, lifecycle }) => (component) => {
2
- if (!component.events) {
3
- component.events = {
4
- on: (event, handler) => {
5
- component.element.addEventListener(event, ((e) => handler(e.detail)));
6
- },
7
- off: (event, handler) => {
8
- component.element.removeEventListener(event, handler);
9
- },
10
- emit: (event, data) => {
11
- const customEvent = new CustomEvent(event, { detail: data });
12
- component.element.dispatchEvent(customEvent);
13
- },
14
- };
15
- }
16
- else if (!component.events.emit) {
17
- component.events.emit = (event, data) => {
18
- const customEvent = new CustomEvent(event, { detail: data });
19
- component.element.dispatchEvent(customEvent);
20
- };
21
- }
1
+ export const withAPI = ({ lifecycle }) => (component) => {
22
2
  const radiosComponent = {
23
3
  element: component.element,
24
4
  radios: component.radios,
@@ -41,11 +21,11 @@ export const withAPI = ({ disabled, lifecycle }) => (component) => {
41
21
  return this;
42
22
  },
43
23
  enable() {
44
- disabled.enable();
24
+ component.enable();
45
25
  return this;
46
26
  },
47
27
  disable() {
48
- disabled.disable();
28
+ component.disable();
49
29
  return this;
50
30
  },
51
31
  enableOption(value) {
@@ -57,11 +37,11 @@ export const withAPI = ({ disabled, lifecycle }) => (component) => {
57
37
  return this;
58
38
  },
59
39
  on(event, handler) {
60
- component.events.on(event, handler);
40
+ component.on(event, handler);
61
41
  return this;
62
42
  },
63
43
  off(event, handler) {
64
- component.events.off(event, handler);
44
+ component.off(event, handler);
65
45
  return this;
66
46
  },
67
47
  destroy() {
@@ -58,23 +58,11 @@ export const withRadio = (config) => (component) => {
58
58
  radio.input.checked = false;
59
59
  }
60
60
  });
61
- if (component.events && typeof component.events.emit === 'function') {
62
- component.events.emit('change', {
63
- value: option.value,
64
- originalEvent: e,
65
- option
66
- });
67
- }
68
- else if (typeof component.on === 'function') {
69
- const changeEvent = new CustomEvent('change', {
70
- detail: {
71
- value: option.value,
72
- originalEvent: e,
73
- option
74
- }
75
- });
76
- component.element.dispatchEvent(changeEvent);
77
- }
61
+ component.emit?.('change', {
62
+ value: option.value,
63
+ originalEvent: e,
64
+ option
65
+ });
78
66
  }
79
67
  };
80
68
  input.addEventListener('change', handleChange);
@@ -112,28 +100,15 @@ export const withRadio = (config) => (component) => {
112
100
  if (config.options && Array.isArray(config.options)) {
113
101
  config.options.forEach(option => addOption(option));
114
102
  }
115
- if (!component.events) {
116
- component.events = {
117
- on: (event, handler) => {
118
- component.element.addEventListener(event, (e) => handler(e.detail));
119
- },
120
- off: (event, handler) => {
121
- component.element.removeEventListener(event, handler);
122
- },
123
- emit: (event, data) => {
124
- const customEvent = new CustomEvent(event, { detail: data });
125
- component.element.dispatchEvent(customEvent);
126
- }
127
- };
128
- }
129
103
  return {
130
104
  ...component,
131
105
  radios,
132
106
  getValue: () => selectedValue,
133
107
  setValue: (value) => {
134
- selectedValue = value;
108
+ const known = radios.some(radio => radio.config.value === value);
109
+ selectedValue = known ? value : '';
135
110
  radios.forEach(radio => {
136
- radio.input.checked = radio.config.value === value;
111
+ radio.input.checked = known && radio.config.value === value;
137
112
  });
138
113
  return component;
139
114
  },
@@ -38,6 +38,7 @@ export const withStates = (config) => (component) => {
38
38
  viewMode: currentViewMode,
39
39
  });
40
40
  }
41
+ config.onExpand?.();
41
42
  };
42
43
  const collapseToBar = () => {
43
44
  if (currentState === SEARCH_STATES.BAR) {
@@ -67,6 +68,7 @@ export const withStates = (config) => (component) => {
67
68
  viewMode: currentViewMode,
68
69
  });
69
70
  }
71
+ config.onCollapse?.();
70
72
  };
71
73
  const setViewMode = (mode) => {
72
74
  if (mode === currentViewMode) {
@@ -207,7 +207,10 @@ export const withSuggestions = () => (component) => {
207
207
  };
208
208
  setTimeout(() => {
209
209
  setupKeyboardNavigation();
210
+ if (getSuggestions().length > 0)
211
+ renderSuggestions();
210
212
  }, 0);
213
+ component.on?.("expand", () => renderSuggestions());
211
214
  return {
212
215
  ...component,
213
216
  suggestions: {
@@ -20,6 +20,8 @@ export const withAPI = (options) => (component) => ({
20
20
  return this;
21
21
  },
22
22
  open(interactionType = "mouse") {
23
+ if (component.textfield?.input?.disabled)
24
+ return this;
23
25
  if (component.menu && typeof component.menu.open === "function") {
24
26
  component.menu.open(undefined, interactionType);
25
27
  }