mtrl 0.10.4 → 0.10.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 (221) hide show
  1. package/README.md +15 -5
  2. package/dist/README.md +15 -5
  3. package/dist/components/bottom-app-bar/bottom-app-bar.js +2 -1
  4. package/dist/components/bottom-app-bar/index.d.ts +3 -1
  5. package/dist/components/button/api.js +3 -0
  6. package/dist/components/button/constants.d.ts +2 -0
  7. package/dist/components/button/index.d.ts +1 -0
  8. package/dist/components/button/types.d.ts +7 -1
  9. package/dist/components/button-group/button-group.js +3 -0
  10. package/dist/components/button-group/config.js +2 -2
  11. package/dist/components/button-group/constants.d.ts +8 -2
  12. package/dist/components/button-group/index.d.ts +1 -0
  13. package/dist/components/button-group/types.d.ts +2 -0
  14. package/dist/components/card/index.d.ts +21 -3
  15. package/dist/components/card/index.js +1 -1
  16. package/dist/components/card/types.d.ts +19 -3
  17. package/dist/components/carousel/api.d.ts +1 -0
  18. package/dist/components/carousel/api.js +4 -0
  19. package/dist/components/carousel/features/scroll.d.ts +1 -0
  20. package/dist/components/carousel/features/scroll.js +117 -2
  21. package/dist/components/carousel/types.d.ts +10 -0
  22. package/dist/components/checkbox/api.js +3 -0
  23. package/dist/components/checkbox/config.js +1 -0
  24. package/dist/components/checkbox/types.d.ts +8 -2
  25. package/dist/components/chips/types.d.ts +5 -1
  26. package/dist/components/datepicker/index.d.ts +3 -1
  27. package/dist/components/datepicker/index.js +1 -1
  28. package/dist/components/extended-fab/api.d.ts +2 -0
  29. package/dist/components/extended-fab/api.js +3 -0
  30. package/dist/components/extended-fab/config.d.ts +1 -0
  31. package/dist/components/extended-fab/config.js +1 -0
  32. package/dist/components/extended-fab/types.d.ts +2 -0
  33. package/dist/components/fab/api.d.ts +2 -0
  34. package/dist/components/fab/api.js +3 -0
  35. package/dist/components/fab/config.d.ts +1 -0
  36. package/dist/components/fab/config.js +1 -0
  37. package/dist/components/fab/constants.d.ts +2 -1
  38. package/dist/components/fab/types.d.ts +2 -0
  39. package/dist/components/fab-menu/fab-menu.js +2 -1
  40. package/dist/components/icon-button/api.js +3 -0
  41. package/dist/components/icon-button/constants.d.ts +2 -0
  42. package/dist/components/icon-button/types.d.ts +2 -0
  43. package/dist/components/index.d.ts +18 -5
  44. package/dist/components/index.js +2 -1
  45. package/dist/components/list/types.d.ts +5 -3
  46. package/dist/components/menu/features/keyboard.js +8 -5
  47. package/dist/components/menu/features/opener.js +1 -1
  48. package/dist/components/menu/index.d.ts +1 -0
  49. package/dist/components/progress/features/canvas.js +1 -1
  50. package/dist/components/progress/features/colors.d.ts +2 -1
  51. package/dist/components/progress/features/colors.js +20 -10
  52. package/dist/components/radios/api.d.ts +1 -0
  53. package/dist/components/radios/api.js +3 -0
  54. package/dist/components/radios/constants.d.ts +6 -6
  55. package/dist/components/radios/radio.d.ts +1 -0
  56. package/dist/components/radios/radio.js +1 -0
  57. package/dist/components/radios/types.d.ts +2 -0
  58. package/dist/components/search/features/states.d.ts +2 -0
  59. package/dist/components/search/features/states.js +12 -1
  60. package/dist/components/search/features/structure.js +2 -0
  61. package/dist/components/segmented-button/config.js +2 -2
  62. package/dist/components/select/api.js +3 -0
  63. package/dist/components/select/constants.d.ts +1 -1
  64. package/dist/components/select/features.js +2 -0
  65. package/dist/components/select/index.d.ts +1 -0
  66. package/dist/components/select/types.d.ts +4 -0
  67. package/dist/components/switch/api.js +3 -0
  68. package/dist/components/switch/config.js +1 -0
  69. package/dist/components/switch/index.d.ts +6 -2
  70. package/dist/components/switch/index.js +1 -1
  71. package/dist/components/switch/types.d.ts +6 -2
  72. package/dist/components/tabs/config.js +2 -2
  73. package/dist/components/tabs/index.d.ts +36 -3
  74. package/dist/components/tabs/index.js +2 -1
  75. package/dist/components/tabs/tab.js +8 -1
  76. package/dist/components/tabs/types.d.ts +2 -0
  77. package/dist/components/tabs/utils.js +2 -1
  78. package/dist/components/textfield/api.d.ts +5 -0
  79. package/dist/components/textfield/api.js +209 -188
  80. package/dist/components/textfield/config.js +4 -3
  81. package/dist/components/textfield/constants.d.ts +24 -7
  82. package/dist/components/textfield/constants.js +9 -7
  83. package/dist/components/textfield/features/index.d.ts +3 -1
  84. package/dist/components/textfield/features/index.js +1 -0
  85. package/dist/components/textfield/features/leading-icon.js +1 -0
  86. package/dist/components/textfield/features/placement.js +15 -2
  87. package/dist/components/textfield/features/required.d.ts +33 -0
  88. package/dist/components/textfield/features/required.js +50 -0
  89. package/dist/components/textfield/features/supporting-text.js +43 -21
  90. package/dist/components/textfield/features/trailing-icon.d.ts +23 -1
  91. package/dist/components/textfield/features/trailing-icon.js +39 -5
  92. package/dist/components/textfield/index.d.ts +16 -1
  93. package/dist/components/textfield/index.js +1 -0
  94. package/dist/components/textfield/textfield.js +2 -2
  95. package/dist/components/textfield/types.d.ts +33 -3
  96. package/dist/components/toolbar/toolbar.js +2 -1
  97. package/dist/components/tooltip/constants.d.ts +4 -1
  98. package/dist/components/top-app-bar/index.d.ts +3 -1
  99. package/dist/components/top-app-bar/top-app-bar.d.ts +2 -57
  100. package/dist/components/top-app-bar/top-app-bar.js +2 -1
  101. package/dist/core/utils/theme.d.ts +6 -2
  102. package/dist/core/utils/theme.js +12 -19
  103. package/dist/elements/carousel.d.ts +5 -0
  104. package/dist/elements/carousel.js +1 -0
  105. package/dist/elements/css/textfield.js +1 -1
  106. package/dist/elements/dialog.js +1 -1
  107. package/dist/elements/index.d.ts +15 -4
  108. package/dist/elements/index.js +2 -1
  109. package/dist/elements/preupgrade.css +1 -1
  110. package/dist/elements/textfield.d.ts +1 -1
  111. package/dist/elements/textfield.js +1 -1
  112. package/dist/index.cjs +0 -0
  113. package/dist/package.json +1 -1
  114. package/dist/react/dialog.d.ts +3 -0
  115. package/dist/react/dialog.js +3 -0
  116. package/dist/react/index.d.ts +4 -1
  117. package/dist/react/index.js +2 -1
  118. package/dist/solid/dialog.d.ts +3 -0
  119. package/dist/solid/dialog.js +3 -0
  120. package/dist/solid/index.d.ts +4 -1
  121. package/dist/solid/index.js +2 -1
  122. package/dist/styles/badge.css +1 -1
  123. package/dist/styles/base.css +2 -2
  124. package/dist/styles/bottom-app-bar.css +1 -1
  125. package/dist/styles/bottom-sheet.css +1 -1
  126. package/dist/styles/button-group.css +1 -1
  127. package/dist/styles/button.css +1 -1
  128. package/dist/styles/card.css +1 -1
  129. package/dist/styles/carousel.css +1 -1
  130. package/dist/styles/checkbox.css +1 -1
  131. package/dist/styles/chips.css +1 -1
  132. package/dist/styles/datepicker.css +1 -1
  133. package/dist/styles/dialog.css +1 -1
  134. package/dist/styles/divider.css +1 -1
  135. package/dist/styles/drawer.css +1 -1
  136. package/dist/styles/extended-fab.css +1 -1
  137. package/dist/styles/fab-menu.css +1 -1
  138. package/dist/styles/fab.css +1 -1
  139. package/dist/styles/icon-button.css +1 -1
  140. package/dist/styles/list.css +1 -1
  141. package/dist/styles/loading-indicator.css +1 -1
  142. package/dist/styles/menu.css +1 -1
  143. package/dist/styles/navigation-rail.css +1 -1
  144. package/dist/styles/progress.css +1 -1
  145. package/dist/styles/radios.css +1 -1
  146. package/dist/styles/search.css +1 -1
  147. package/dist/styles/segmented-button.css +1 -1
  148. package/dist/styles/select.css +1 -1
  149. package/dist/styles/side-sheet.css +1 -1
  150. package/dist/styles/slider.css +1 -1
  151. package/dist/styles/snackbar.css +1 -1
  152. package/dist/styles/split-button.css +1 -1
  153. package/dist/styles/switch.css +1 -1
  154. package/dist/styles/tabs.css +1 -1
  155. package/dist/styles/textfield.css +2 -2
  156. package/dist/styles/timepicker.css +1 -1
  157. package/dist/styles/toolbar.css +1 -1
  158. package/dist/styles/tooltip.css +1 -1
  159. package/dist/styles/top-app-bar.css +1 -1
  160. package/dist/styles/utilities.css +1 -1
  161. package/dist/styles.css +2 -2
  162. package/dist/svelte/Dialog.svelte +3 -0
  163. package/dist/svelte/index.d.ts +5 -1
  164. package/dist/svelte/index.js +5 -1
  165. package/dist/themes/autumn.css +2 -2
  166. package/dist/themes/baseline.css +2 -2
  167. package/dist/themes/brownbeige.css +2 -2
  168. package/dist/themes/browngreen.css +2 -2
  169. package/dist/themes/content.css +2 -2
  170. package/dist/themes/desert.css +2 -2
  171. package/dist/themes/expressive.css +2 -2
  172. package/dist/themes/fidelity.css +2 -2
  173. package/dist/themes/forest.css +2 -2
  174. package/dist/themes/fruit-salad.css +2 -2
  175. package/dist/themes/highcontrast.css +2 -2
  176. package/dist/themes/legacy.css +2 -2
  177. package/dist/themes/material.css +2 -2
  178. package/dist/themes/monochrome.css +2 -2
  179. package/dist/themes/neutral.css +2 -2
  180. package/dist/themes/ocean.css +2 -2
  181. package/dist/themes/rainbow.css +2 -2
  182. package/dist/themes/sageivory.css +2 -2
  183. package/dist/themes/spring.css +2 -2
  184. package/dist/themes/summer.css +2 -2
  185. package/dist/themes/sunset.css +2 -2
  186. package/dist/themes/tealcaramel.css +2 -2
  187. package/dist/themes/vibrant.css +2 -2
  188. package/dist/themes/winter.css +2 -2
  189. package/dist/vue/dialog.d.ts +3 -0
  190. package/dist/vue/dialog.js +3 -0
  191. package/dist/vue/index.d.ts +4 -1
  192. package/dist/vue/index.js +2 -1
  193. package/package.json +1 -1
  194. package/src/styles/abstract/_theme.scss +6 -6
  195. package/src/styles/abstract/_variables.scss +14 -4
  196. package/src/styles/components/_textfield.scss +61 -8
  197. package/src/styles/themes/_autumn.scss +173 -0
  198. package/src/styles/themes/_base-theme.scss +100 -12
  199. package/src/styles/themes/_baseline.scss +172 -0
  200. package/src/styles/themes/_brownbeige.scss +150 -0
  201. package/src/styles/themes/_browngreen.scss +183 -0
  202. package/src/styles/themes/_content.scss +148 -0
  203. package/src/styles/themes/_desert.scss +150 -0
  204. package/src/styles/themes/_expressive.scss +150 -0
  205. package/src/styles/themes/_fidelity.scss +148 -0
  206. package/src/styles/themes/_forest.scss +175 -0
  207. package/src/styles/themes/_fruit-salad.scss +150 -0
  208. package/src/styles/themes/_highcontrast.scss +74 -0
  209. package/src/styles/themes/_legacy.scss +181 -0
  210. package/src/styles/themes/_material.scss +181 -0
  211. package/src/styles/themes/_monochrome.scss +134 -0
  212. package/src/styles/themes/_neutral.scss +150 -0
  213. package/src/styles/themes/_ocean.scss +175 -0
  214. package/src/styles/themes/_rainbow.scss +150 -0
  215. package/src/styles/themes/_sageivory.scss +150 -0
  216. package/src/styles/themes/_spring.scss +171 -0
  217. package/src/styles/themes/_summer.scss +150 -0
  218. package/src/styles/themes/_sunset.scss +175 -0
  219. package/src/styles/themes/_tealcaramel.scss +150 -0
  220. package/src/styles/themes/_vibrant.scss +150 -0
  221. package/src/styles/themes/_winter.scss +181 -0
