kmaterialize 2.3.3-1.0.25 → 2.3.3-1.0.27

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 (31) hide show
  1. package/components/air-datepicker/air-datepicker.ts +2 -2
  2. package/components/appbar/navbar-content.scss +317 -0
  3. package/components/appbar/navbar-scroll.scss +101 -0
  4. package/components/appbar/navbar-scroll.ts +67 -0
  5. package/components/dropdown/_dropdown.scss +7 -1
  6. package/components/extensions/web/crazy-button.scss +9 -0
  7. package/components/extensions/web/crazy-button.ts +13 -3
  8. package/components/maskito-input/maskito-input.ts +139 -0
  9. package/components/navigation-drawer/navigation-rail.scss +30 -0
  10. package/components/popup/popup-stepper.scss +79 -0
  11. package/components/popup/popup-stepper.ts +162 -0
  12. package/components/popup/popup.ts +13 -0
  13. package/components/popup/popupSpec.js +17 -0
  14. package/components/radio-button/_radio-buttons.scss +18 -0
  15. package/components/rich-textarea/_rich-textarea.scss +104 -0
  16. package/components/rich-textarea/rich-textarea.ts +265 -0
  17. package/components/slider/_range.scss +130 -139
  18. package/components/slider/range.ts +162 -188
  19. package/components/switch/_switches.scss +30 -0
  20. package/components/tabs/_tabs.scss +16 -0
  21. package/dist/css/materialize.colors.min.css +1 -1
  22. package/dist/css/materialize.css +1360 -117
  23. package/dist/css/materialize.min.css +2 -2
  24. package/dist/css/materialize.min.css.map +1 -1
  25. package/dist/js/materialize.cjs.js +1202 -503
  26. package/dist/js/materialize.d.ts +165 -29
  27. package/dist/js/materialize.js +1202 -503
  28. package/dist/js/materialize.min.js +3 -3
  29. package/dist/js/materialize.mjs +1200 -504
  30. package/package.json +16 -1
  31. package/sass/materialize.scss +9 -0
@@ -111,8 +111,8 @@ export class AirDatepickerField extends Component<AirDatepickerFieldOptions> {
111
111
 
112
112
  const dataset = this.el.dataset;
113
113
  const builtInLocale = dataset.dateLang === 'fr-FR'
114
- ? (await import('air-datepicker/locale/fr')).default
115
- : (await import('air-datepicker/locale/en')).default;
114
+ ? (await import('air-datepicker/locale/fr.js')).default
115
+ : (await import('air-datepicker/locale/en.js')).default;
116
116
  const pickerOptions: AirDatepickerOptions = {
117
117
  dateFormat: dataset.dateFormat || 'yyyy-MM-dd',
118
118
  autoClose: dataset.dateAutoClose !== 'false',
@@ -0,0 +1,317 @@
1
+ /* App-bar content geometry and surface variants. */
2
+ .navbar .nav-icon-img {
3
+ display: block;
4
+ height: 24px;
5
+ width: 24px;
6
+ object-fit: contain;
7
+ }
8
+
9
+ .navbar .navbar-search-toolbar {
10
+ width: auto;
11
+ margin: 0 0 0 auto;
12
+ padding: 4px;
13
+ overflow: visible;
14
+ }
15
+
16
+ /* Inset surfaces retain their height at desktop and mobile sizes. */
17
+ .navbar.appbar-bordered.navbar-rounded:not(.nav-extended):not(.navbar-wrap) {
18
+ box-sizing: border-box;
19
+ height: 64px;
20
+ margin: 8px 0;
21
+ padding: 4px;
22
+ border-radius: 16px;
23
+ overflow: hidden;
24
+ }
25
+
26
+ .navbar.appbar-bordered.navbar-rounded:not(.nav-extended):not(.navbar-wrap) > .nav-wrapper {
27
+ box-sizing: border-box;
28
+ flex: 0 0 56px;
29
+ height: 56px;
30
+ border-radius: 11px;
31
+ transform: none;
32
+ }
33
+
34
+ .navbar.appbar-bordered.navbar-full {
35
+ width: 100%;
36
+ margin: 0;
37
+ padding: 0;
38
+ border-radius: 0;
39
+ border-left: 0;
40
+ border-right: 0;
41
+ }
42
+
43
+ /* Dropdown triggers: keep the label and arrow in one predictable hit
44
+ target instead of inheriting the legacy floated-icon offsets. */
45
+ .navbar.appbar-bordered .dropdown-trigger {
46
+ display: inline-flex !important;
47
+ align-items: center;
48
+ justify-content: center;
49
+ gap: 8px;
50
+ width: max-content !important;
51
+ max-width: none;
52
+ height: 40px;
53
+ min-height: 40px;
54
+ padding: 0 14px 0 16px;
55
+ line-height: 1;
56
+ }
57
+
58
+ .navbar.appbar-bordered .dropdown-trigger > .material-icons {
59
+ display: inline-block !important;
60
+ position: static !important;
61
+ float: none !important;
62
+ width: 20px;
63
+ height: 20px;
64
+ margin: 0;
65
+ line-height: 20px;
66
+ transition: transform 160ms ease;
67
+ }
68
+
69
+ .navbar.appbar-bordered .nav-wrapper > ul > li:has(> .dropdown-trigger) {
70
+ flex: 0 0 auto;
71
+ display: flex;
72
+ align-items: center;
73
+ height: 40px;
74
+ margin: 0;
75
+ padding: 0;
76
+ }
77
+
78
+ .navbar.appbar-bordered .nav-wrapper > ul > li:has(> .dropdown-trigger) > .dropdown-trigger {
79
+ flex: 0 0 auto;
80
+ align-self: center;
81
+ margin: 0;
82
+ }
83
+
84
+ .navbar.appbar-bordered .dropdown-trigger[aria-expanded="true"] > .material-icons.right {
85
+ transform: rotate(180deg);
86
+ }
87
+
88
+ /* Icon links: center standalone icons inside their 40px hit targets. */
89
+ .navbar.appbar-bordered .nav-wrapper > ul > li:has(> a:not(.btn):not(.btn-large):not(.btn-floating):not(.dropdown-trigger) > .material-icons:only-child) {
90
+ display: flex;
91
+ align-items: center;
92
+ justify-content: center;
93
+ flex: 0 0 40px;
94
+ width: 40px;
95
+ height: 40px;
96
+ margin: 0;
97
+ padding: 0;
98
+ }
99
+
100
+ .navbar.appbar-bordered .nav-wrapper > ul > li > a:not(.btn):not(.btn-large):not(.btn-floating):not(.dropdown-trigger):has(> .material-icons:only-child) {
101
+ display: inline-grid !important;
102
+ place-items: center;
103
+ box-sizing: border-box;
104
+ width: 40px !important;
105
+ height: 40px;
106
+ min-height: 40px;
107
+ padding: 0 !important;
108
+ line-height: 1;
109
+ }
110
+
111
+ .navbar.appbar-bordered .nav-wrapper > ul > li > a:not(.btn):not(.btn-large):not(.btn-floating):not(.dropdown-trigger) > .material-icons:only-child {
112
+ display: block !important;
113
+ position: static !important;
114
+ float: none !important;
115
+ width: 24px;
116
+ height: 24px;
117
+ margin: 0 !important;
118
+ line-height: 24px;
119
+ text-align: center;
120
+ }
121
+
122
+ .navbar.appbar-bordered .nav-wrapper > ul > li > a:not(.btn):not(.btn-large):not(.btn-floating):not(.dropdown-trigger):has(> .material-icons.left),
123
+ .navbar.appbar-bordered .nav-wrapper > ul > li > a:not(.btn):not(.btn-large):not(.btn-floating):not(.dropdown-trigger):has(> .material-icons.right) {
124
+ display: inline-flex !important;
125
+ align-items: center;
126
+ gap: 8px;
127
+ box-sizing: border-box;
128
+ width: max-content !important;
129
+ min-width: 0;
130
+ height: 40px;
131
+ min-height: 40px;
132
+ padding: 0 12px !important;
133
+ line-height: 1;
134
+ white-space: nowrap;
135
+ }
136
+
137
+ .navbar.appbar-bordered .nav-wrapper > ul > li > a:not(.btn):not(.btn-large):not(.btn-floating):not(.dropdown-trigger) > .material-icons.left,
138
+ .navbar.appbar-bordered .nav-wrapper > ul > li > a:not(.btn):not(.btn-large):not(.btn-floating):not(.dropdown-trigger) > .material-icons.right {
139
+ display: block !important;
140
+ position: static !important;
141
+ float: none !important;
142
+ flex: 0 0 24px;
143
+ width: 24px;
144
+ height: 24px;
145
+ margin: 0 !important;
146
+ line-height: 24px;
147
+ text-align: center;
148
+ }
149
+
150
+ .navbar.appbar-bordered .nav-wrapper > ul > li:has(> a:not(.btn):not(.btn-large):not(.btn-floating):not(.dropdown-trigger) > .material-icons.left),
151
+ .navbar.appbar-bordered .nav-wrapper > ul > li:has(> a:not(.btn):not(.btn-large):not(.btn-floating):not(.dropdown-trigger) > .material-icons.right) {
152
+ flex: 0 0 auto;
153
+ width: max-content;
154
+ height: 40px;
155
+ }
156
+
157
+ /* Button rows: normalize the item row and prevent text-plus-icon buttons
158
+ from being mistaken for standalone icon buttons. */
159
+ .navbar.appbar-bordered .nav-wrapper > ul > li:has(> :is(.btn, .btn-large)) {
160
+ display: flex;
161
+ align-items: center;
162
+ flex: 0 0 auto;
163
+ height: 48px;
164
+ margin: 0;
165
+ padding: 0;
166
+ }
167
+
168
+ .navbar.appbar-bordered .nav-wrapper > ul > li > a.btn,
169
+ .navbar.appbar-bordered .nav-wrapper > ul > li > a.btn-large {
170
+ display: inline-flex !important;
171
+ align-items: center;
172
+ justify-content: center;
173
+ box-sizing: border-box;
174
+ width: max-content !important;
175
+ min-width: 104px;
176
+ height: 40px;
177
+ min-height: 40px;
178
+ margin: 0 !important;
179
+ padding: 0 20px !important;
180
+ line-height: 1;
181
+ white-space: nowrap;
182
+ }
183
+
184
+ .navbar.appbar-bordered .nav-wrapper > ul > li > a.btn-large {
185
+ min-width: 144px;
186
+ height: 48px;
187
+ min-height: 48px;
188
+ padding-inline: 24px !important;
189
+ }
190
+
191
+ .navbar.appbar-bordered .nav-wrapper > ul > li > a.btn:has(> .material-icons),
192
+ .navbar.appbar-bordered .nav-wrapper > ul > li > a.btn-large:has(> .material-icons) {
193
+ gap: 8px;
194
+ }
195
+
196
+ .navbar.appbar-bordered .nav-wrapper > ul > li > a.btn > .material-icons,
197
+ .navbar.appbar-bordered .nav-wrapper > ul > li > a.btn-large > .material-icons {
198
+ display: block !important;
199
+ position: static !important;
200
+ float: none !important;
201
+ flex: 0 0 20px;
202
+ width: 20px;
203
+ height: 20px;
204
+ margin: 0 !important;
205
+ line-height: 20px;
206
+ }
207
+
208
+ /* Extended navbar tabs: use the navbar's readable foreground instead of the
209
+ low-contrast primary token inherited by transparent tabs. */
210
+ .navbar.appbar-bordered .nav-content,
211
+ .navbar.appbar-bordered .nav-content .tabs {
212
+ background: var(--md-sys-color-surface-variant);
213
+ }
214
+
215
+ .navbar.appbar-bordered .nav-content .tabs > .tab > a {
216
+ color: var(--md-sys-color-on-surface) !important;
217
+ opacity: 0.92;
218
+ }
219
+
220
+ .navbar.appbar-bordered .nav-content .tabs > .tab > a.active {
221
+ color: var(--md-sys-color-on-surface) !important;
222
+ opacity: 1;
223
+ font-weight: 600;
224
+ }
225
+
226
+ .navbar.appbar-bordered .nav-content .tabs > .tab.disabled > a {
227
+ color: var(--md-sys-color-on-surface) !important;
228
+ opacity: 0.5;
229
+ }
230
+
231
+ .navbar.appbar-bordered .nav-content .tabs > .indicator {
232
+ background-color: var(--md-sys-color-on-surface) !important;
233
+ }
234
+
235
+ .navbar.appbar-bordered .sidenav-trigger {
236
+ display: inline-flex !important;
237
+ align-items: center;
238
+ justify-content: center;
239
+ flex: 0 0 40px;
240
+ width: 40px;
241
+ height: 40px;
242
+ min-height: 40px;
243
+ margin: 0;
244
+ padding: 0;
245
+ line-height: 1;
246
+ color: var(--md-sys-color-on-surface) !important;
247
+ }
248
+
249
+ .navbar.appbar-bordered .sidenav-trigger > .material-icons {
250
+ display: block !important;
251
+ position: static !important;
252
+ float: none !important;
253
+ width: 24px;
254
+ height: 24px;
255
+ margin: 0 !important;
256
+ line-height: 24px;
257
+ text-align: center;
258
+ }
259
+
260
+ .navbar.appbar-bordered.navbar-wrap > .nav-wrapper {
261
+ flex-wrap: wrap;
262
+ height: auto;
263
+ min-height: 56px;
264
+ padding-bottom: 8px;
265
+ overflow: visible;
266
+ row-gap: 8px;
267
+ }
268
+
269
+ .navbar.appbar-bordered.navbar-wrap {
270
+ height: auto;
271
+ min-height: 56px;
272
+ }
273
+
274
+ .navbar.appbar-bordered.navbar-wrap > .nav-wrapper > ul:not(.dropdown-content) {
275
+ box-sizing: border-box;
276
+ width: 100%;
277
+ max-width: 100%;
278
+ min-width: 0;
279
+ flex: 1 1 100%;
280
+ padding-left: 0;
281
+ height: auto !important;
282
+ flex-wrap: wrap;
283
+ white-space: normal;
284
+ }
285
+
286
+ .navbar.appbar-bordered.navbar-wrap > .nav-wrapper > ul:not(.dropdown-content) > li {
287
+ height: auto !important;
288
+ }
289
+
290
+ .navbar.appbar-bordered ul:not(.dropdown-content) > li > a {
291
+ white-space: nowrap;
292
+ }
293
+
294
+ .navbar.appbar-bordered:not(.navbar-wrap) .nav-wrapper > ul.ml-auto:not(.nav-actions):not(.dropdown-content) {
295
+ flex: 0 0 auto;
296
+ width: max-content;
297
+ max-width: none;
298
+ }
299
+
300
+ /* Separators used as list items need an explicit display/height; otherwise
301
+ navbar item rules can collapse the one-pixel divider. */
302
+ .navbar.appbar-bordered .nav-actions > li.nav-separator {
303
+ display: block !important;
304
+ flex: 0 0 1px;
305
+ width: 1px;
306
+ height: 24px;
307
+ margin: 0 4px;
308
+ padding: 0;
309
+ background: var(--md-sys-color-on-surface-variant);
310
+ opacity: 0.7;
311
+ border: 0;
312
+ visibility: visible !important;
313
+ }
314
+
315
+ @media (prefers-reduced-motion: reduce) {
316
+ .navbar.appbar-bordered .dropdown-trigger > .material-icons { transition: none; }
317
+ }
@@ -0,0 +1,101 @@
1
+ /* Crowded navigation options. Scroll keeps one compact row; wrap grows the
2
+ bar vertically and preserves every item without clipping. */
3
+ .navbar.appbar-bordered.navbar-scroll > .nav-wrapper {
4
+ position: relative;
5
+ flex-wrap: nowrap;
6
+ white-space: nowrap;
7
+ overflow-x: auto;
8
+ overflow-y: hidden;
9
+ }
10
+
11
+ .navbar.appbar-bordered.navbar-scroll > .nav-wrapper > ul:not(.dropdown-content) {
12
+ flex: 0 0 auto;
13
+ width: max-content;
14
+ min-width: max-content;
15
+ }
16
+
17
+ .navbar.appbar-bordered.navbar-scroll {
18
+ position: relative;
19
+ isolation: isolate;
20
+ }
21
+
22
+ .navbar.appbar-bordered.navbar-scroll > .nav-wrapper {
23
+ position: relative;
24
+ z-index: 0;
25
+ }
26
+
27
+ .navbar.appbar-bordered.navbar-scroll::after {
28
+ content: '';
29
+ position: absolute;
30
+ z-index: 20;
31
+ right: 0;
32
+ top: 0;
33
+ width: 15px;
34
+ height: 100%;
35
+ pointer-events: none;
36
+ opacity: var(--scroll-right-fade, 1) !important;
37
+ background: linear-gradient(
38
+ to right,
39
+ transparent,
40
+ var(--md-sys-color-surface-variant)
41
+ );
42
+ transition: opacity 160ms ease;
43
+ }
44
+
45
+ .navbar.appbar-bordered.navbar-scroll::before {
46
+ content: '';
47
+ position: absolute;
48
+ z-index: 20;
49
+ left: 0;
50
+ top: 0;
51
+ width: 15px;
52
+ height: 100%;
53
+ pointer-events: none;
54
+ opacity: var(--scroll-left-fade, 1) !important;
55
+ background: linear-gradient(
56
+ to left,
57
+ transparent,
58
+ var(--md-sys-color-surface-variant)
59
+ );
60
+ transition: opacity 160ms ease;
61
+ }
62
+
63
+ .navbar.appbar-bordered.navbar-scroll:not(.is-scroll-end)::after {
64
+ opacity: var(--scroll-right-fade, 0) !important;
65
+ }
66
+
67
+ .navbar.appbar-bordered.navbar-scroll.is-scroll-start::before {
68
+ opacity: 0;
69
+ }
70
+
71
+ .navbar.appbar-bordered.navbar-scroll:not(.is-scroll-start)::before {
72
+ opacity: var(--scroll-left-fade, 0) !important;
73
+ }
74
+
75
+ /* Fixed edge masks. These are real children of the navbar (not descendants
76
+ of the scrolling row), so they cannot move with the links. */
77
+ .navbar.appbar-bordered.navbar-scroll > .navbar-scroll-fade {
78
+ display: block;
79
+ position: absolute;
80
+ z-index: 1000;
81
+ top: 0;
82
+ width: 28px;
83
+ height: 100%;
84
+ pointer-events: none;
85
+ opacity: 1;
86
+ transition: opacity 160ms ease;
87
+ }
88
+
89
+ .navbar.appbar-bordered.navbar-scroll > .navbar-scroll-fade-left {
90
+ left: 0;
91
+ background: linear-gradient(to left, transparent, var(--md-sys-color-surface-variant));
92
+ }
93
+
94
+ .navbar.appbar-bordered.navbar-scroll > .navbar-scroll-fade-right {
95
+ right: 0;
96
+ background: linear-gradient(to right, transparent, var(--md-sys-color-surface-variant));
97
+ }
98
+
99
+ .navbar.appbar-bordered.navbar-scroll > .navbar-scroll-fade.is-hidden {
100
+ opacity: 0;
101
+ }
@@ -0,0 +1,67 @@
1
+ const instances = new WeakMap<HTMLElement, () => void>();
2
+
3
+ /** Initialize navbar overflow fades. Returns cleanup; safe to reinitialize. */
4
+ export function initNavbarScroll(navbar: HTMLElement): () => void {
5
+ instances.get(navbar)?.();
6
+ const row = navbar.querySelector<HTMLElement>(':scope > .nav-wrapper');
7
+ if (!row) return () => {};
8
+ const left = document.createElement('span');
9
+ const right = document.createElement('span');
10
+ left.className = 'navbar-scroll-fade navbar-scroll-fade-left';
11
+ right.className = 'navbar-scroll-fade navbar-scroll-fade-right';
12
+ left.setAttribute('aria-hidden', 'true');
13
+ right.setAttribute('aria-hidden', 'true');
14
+ navbar.append(left, right);
15
+ const previous = ['--scroll-left-fade', '--scroll-right-fade'].map(name => ({
16
+ name, value: navbar.style.getPropertyValue(name), priority: navbar.style.getPropertyPriority(name)
17
+ }));
18
+ const start = navbar.classList.contains('is-scroll-start');
19
+ const end = navbar.classList.contains('is-scroll-end');
20
+ let disposed = false;
21
+ const update = () => {
22
+ if (disposed) return;
23
+ const max = Math.max(0, row.scrollWidth - row.clientWidth);
24
+ const rtl = getComputedStyle(row).direction === 'rtl';
25
+ const offset = Math.min(max, Math.max(0, rtl ? -row.scrollLeft : row.scrollLeft));
26
+ const atLeft = max <= 1 || (rtl ? offset >= max - 1 : offset <= 1);
27
+ const atRight = max <= 1 || (rtl ? offset <= 1 : offset >= max - 1);
28
+ navbar.classList.toggle('is-scroll-start', atLeft);
29
+ navbar.classList.toggle('is-scroll-end', atRight);
30
+ navbar.style.setProperty('--scroll-left-fade', atLeft ? '0' : '1');
31
+ navbar.style.setProperty('--scroll-right-fade', atRight ? '0' : '1');
32
+ left.classList.toggle('is-hidden', atLeft);
33
+ right.classList.toggle('is-hidden', atRight);
34
+ };
35
+ const resize = typeof ResizeObserver === 'undefined' ? undefined : new ResizeObserver(update);
36
+ const observeSizes = () => {
37
+ resize?.disconnect();
38
+ resize?.observe(row);
39
+ Array.from(row.children).forEach(child => resize?.observe(child));
40
+ update();
41
+ };
42
+ const mutation = new MutationObserver(observeSizes);
43
+ mutation.observe(row, { childList: true, subtree: true, characterData: true });
44
+ row.addEventListener('scroll', update, { passive: true });
45
+ window.addEventListener('resize', update);
46
+ document.fonts?.ready.then(update);
47
+ observeSizes();
48
+ const dispose = () => {
49
+ if (disposed) return;
50
+ disposed = true;
51
+ row.removeEventListener('scroll', update);
52
+ window.removeEventListener('resize', update);
53
+ resize?.disconnect();
54
+ mutation.disconnect();
55
+ left.remove();
56
+ right.remove();
57
+ navbar.classList.toggle('is-scroll-start', start);
58
+ navbar.classList.toggle('is-scroll-end', end);
59
+ previous.forEach(({ name, value, priority }) => {
60
+ if (value) navbar.style.setProperty(name, value, priority);
61
+ else navbar.style.removeProperty(name);
62
+ });
63
+ if (instances.get(navbar) === dispose) instances.delete(navbar);
64
+ };
65
+ instances.set(navbar, dispose);
66
+ return dispose;
67
+ }
@@ -16,6 +16,7 @@
16
16
  display: none;
17
17
  min-width: 100px;
18
18
  border-radius: 12px;
19
+ border: 1px solid var(--md-sys-color-outline-variant);
19
20
  overflow-y: auto;
20
21
  overflow-x: hidden;
21
22
  opacity: 0;
@@ -42,7 +43,7 @@
42
43
  }
43
44
  & > a, & > span {
44
45
  font-size: 16px;
45
- color: var(--md-sys-color-primary);
46
+ color: var(--md-sys-color-on-surface);
46
47
  display: block;
47
48
  line-height: 22px;
48
49
  padding: (($dropdown-item-height - 22) * 0.5) 16px;
@@ -61,6 +62,11 @@
61
62
  width: 24px;
62
63
  }
63
64
  }