@@ -1,8 +1,8 @@
1
1
  import { createComponentConfig, createElementConfig, } from "../../core/config/component.js";
2
- import { TEXTFIELD_DEFAULTS } from "./constants.js";
2
+ import { TEXT_FIELD_DEFAULTS } from "./constants.js";
3
3
  export const defaultConfig = {
4
- type: TEXTFIELD_DEFAULTS.TYPE,
5
- variant: TEXTFIELD_DEFAULTS.VARIANT,
4
+ type: TEXT_FIELD_DEFAULTS.TYPE,
5
+ variant: TEXT_FIELD_DEFAULTS.VARIANT,
6
6
  };
7
7
  export const createBaseConfig = (config = {}) => createComponentConfig(defaultConfig, config, "textfield");
8
8
  export const getElementConfig = (config) => createElementConfig(config, {
@@ -13,6 +13,7 @@ export const getApiConfig = (comp) => ({
13
13
  disabled: {
14
14
  enable: comp.disabled.enable,
15
15
  disable: comp.disabled.disable,
16
+ isDisabled: comp.disabled.isDisabled,
16
17
  },
17
18
  lifecycle: {
18
19
  destroy: comp.lifecycle.destroy,
@@ -2,7 +2,7 @@
2
2
  * Textfield variant constants
3
3
  * @category Components
4
4
  */
5
- export declare const TEXTFIELD_VARIANTS: {
5
+ declare const TEXTFIELD_VARIANTS: {
6
6
  /** Filled variant with background and animated label */
7
7
  readonly FILLED: "filled";
8
8
  /** Outlined variant with border and animated label */
@@ -12,7 +12,7 @@ export declare const TEXTFIELD_VARIANTS: {
12
12
  * Textfield state constants
13
13
  * @category Components
14
14
  */
15
- export declare const TEXTFIELD_STATES: {
15
+ declare const TEXTFIELD_STATES: {
16
16
  /** Textfield is active (focused) */
17
17
  readonly ACTIVE: "active";
18
18
  /** Textfield is inactive (not focused) */
@@ -24,7 +24,7 @@ export declare const TEXTFIELD_STATES: {
24
24
  * Textfield type constants
25
25
  * @category Components
26
26
  */
27
- export declare const TEXTFIELD_TYPES: {
27
+ declare const TEXTFIELD_TYPES: {
28
28
  /** Standard text input */
29
29
  readonly TEXT: "text";
30
30
  /** Password input with obscured characters */
@@ -46,7 +46,7 @@ export declare const TEXTFIELD_TYPES: {
46
46
  * Textfield event constants
47
47
  * @category Components
48
48
  */
49
- export declare const TEXTFIELD_EVENTS: {
49
+ declare const TEXTFIELD_EVENTS: {
50
50
  /** Emitted on native input change; setValue() is silent */
51
51
  readonly CHANGE: "change";
52
52
  /** Fired during input */
@@ -55,6 +55,8 @@ export declare const TEXTFIELD_EVENTS: {
55
55
  readonly FOCUS: "focus";
56
56
  /** Fired when textfield loses focus */
57
57
  readonly BLUR: "blur";
58
+ /** Fired when the trailing icon button is activated (`trailingIconLabel`; FLO-301) */
59
+ readonly TRAILING: "trailing";
58
60
  /** Legacy name, not emitted; listen for keydown on the input and check key === "Enter" */
59
61
  readonly ENTER: "enter";
60
62
  /** Native input event only; not emitted through on() */
@@ -66,7 +68,7 @@ export declare const TEXTFIELD_EVENTS: {
66
68
  * Textfield density constants
67
69
  * @category Components
68
70
  */
69
- export declare const TEXTFIELD_DENSITY: {
71
+ declare const TEXTFIELD_DENSITY: {
70
72
  /** Default density (56px height) */
71
73
  readonly DEFAULT: "default";
72
74
  /** Compact density (40px height) */
@@ -76,7 +78,7 @@ export declare const TEXTFIELD_DENSITY: {
76
78
  * Default textfield configuration values
77
79
  * @category Components
78
80
  */
79
- export declare const TEXTFIELD_DEFAULTS: {
81
+ declare const TEXTFIELD_DEFAULTS: {
80
82
  /** Default input type */
81
83
  readonly TYPE: "text";
82
84
  /** Default visual variant */
@@ -98,7 +100,7 @@ export declare const TEXTFIELD_DEFAULTS: {
98
100
  * CSS class names used by the textfield component
99
101
  * @category Components
100
102
  */
101
- export declare const TEXTFIELD_CLASSES: {
103
+ declare const TEXTFIELD_CLASSES: {
102
104
  /** Root element class */
103
105
  readonly ROOT: "textfield";
104
106
  /** Input element class */
@@ -152,3 +154,18 @@ export declare const TEXTFIELD_CLASSES: {
152
154
  /** Multiline class */
153
155
  readonly MULTILINE: "textfield--multiline";
154
156
  };
157
+ export { TEXTFIELD_VARIANTS as TEXT_FIELD_VARIANTS, TEXTFIELD_STATES as TEXT_FIELD_STATES, TEXTFIELD_TYPES as TEXT_FIELD_TYPES, TEXTFIELD_EVENTS as TEXT_FIELD_EVENTS, TEXTFIELD_DENSITY as TEXT_FIELD_DENSITY, TEXTFIELD_DEFAULTS as TEXT_FIELD_DEFAULTS, TEXTFIELD_CLASSES as TEXT_FIELD_CLASSES,
158
+ /** @deprecated Use TEXT_FIELD_VARIANTS: M3 writes "text field" as two words. Removed in 1.0 (FLO-383). */
159
+ TEXTFIELD_VARIANTS,
160
+ /** @deprecated Use TEXT_FIELD_STATES: M3 writes "text field" as two words. Removed in 1.0 (FLO-383). */
161
+ TEXTFIELD_STATES,
162
+ /** @deprecated Use TEXT_FIELD_TYPES: M3 writes "text field" as two words. Removed in 1.0 (FLO-383). */
163
+ TEXTFIELD_TYPES,
164
+ /** @deprecated Use TEXT_FIELD_EVENTS: M3 writes "text field" as two words. Removed in 1.0 (FLO-383). */
165
+ TEXTFIELD_EVENTS,
166
+ /** @deprecated Use TEXT_FIELD_DENSITY: M3 writes "text field" as two words. Removed in 1.0 (FLO-383). */
167
+ TEXTFIELD_DENSITY,
168
+ /** @deprecated Use TEXT_FIELD_DEFAULTS: M3 writes "text field" as two words. Removed in 1.0 (FLO-383). */
169
+ TEXTFIELD_DEFAULTS,
170
+ /** @deprecated Use TEXT_FIELD_CLASSES: M3 writes "text field" as two words. Removed in 1.0 (FLO-383). */
171
+ TEXTFIELD_CLASSES, };
@@ -1,13 +1,13 @@
1
- export const TEXTFIELD_VARIANTS = {
1
+ const TEXTFIELD_VARIANTS = {
2
2
  FILLED: "filled",
3
3
  OUTLINED: "outlined",
4
4
  };
5
- export const TEXTFIELD_STATES = {
5
+ const TEXTFIELD_STATES = {
6
6
  ACTIVE: "active",
7
7
  INACTIVE: "inactive",
8
8
  DISABLED: "disabled",
9
9
  };
10
- export const TEXTFIELD_TYPES = {
10
+ const TEXTFIELD_TYPES = {
11
11
  TEXT: "text",
12
12
  PASSWORD: "password",
13
13
  EMAIL: "email",
@@ -17,20 +17,21 @@ export const TEXTFIELD_TYPES = {
17
17
  SEARCH: "search",
18
18
  MULTILINE: "multiline",
19
19
  };
20
- export const TEXTFIELD_EVENTS = {
20
+ const TEXTFIELD_EVENTS = {
21
21
  CHANGE: "change",
22
22
  INPUT: "input",
23
23
  FOCUS: "focus",
24
24
  BLUR: "blur",
25
+ TRAILING: "trailing",
25
26
  ENTER: "enter",
26
27
  KEYDOWN: "keydown",
27
28
  KEYUP: "keyup",
28
29
  };
29
- export const TEXTFIELD_DENSITY = {
30
+ const TEXTFIELD_DENSITY = {
30
31
  DEFAULT: "default",
31
32
  COMPACT: "compact",
32
33
  };
33
- export const TEXTFIELD_DEFAULTS = {
34
+ const TEXTFIELD_DEFAULTS = {
34
35
  TYPE: TEXTFIELD_TYPES.TEXT,
35
36
  VARIANT: TEXTFIELD_VARIANTS.FILLED,
36
37
  DENSITY: TEXTFIELD_DENSITY.DEFAULT,
@@ -40,7 +41,7 @@ export const TEXTFIELD_DEFAULTS = {
40
41
  FLOAT_LABEL: true,
41
42
  ANIMATION_DURATION: 150,
42
43
  };
43
- export const TEXTFIELD_CLASSES = {
44
+ const TEXTFIELD_CLASSES = {
44
45
  ROOT: "textfield",
45
46
  INPUT: "textfield__input",
46
47
  LABEL: "textfield__label",
@@ -66,3 +67,4 @@ export const TEXTFIELD_CLASSES = {
66
67
  RTL: "textfield--rtl",
67
68
  MULTILINE: "textfield--multiline",
68
69
  };
70
+ export { TEXTFIELD_VARIANTS as TEXT_FIELD_VARIANTS, TEXTFIELD_STATES as TEXT_FIELD_STATES, TEXTFIELD_TYPES as TEXT_FIELD_TYPES, TEXTFIELD_EVENTS as TEXT_FIELD_EVENTS, TEXTFIELD_DENSITY as TEXT_FIELD_DENSITY, TEXTFIELD_DEFAULTS as TEXT_FIELD_DEFAULTS, TEXTFIELD_CLASSES as TEXT_FIELD_CLASSES, TEXTFIELD_VARIANTS, TEXTFIELD_STATES, TEXTFIELD_TYPES, TEXTFIELD_EVENTS, TEXTFIELD_DENSITY, TEXTFIELD_DEFAULTS, TEXTFIELD_CLASSES, };
@@ -8,8 +8,10 @@ export { withDensity } from "./density.js";
8
8
  export { withError } from "./error.js";
9
9
  export { withField } from "./field.js";
10
10
  export { withCounter } from "./counter.js";
11
+ export { withRequired } from "./required.js";
11
12
  export type { LeadingIconComponent, LeadingIconConfig } from "./leading-icon.js";
12
- export type { TrailingIconComponent, TrailingIconConfig, } from "./trailing-icon.js";
13
+ export type { TrailingIconComponent, TrailingIconConfig, TextfieldTrailingPayload, } from "./trailing-icon.js";
14
+ export type { RequiredConfig, RequiredFeature } from "./required.js";
13
15
  export type { PrefixTextComponent, PrefixTextConfig } from "./prefix-text.js";
14
16
  export type { SuffixTextComponent, SuffixTextConfig } from "./suffix-text.js";
15
17
  export type { SupportingTextComponent, SupportingTextConfig, } from "./supporting-text.js";
@@ -8,3 +8,4 @@ export { withDensity } from "./density.js";
8
8
  export { withError } from "./error.js";
9
9
  export { withField } from "./field.js";
10
10
  export { withCounter } from "./counter.js";
11
+ export { withRequired } from "./required.js";
@@ -9,6 +9,7 @@ export const withLeadingIcon = (config) => (component) => {
9
9
  return iconElement;
10
10
  const element = document.createElement('span');
11
11
  element.className = `${PREFIX}-${NAME}__leading-icon`;
12
+ element.setAttribute('aria-hidden', 'true');
12
13
  fieldOf(component).appendChild(element);
13
14
  component.element.classList.add(`${PREFIX}-${NAME}--with-leading-icon`);
14
15
  if (component.input) {
@@ -18,9 +18,17 @@ export const withPlacement = () => (component) => {
18
18
  const PREFIX = component.config.prefix || "mtrl";
19
19
  const COMPONENT = component.config.componentName || "textfield";
20
20
  let destroyed = false;
21
+ let listening = false;
22
+ const ensureListening = () => {
23
+ if (listening || destroyed)
24
+ return;
25
+ listening = true;
26
+ setupEventListeners();
27
+ };
21
28
  const schedulePositionUpdate = () => {
22
29
  if (destroyed)
23
30
  return;
31
+ ensureListening();
24
32
  schedule(measure);
25
33
  };
26
34
  const classObservers = new WeakMap();
@@ -97,6 +105,7 @@ export const withPlacement = () => (component) => {
97
105
  };
98
106
  };
99
107
  const updateElementPositions = () => {
108
+ ensureListening();
100
109
  measure()();
101
110
  return component;
102
111
  };
@@ -121,8 +130,12 @@ export const withPlacement = () => (component) => {
121
130
  labelObserver.observe(labelEl);
122
131
  }
123
132
  };
124
- setupEventListeners();
125
- schedulePositionUpdate();
133
+ const has = (modifier) => component.element.classList.contains(`${PREFIX}-${COMPONENT}--${modifier}`);
134
+ const needsPlacement = has("outlined") ||
135
+ has("with-leading-icon") ||
136
+ component.element.querySelector(`.${PREFIX}-${COMPONENT}__prefix, .${PREFIX}-${COMPONENT}__suffix`) !== null;
137
+ if (needsPlacement)
138
+ schedulePositionUpdate();
126
139
  if ("lifecycle" in component && component.lifecycle?.destroy) {
127
140
  const originalDestroy = component.lifecycle.destroy;
128
141
  component.lifecycle.destroy = () => {
@@ -0,0 +1,33 @@
1
+ import type { BaseComponent, ElementComponent } from "../../../core/compose/component.js";
2
+ import type { LabelManager } from "../../../core/compose/features/textlabel.js";
3
+ /**
4
+ * A required field's asterisk (FLO-301). M3's text field guidelines mark a
5
+ * required field with an asterisk after its label, and Material's own web
6
+ * text field appends it to the label text in the label's colour. Here it is a
7
+ * span after the text, hidden from screen readers: the input's native
8
+ * `required` already announces the field as required, and "star" would not.
9
+ */
10
+ export interface RequiredConfig {
11
+ /** Whether the field is required; the input gets `required` from withTextInput */
12
+ required?: boolean;
13
+ /**
14
+ * No asterisk on a required field. M3 lets a form whose fields are mostly
15
+ * required mark its optional ones instead (Material Web's `noAsterisk`).
16
+ */
17
+ noAsterisk?: boolean;
18
+ prefix?: string;
19
+ componentName?: string;
20
+ }
21
+ /** What this feature installs */
22
+ export interface RequiredFeature {
23
+ /** Makes the field required or optional, its asterisk with it */
24
+ setRequired: (required: boolean) => RequiredFeature;
25
+ /** Whether the field is required */
26
+ isRequired: () => boolean;
27
+ }
28
+ interface LabelledInputComponent extends ElementComponent {
29
+ input?: HTMLInputElement | HTMLTextAreaElement;
30
+ label?: LabelManager;
31
+ }
32
+ export declare const withRequired: <T extends RequiredConfig & object>(config: T) => <C extends LabelledInputComponent>(component: C) => C & RequiredFeature & BaseComponent;
33
+ export {};
@@ -0,0 +1,50 @@
1
+ export const withRequired = (config) => (component) => {
2
+ const PREFIX = config.prefix || "mtrl";
3
+ const NAME = config.componentName || "textfield";
4
+ const label = component.label;
5
+ let required = Boolean(config.required);
6
+ const asterisk = () => label?.getElement().querySelector(`.${PREFIX}-${NAME}__required`) ?? null;
7
+ const render = () => {
8
+ if (!label)
9
+ return;
10
+ const shown = asterisk();
11
+ if (required && !config.noAsterisk) {
12
+ if (shown)
13
+ return;
14
+ const span = document.createElement("span");
15
+ span.className = `${PREFIX}-${NAME}__required`;
16
+ span.setAttribute("aria-hidden", "true");
17
+ span.textContent = "*";
18
+ label.getElement().append(span);
19
+ }
20
+ else {
21
+ shown?.remove();
22
+ }
23
+ };
24
+ if (label) {
25
+ const setText = label.setText.bind(label);
26
+ label.setText = (text) => {
27
+ setText(text);
28
+ render();
29
+ return label;
30
+ };
31
+ label.getText = () => {
32
+ const text = label.getElement().textContent ?? "";
33
+ return asterisk() ? text.slice(0, -1) : text;
34
+ };
35
+ }
36
+ render();
37
+ return {
38
+ ...component,
39
+ setRequired(next) {
40
+ required = next;
41
+ if (component.input)
42
+ component.input.required = next;
43
+ render();
44
+ return this;
45
+ },
46
+ isRequired() {
47
+ return required;
48
+ },
49
+ };
50
+ };
@@ -14,27 +14,42 @@ export const withSupportingText = (config) => (component) => {
14
14
  else
15
15
  removeIdRef(component.input, "aria-describedby", supportingId);
16
16
  };
17
- const createSupportingElement = (text, isError = false) => {
17
+ let pendingFrame = 0;
18
+ const cancelFill = () => {
19
+ if (pendingFrame)
20
+ cancelAnimationFrame(pendingFrame);
21
+ pendingFrame = 0;
22
+ };
23
+ const createSupportingElement = () => {
18
24
  const element = document.createElement("div");
19
25
  element.className = `${PREFIX}-${COMPONENT}__helper`;
20
26
  element.id = supportingId;
21
- element.textContent = text;
22
- if (isError)
23
- element.classList.add(`${PREFIX}-${COMPONENT}__helper--error`);
27
+ element.setAttribute("aria-live", "polite");
24
28
  return element;
25
29
  };
30
+ const markError = (element, isError) => {
31
+ element.classList.toggle(`${PREFIX}-${COMPONENT}__helper--error`, isError);
32
+ };
33
+ const detach = () => {
34
+ cancelFill();
35
+ if (!supportingElement)
36
+ return;
37
+ supportingElement.remove();
38
+ supportingElement = null;
39
+ row.release();
40
+ describe(null);
41
+ };
26
42
  if (config.supportingText) {
27
- supportingElement = createSupportingElement(config.supportingText, config.error);
43
+ supportingElement = createSupportingElement();
44
+ supportingElement.textContent = config.supportingText;
45
+ markError(supportingElement, Boolean(config.error));
28
46
  show(supportingElement);
29
47
  describe(supportingElement);
30
48
  }
31
49
  if ("lifecycle" in component && component.lifecycle?.destroy) {
32
50
  const originalDestroy = component.lifecycle.destroy;
33
51
  component.lifecycle.destroy = () => {
34
- if (supportingElement) {
35
- supportingElement.remove();
36
- row.release();
37
- }
52
+ detach();
38
53
  originalDestroy.call(component.lifecycle);
39
54
  };
40
55
  }
@@ -44,22 +59,29 @@ export const withSupportingText = (config) => (component) => {
44
59
  return supportingElement;
45
60
  },
46
61
  setSupportingText(text, isError = false) {
47
- supportingElement?.remove();
48
- supportingElement = text ? createSupportingElement(text, isError) : null;
49
- if (supportingElement)
50
- show(supportingElement);
51
- else
52
- row.release();
62
+ if (!text) {
63
+ detach();
64
+ return this;
65
+ }
66
+ if (supportingElement) {
67
+ supportingElement.textContent = text;
68
+ markError(supportingElement, isError);
69
+ return this;
70
+ }
71
+ supportingElement = createSupportingElement();
72
+ supportingElement.textContent = text;
73
+ markError(supportingElement, isError);
74
+ show(supportingElement);
53
75
  describe(supportingElement);
76
+ const element = supportingElement;
77
+ pendingFrame = requestAnimationFrame(() => {
78
+ pendingFrame = 0;
79
+ element.replaceChildren(document.createTextNode(element.textContent ?? ""));
80
+ });
54
81
  return this;
55
82
  },
56
83
  removeSupportingText() {
57
- if (supportingElement) {
58
- supportingElement.remove();
59
- supportingElement = null;
60
- row.release();
61
- describe(null);
62
- }
84
+ detach();
63
85
  return this;
64
86
  },
65
87
  };
@@ -4,10 +4,18 @@ import { BaseComponent, ElementComponent } from "../../../core/compose/component
4
4
  */
5
5
  interface InputElementComponent extends ElementComponent {
6
6
  input?: HTMLInputElement | HTMLTextAreaElement;
7
+ emit?: (event: string, data?: unknown) => void;
7
8
  lifecycle?: {
8
9
  destroy: () => void;
9
10
  };
10
11
  }
12
+ /** What an interactive trailing icon's `trailing` event carries (FLO-301) */
13
+ export interface TextfieldTrailingPayload {
14
+ /** The field's value when the icon was activated */
15
+ value: string;
16
+ /** The click (Enter and Space reach a button as one) */
17
+ event: MouseEvent;
18
+ }
11
19
  /**
12
20
  * Configuration for trailing icon feature
13
21
  */
@@ -16,6 +24,18 @@ export interface TrailingIconConfig {
16
24
  * Trailing icon HTML content
17
25
  */
18
26
  trailingIcon?: string;
27
+ /**
28
+ * Makes the trailing icon a button with this accessible name: clear, show
29
+ * password, open a menu (FLO-301). M3 draws an interactive trailing icon as an
30
+ * icon button, as Compose's trailing slot holds an `IconButton`: a 40dp state
31
+ * layer and a 48dp target. Activating it emits `trailing`. Without a label the
32
+ * icon is decorative.
33
+ */
34
+ trailingIconLabel?: string;
35
+ /** Called when the trailing icon button is activated, after `trailing` is emitted */
36
+ onTrailingClick?: (event: TextfieldTrailingPayload) => void;
37
+ /** Whether the field starts disabled; the trailing button is disabled with it */
38
+ disabled?: boolean;
19
39
  /**
20
40
  * CSS class prefix
21
41
  */
@@ -45,9 +65,11 @@ export interface TrailingIconFeature {
45
65
  /**
46
66
  * Sets trailing icon content
47
67
  * @param html - HTML content for the icon
68
+ * @param label - The button's accessible name, making the icon a button; an empty
69
+ * string makes it decorative again; left out, the icon keeps what it is
48
70
  * @returns Component instance for chaining
49
71
  */
50
- setTrailingIcon: (html: string) => TrailingIconComponent;
72
+ setTrailingIcon: (html: string, label?: string) => TrailingIconComponent;
51
73
  /**
52
74
  * Removes trailing icon
53
75
  * @returns Component instance for chaining
@@ -4,12 +4,44 @@ export const withTrailingIcon = (config) => (component) => {
4
4
  const PREFIX = config.prefix || 'mtrl';
5
5
  const NAME = config.componentName || 'textfield';
6
6
  let slot = null;
7
+ let label = config.trailingIconLabel ?? '';
8
+ const activate = (event) => {
9
+ if (component.input?.disabled)
10
+ return;
11
+ const detail = { value: component.input?.value ?? '', event };
12
+ component.emit?.('trailing', detail);
13
+ config.onTrailingClick?.(detail);
14
+ };
15
+ const create = () => {
16
+ const className = `${PREFIX}-${NAME}__trailing-icon`;
17
+ if (!label) {
18
+ const element = document.createElement('span');
19
+ element.className = className;
20
+ return element;
21
+ }
22
+ const button = document.createElement('button');
23
+ button.type = 'button';
24
+ button.className = `${className} ${className}--button`;
25
+ button.setAttribute('aria-label', label);
26
+ button.disabled = Boolean(component.input?.disabled ?? config.disabled);
27
+ button.addEventListener('click', activate);
28
+ return button;
29
+ };
7
30
  const ensureSlot = () => {
8
- if (slot && slot.parentNode)
31
+ const wanted = label ? 'BUTTON' : 'SPAN';
32
+ if (slot && slot.parentNode && slot.tagName === wanted) {
33
+ if (label)
34
+ slot.setAttribute('aria-label', label);
9
35
  return slot;
10
- const element = document.createElement('span');
11
- element.className = `${PREFIX}-${NAME}__trailing-icon`;
12
- fieldOf(component).appendChild(element);
36
+ }
37
+ const element = create();
38
+ if (slot?.parentNode) {
39
+ element.append(...Array.from(slot.childNodes));
40
+ slot.replaceWith(element);
41
+ }
42
+ else {
43
+ fieldOf(component).appendChild(element);
44
+ }
13
45
  component.element.classList.add(`${PREFIX}-${NAME}--with-trailing-icon`);
14
46
  if (component.input) {
15
47
  component.input.classList.add(`${PREFIX}-${NAME}__input--with-trailing-icon`);
@@ -42,7 +74,9 @@ export const withTrailingIcon = (config) => (component) => {
42
74
  return {
43
75
  ...component,
44
76
  trailingIcon: slot,
45
- setTrailingIcon(html) {
77
+ setTrailingIcon(html, nextLabel) {
78
+ if (nextLabel !== undefined)
79
+ label = nextLabel;
46
80
  this.trailingIcon = ensureSlot();
47
81
  write(this.trailingIcon, html);
48
82
  return this;
@@ -1,2 +1,17 @@
1
1
  export { default } from "./textfield.js";
2
- export type { TextfieldConfig, TextfieldComponent, TextfieldDensity, TextfieldEvents, TextfieldValuePayload, TextfieldFocusPayload, } from "./types.js";
2
+ export { default as createTextField } from "./textfield.js";
3
+ export type { TextfieldConfig as TextFieldConfig, TextfieldComponent as TextFieldComponent,
4
+ /** @deprecated Use TextFieldConfig: M3 writes "text field" as two words. Removed in 1.0 (FLO-383). */
5
+ TextfieldConfig,
6
+ /** @deprecated Use TextFieldComponent: M3 writes "text field" as two words. Removed in 1.0 (FLO-383). */
7
+ TextfieldComponent, TextfieldDensity as TextFieldDensity, TextfieldEvents as TextFieldEvents, TextfieldValuePayload as TextFieldValuePayload, TextfieldFocusPayload as TextFieldFocusPayload, TextfieldTrailingPayload as TextFieldTrailingPayload,
8
+ /** @deprecated Use TextFieldDensity: M3 writes "text field" as two words. Removed in 1.0 (FLO-383). */
9
+ TextfieldDensity,
10
+ /** @deprecated Use TextFieldEvents: M3 writes "text field" as two words. Removed in 1.0 (FLO-383). */
11
+ TextfieldEvents,
12
+ /** @deprecated Use TextFieldValuePayload: M3 writes "text field" as two words. Removed in 1.0 (FLO-383). */
13
+ TextfieldValuePayload,
14
+ /** @deprecated Use TextFieldFocusPayload: M3 writes "text field" as two words. Removed in 1.0 (FLO-383). */
15
+ TextfieldFocusPayload,
16
+ /** @deprecated Use TextFieldTrailingPayload: M3 writes "text field" as two words. Removed in 1.0 (FLO-383). */
17
+ TextfieldTrailingPayload, } from "./types.js";
@@ -1 +1,2 @@
1
1
  export { default } from "./textfield.js";
2
+ export { default as createTextField } from "./textfield.js";
@@ -2,12 +2,12 @@ import { pipe } from "../../core/compose/index.js";
2
2
  import { createBase, withElement } from "../../core/compose/component.js";
3
3
  import { withEvents, withDisabled, withLifecycle, withVariant, withTextInput, withTextLabel, } from "../../core/compose/features/index.js";
4
4
  import { withAPI } from "./api.js";
5
- import { withLeadingIcon, withTrailingIcon, withSupportingText, withPrefixText, withSuffixText, withPlacement, withDensity, withError, withField, withCounter, } from "./features/index.js";
5
+ import { withLeadingIcon, withTrailingIcon, withSupportingText, withPrefixText, withSuffixText, withPlacement, withDensity, withError, withField, withCounter, withRequired, } from "./features/index.js";
6
6
  import { createBaseConfig, getElementConfig, getApiConfig } from "./config.js";
7
7
  const createTextfield = (config = {}) => {
8
8
  const baseConfig = createBaseConfig(config);
9
9
  try {
10
- const textfield = pipe(createBase, withEvents(), withElement(getElementConfig(baseConfig)), withLifecycle(), withVariant(baseConfig), withField(baseConfig), withTextInput(baseConfig), withDensity(baseConfig), withTextLabel(baseConfig), withLeadingIcon(baseConfig), withTrailingIcon(baseConfig), withPrefixText(baseConfig), withSuffixText(baseConfig), withSupportingText(baseConfig), withError(baseConfig), withCounter(baseConfig), withDisabled(baseConfig), withPlacement(), (comp) => withAPI(getApiConfig(comp))(comp))(baseConfig);
10
+ const textfield = pipe(createBase, withEvents(), withElement(getElementConfig(baseConfig)), withLifecycle(), withVariant(baseConfig), withField(baseConfig), withTextInput(baseConfig), withDensity(baseConfig), withTextLabel(baseConfig), withRequired(baseConfig), withLeadingIcon(baseConfig), withTrailingIcon(baseConfig), withPrefixText(baseConfig), withSuffixText(baseConfig), withSupportingText(baseConfig), withError(baseConfig), withCounter(baseConfig), withDisabled(baseConfig), withPlacement(), (comp) => withAPI(getApiConfig(comp))(comp))(baseConfig);
11
11
  return textfield;
12
12
  }
13
13
  catch (error) {
@@ -1,4 +1,6 @@
1
1
  import type { EventCallback } from "../../core/state/emitter.js";
2
+ import type { TextfieldTrailingPayload } from "./features/trailing-icon.js";
3
+ export type { TextfieldTrailingPayload };
2
4
  /**
3
5
  * Available Textfield variants
4
6
  */
@@ -60,8 +62,13 @@ export interface TextfieldConfig {
60
62
  value?: string;
61
63
  /** Placeholder text */
62
64
  placeholder?: string;
63
- /** Whether input is required */
65
+ /** Whether input is required; the label gets an asterisk (FLO-301) */
64
66
  required?: boolean;
67
+ /**
68
+ * No asterisk on a required field: M3 lets a form whose fields are mostly
69
+ * required mark its optional ones instead (FLO-301)
70
+ */
71
+ noAsterisk?: boolean;
65
72
  /** Whether textfield is disabled */
66
73
  disabled?: boolean;
67
74
  /** Whether textfield is readonly */
@@ -76,6 +83,15 @@ export interface TextfieldConfig {
76
83
  leadingIcon?: string;
77
84
  /** Trailing icon HTML content */
78
85
  trailingIcon?: string;
86
+ /**
87
+ * Makes the trailing icon a button with this accessible name (clear, show
88
+ * password, …), emitting `trailing` when activated. Without it the icon is
89
+ * decorative. M3 draws an interactive trailing icon as an icon button, as
90
+ * Compose's trailing slot holds an `IconButton` (FLO-301).
91
+ */
92
+ trailingIconLabel?: string;
93
+ /** Called when the trailing icon button is activated, after `trailing` is emitted */
94
+ onTrailingClick?: (event: TextfieldTrailingPayload) => void;
79
95
  /** Supporting text content */
80
96
  supportingText?: string;
81
97
  /** Whether supporting text indicates an error */
@@ -103,6 +119,8 @@ export interface TextfieldFocusPayload {
103
119
  }
104
120
  /** Events emitted by the text input feature. setValue() is silent. */
105
121
  export interface TextfieldEvents {
122
+ /** The trailing icon button was activated (`trailingIconLabel`; FLO-301) */
123
+ trailing: (payload: TextfieldTrailingPayload) => void;
106
124
  input: (payload: TextfieldValuePayload) => void;
107
125
  change: (payload: TextfieldValuePayload) => void;
108
126
  focus: (payload: TextfieldFocusPayload) => void;
@@ -148,9 +166,17 @@ export interface TextfieldComponent {
148
166
  /** Trailing icon element (if present) */
149
167
  trailingIcon: HTMLElement | null;
150
168
  /** Sets the trailing icon HTML content */
151
- setTrailingIcon: (html: string) => TextfieldComponent;
169
+ /**
170
+ * Sets the trailing icon. `label` makes it a button with that accessible name,
171
+ * an empty one makes it decorative; left out, the icon keeps what it is.
172
+ */
173
+ setTrailingIcon: (html: string, label?: string) => TextfieldComponent;
152
174
  /** Removes the trailing icon */
153
175
  removeTrailingIcon: () => TextfieldComponent;
176
+ /** Makes the field required or optional, the label's asterisk with it (FLO-301) */
177
+ setRequired: (required: boolean) => TextfieldComponent;
178
+ /** Whether the field is required */
179
+ isRequired: () => boolean;
154
180
  /** Supporting text element (if present) */
155
181
  supportingTextElement: HTMLElement | null;
156
182
  /** Sets the supporting text content */
@@ -187,6 +213,8 @@ export interface TextfieldComponent {
187
213
  enable: () => TextfieldComponent;
188
214
  /** Disables the textfield */
189
215
  disable: () => TextfieldComponent;
216
+ /** Whether the text field is disabled (FLO-384) */
217
+ isDisabled: () => boolean;
190
218
  /** Destroys the textfield component and cleans up resources */
191
219
  destroy: () => void;
192
220
  }
@@ -197,6 +225,7 @@ export interface ApiOptions {
197
225
  disabled: {
198
226
  enable: () => void;
199
227
  disable: () => void;
228
+ isDisabled: () => boolean;
200
229
  };
201
230
  lifecycle: {
202
231
  destroy: () => void;
@@ -227,7 +256,7 @@ export interface BaseComponent {
227
256
  setLeadingIcon?: (html: string) => void;
228
257
  removeLeadingIcon?: () => void;
229
258
  trailingIcon?: HTMLElement | null;
230
- setTrailingIcon?: (html: string) => void;
259
+ setTrailingIcon?: (html: string, label?: string) => void;
231
260
  removeTrailingIcon?: () => void;
232
261
  supportingTextElement?: HTMLElement | null;
233
262
  setSupportingText?: (text: string, isError?: boolean) => void;
@@ -244,6 +273,7 @@ export interface BaseComponent {
244
273
  disabled?: {
245
274
  enable: () => void;
246
275
  disable: () => void;
276
+ isDisabled: () => boolean;
247
277
  };
248
278
  lifecycle?: {
249
279
  destroy: () => void;