65
+ > li.disabled > a,
66
+ > li.disabled > span {
67
+ color: color-mix(in srgb, var(--md-sys-color-on-surface) 38%, transparent);
68
+ }
69
+
64
70
  li:not(.disabled):hover, li.active {
65
71
  background-color: color-mix(in srgb, var(--md-sys-color-surface), var(--md-sys-color-on-surface) 8%);
66
72
  }
@@ -4,6 +4,15 @@
4
4
  max-width: 100%;
5
5
  > .btn, > .btn-split > .btn { position: relative; }
6
6
  .btn > img { width: var(--button-icon); height: var(--button-icon); object-fit: contain; }
7
+ // Image-only buttons fill the control and follow its round or square shape.
8
+ .btn.btn-icon[part="button"] > img {
9
+ display: block;
10
+ width: 100%;
11
+ height: 100%;
12
+ flex-shrink: 0;
13
+ object-fit: cover;
14
+ border-radius: inherit;
15
+ }
7
16
  .btn > i { color: inherit; flex-shrink: 0; }
8
17
  slot { display: contents; }
9
18
  &[size="small"] .btn { --button-height: 30px; }
@@ -9,6 +9,14 @@ const boolean = () => ({ type: 'boolean' as const, default: false, reflect: true
9
9
  const escape = (value: unknown) => String(value).replace(/[&<>"']/g, char => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[char]!);
10
10
  const sizes = { small: 'xs', normal: 'sm', large: 'md', 'extra-large': 'lg' };
11
11
 
12
+ // Palette names from sass/_colors.scss take precedence over CSS named colors.
13
+ const paletteColors = new Set([
14
+ 'materialize-red', 'red', 'pink', 'purple', 'deep-purple', 'indigo',
15
+ 'blue', 'light-blue', 'cyan', 'teal', 'green', 'light-green', 'lime',
16
+ 'yellow', 'amber', 'orange', 'deep-orange', 'brown', 'blue-grey',
17
+ 'grey', 'gold', 'black', 'white', 'transparent',
18
+ ]);
19
+
12
20
  /** RegularBtn's attribute API, backed by the renamed Crazycomponent2 runtime. */
13
21
  export default class CrazyButton extends Kmcomponent {
14
22
  static properties: KmcomponentProperties = {
@@ -123,7 +131,8 @@ export default class CrazyButton extends Kmcomponent {
123
131
  button.removeEventListener('click', click);
124
132
  });
125
133
  const trigger = this.querySelector<HTMLElement>('.dropdown-trigger');
126
- const menu = document.getElementById(String(a['menu-target']));
134
+ const menuTarget = String(a['menu-target'] ?? '');
135
+ const menu = trigger && menuTarget ? document.getElementById(menuTarget) : null;
127
136
  if (trigger && menu && !a.disabled) {
128
137
  const parent = menu.parentNode;
129
138
  const next = menu.nextSibling;
@@ -146,8 +155,9 @@ export default class CrazyButton extends Kmcomponent {
146
155
  private restoreFocus = false;
147
156
 
148
157
  private applyColor(element: HTMLElement, color: string, foreground: boolean): void {
149
- if (!color.trim() || this.getProperty('disabled')) return;
150
- if (CSS.supports('color', color)) {
158
+ color = color.trim();
159
+ if (!color || this.getProperty('disabled')) return;
160
+ if (!paletteColors.has(color) && CSS.supports('color', color)) {
151
161
  element.style.setProperty(foreground ? 'color' : 'background-color', color);
152
162
  if (foreground) element.style.borderColor = color;
153
163
  } else {