kmaterialize 2.3.3-1.0.35 → 2.3.3-1.0.36

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.
@@ -36,7 +36,6 @@
36
36
  right: 6px;
37
37
 
38
38
  > .btn {
39
- --button-icon: 24px;
40
39
  color: inherit;
41
40
  }
42
41
  }
@@ -89,6 +89,17 @@ describe("CodeCard", function(){
89
89
  });
90
90
 
91
91
  it("highlights JavaScript while preserving the source text", async function(){
92
+ // Load this test's peer explicitly instead of depending on Editor test order.
93
+ if(!window.Prism){
94
+ await new Promise((resolve, reject) => {
95
+ const script = document.createElement("script");
96
+ script.dataset.manual = "";
97
+ script.src = "/__spec__/node_modules/prismjs/prism.js";
98
+ script.onload = resolve;
99
+ script.onerror = () => reject(new Error("Failed to load Prism"));
100
+ document.head.append(script);
101
+ });
102
+ }
92
103
  instance = M.CodeCard.init(host);
93
104
  await instance.ready;
94
105
  expect(host.querySelectorAll(".token").length).toBeGreaterThan(0);
@@ -273,7 +273,9 @@ describe('Editor Handlebars workspace', function () {
273
273
  script.onload = () => resolve(); script.onerror = () => reject(new Error('Failed to load Prism'));
274
274
  document.head.append(script);
275
275
  });
276
- await load('prism.js'); await load('components/prism-markup-templating.js'); await load('components/prism-handlebars.js');
276
+ // Preserve the Prism instance already used by other components and the peer cache.
277
+ if (!window.Prism) await load('prism.js');
278
+ await load('components/prism-markup-templating.js'); await load('components/prism-handlebars.js');
277
279
  const template = '<h1>{{name}}</h1>\n<style>h1 { color: red; }</style>\n' + 'x'.repeat(200);
278
280
  await start({ template, data: { name: 'Ada' }, highlight: true });
279
281
  const input = host.querySelector('textarea'), mirror = host.querySelector('.editor-highlight');
@@ -33,6 +33,7 @@ export default class CrazyButton extends Kmcomponent {
33
33
  'icon-position': string('right', ['left', 'right']),
34
34
  'color-primary': string(), 'color-secondary': string(),
35
35
  'data-view': string(), 'aria-current': string(),
36
+ 'aria-controls': string(), 'aria-pressed': string(),
36
37
  href: string(), target: string('_self', ['_self', '_blank']),
37
38
  variant: string('', ['', 'filled', 'tonal', 'outlined', 'elevated', 'text', 'standard']),
38
39
  disabled: boolean(), toggle: boolean(), pressed: boolean(),
@@ -84,7 +85,8 @@ export default class CrazyButton extends Kmcomponent {
84
85
  ${a['aria-label'] || iconOnly ? `aria-label="${escape(a['aria-label'] || a.label || a['icon-text'] || 'Button')}"` : ''}
85
86
  ${a['data-view'] ? `data-view="${escape(a['data-view'])}"` : ''}
86
87
  ${a['aria-current'] ? `aria-current="${escape(a['aria-current'])}"` : ''}
87
- ${a.toggle ? `aria-pressed="${a.pressed}"` : ''} ${menu ? menuAttrs : ''}>${content}</${tag}>`;
88
+ ${a.toggle ? `aria-pressed="${a.pressed}"` : a['aria-pressed'] ? `aria-pressed="${escape(a['aria-pressed'])}"` : ''}
89
+ ${a['aria-controls'] && !menu ? `aria-controls="${escape(a['aria-controls'])}"` : ''} ${menu ? menuAttrs : ''}>${content}</${tag}>`;
88
90
  return a.split && a['menu-target']
89
91
  ? `<span class="btn-split">${button}<button type="button" class="${escape(classes)} btn-icon btn-menu-trigger dropdown-trigger no-autoinit" ${menuAttrs} aria-label="${escape(a['menu-label'])}" ${a.disabled ? 'disabled' : ''}><i class="material-icons" aria-hidden="true">arrow_drop_down</i></button></span>`
90
92
  : button;
@@ -31,12 +31,12 @@ export class PasswordInput extends Component<PasswordInputOptions> {
31
31
  this._suffixEl = this.el.parentElement?.querySelector<HTMLElement>('[data-password-toggle-icon]') ?? null;
32
32
 
33
33
  if (this._suffixEl) {
34
- for (const name of ['type', 'role', 'tabindex', 'aria-label', 'aria-pressed', 'aria-controls', 'aria-disabled', 'disabled'])
34
+ for (const name of ['type', 'role', 'tabindex', 'aria-label', 'aria-pressed', 'aria-controls', 'aria-disabled', 'disabled', 'icon-text'])
35
35
  this._originalAttributes.set(name, this._suffixEl.getAttribute(name));
36
36
 
37
37
  if (this._suffixEl instanceof HTMLButtonElement) {
38
38
  this._suffixEl.type = 'button';
39
- } else {
39
+ } else if (!this._isCrazyButton()) {
40
40
  this._suffixEl.setAttribute('role', 'button');
41
41
  this._suffixEl.tabIndex = 0;
42
42
  }
@@ -96,6 +96,10 @@ export class PasswordInput extends Component<PasswordInputOptions> {
96
96
  this._syncState();
97
97
  }
98
98
 
99
+ private _isCrazyButton(): boolean {
100
+ return this._suffixEl?.matches('crazy-button, regular-btn') ?? false;
101
+ }
102
+
99
103
  private _syncState() {
100
104
  const visible = this.el.type === 'text';
101
105
  const disabled = this.el.matches(':disabled');
@@ -104,8 +108,13 @@ export class PasswordInput extends Component<PasswordInputOptions> {
104
108
  this._suffixEl?.setAttribute('aria-pressed', String(visible));
105
109
  this._suffixEl?.setAttribute('aria-disabled', String(disabled));
106
110
  if (this._suffixEl instanceof HTMLButtonElement) this._suffixEl.disabled = disabled;
107
- const icon = this._suffixEl?.querySelector('i');
108
- if (icon) icon.textContent = visible ? 'visibility_off' : 'visibility';
111
+ if (this._isCrazyButton()) {
112
+ this._suffixEl?.toggleAttribute('disabled', disabled);
113
+ this._suffixEl?.setAttribute('icon-text', visible ? 'visibility_off' : 'visibility');
114
+ } else {
115
+ const icon = this._suffixEl?.querySelector('i');
116
+ if (icon) icon.textContent = visible ? 'visibility_off' : 'visibility';
117
+ }
109
118
  }
110
119
 
111
120
  private _handleToggleClick = (event: MouseEvent) => {
@@ -115,7 +124,7 @@ export class PasswordInput extends Component<PasswordInputOptions> {
115
124
 
116
125
  private _handleToggleKeydown = (event: KeyboardEvent) => {
117
126
  // Native buttons already dispatch clicks for Enter and Space.
118
- if (this._suffixEl instanceof HTMLButtonElement || !['Enter', ' '].includes(event.key)) return;
127
+ if (this._suffixEl instanceof HTMLButtonElement || this._isCrazyButton() || !['Enter', ' '].includes(event.key)) return;
119
128
  event.preventDefault();
120
129
  this.toggle();
121
130
  };
@@ -65,16 +65,28 @@
65
65
  button:hover .ql-fill, button.ql-active .ql-fill { fill: currentColor; }
66
66
  a { color: var(--md-sys-color-primary); }
67
67
  }
68
- .ql-container.ql-snow { height: auto; border: 0; font-family: inherit; font-size: 1rem; }
68
+ .ql-container.ql-snow {
69
+ height: auto;
70
+ border: 0;
71
+ border-radius: 0 0 11px 11px;
72
+ font-family: inherit;
73
+ font-size: 1rem;
74
+ &:first-child { border-radius: 11px; }
75
+ }
69
76
  .ql-editor {
70
77
  min-height: var(--rich-textarea-min-height);
71
78
  max-height: var(--rich-textarea-max-height, 480px);
72
79
  padding: 16px;
73
80
  line-height: 1.6;
81
+ font-family: var(--rich-textarea-font-family, inherit);
74
82
  color: inherit;
83
+ // Keep the editor surface inside the rounded border without clipping toolbar popups.
84
+ background: transparent;
85
+ border-radius: inherit;
75
86
  overflow-wrap: anywhere;
76
87
  outline: none;
77
88
  &.ql-blank::before { left: 16px; right: 16px; color: var(--md-sys-color-on-surface-variant); font-style: normal; }
89
+ strong, b { font-weight: 800; }
78
90
  blockquote { border-color: var(--md-sys-color-outline); }
79
91
  }
80
92
  .ql-tooltip {
@@ -9,6 +9,8 @@ export interface RichTextareaOptions extends BaseOptions {
9
9
  toolbar?: false | Array<ToolbarItem | ToolbarItem[]>;
10
10
  formats?: string[];
11
11
  placeholder?: string;
12
+ /** Default font stack for editor content; load any web fonts separately. */
13
+ fontFamily?: string;
12
14
  label?: string;
13
15
  }
14
16
  let sequence = 0;
@@ -80,6 +82,7 @@ export class RichTextarea extends Component<RichTextareaOptions> {
80
82
  const wrapper = document.createElement('div');
81
83
  this._wrapper = wrapper;
82
84
  wrapper.className = 'rich-textarea';
85
+ if (this.options.fontFamily) wrapper.style.setProperty('--rich-textarea-font-family', this.options.fontFamily);
83
86
  const editor = document.createElement('div');
84
87
  const error = document.createElement('div');
85
88
  this._error = error;
@@ -150,3 +150,208 @@ input[type='range'] + .thumb {
150
150
  input[type='range']::-moz-range-thumb { border: 1px solid CanvasText; }
151
151
  input[type='range'] + .thumb { border: 1px solid CanvasText; }
152
152
  }
153
+
154
+ /// Two-handle interval. Native inputs retain focus, keyboard, touch and form behavior.
155
+ .range-interval {
156
+ position: relative;
157
+ display: grid;
158
+ min-width: 0;
159
+ isolation: isolate;
160
+
161
+ --range-direction: to right;
162
+ --range-outer-color: var(--range-muted-color, var(--md-sys-color-surface-variant));
163
+ --range-inner-color: var(--md-sys-color-primary);
164
+ &:dir(rtl) { --range-direction: to left; }
165
+
166
+ &::before {
167
+ content: '';
168
+ grid-area: 1 / 1;
169
+ align-self: center;
170
+ height: var(--range-track-height, 4px);
171
+ border-radius: 999px;
172
+ background: linear-gradient(var(--range-direction),
173
+ var(--range-outer-color) 0 var(--range-start),
174
+ var(--range-inner-color) var(--range-start) var(--range-end),
175
+ var(--range-outer-color) var(--range-end) 100%);
176
+ }
177
+ &:has(input:disabled)::before { opacity: .38; }
178
+ &.range-compact { --range-track-height: 2px; }
179
+ &.range-expressive { --range-track-height: 12px; }
180
+
181
+ > input[type='range'] {
182
+ grid-area: 1 / 1;
183
+ pointer-events: none;
184
+ z-index: var(--range-start-layer, 1);
185
+
186
+ ~ input[type='range'] { z-index: 1; }
187
+ &:focus-visible { z-index: 2; }
188
+
189
+ &::-webkit-slider-runnable-track { background: transparent; }
190
+ &::-moz-range-track { background: transparent; }
191
+ &::-webkit-slider-thumb { pointer-events: auto; }
192
+ &::-moz-range-thumb { pointer-events: auto; }
193
+ &:disabled::-webkit-slider-thumb { pointer-events: none; }
194
+ &:disabled::-moz-range-thumb { pointer-events: none; }
195
+ }
196
+
197
+ // Interval steps use native snapping; single-value ranges provide tick marks.
198
+ > .range-ticks { display: none; }
199
+ }
200
+
201
+ /// Size variants preserve the native 48px interaction area.
202
+ .range-field.range-compact input[type='range'],
203
+ .range-interval.range-compact > input[type='range'] {
204
+ --range-track-height: 2px;
205
+ --range-handle-size: 16px;
206
+ }
207
+ .range-field.range-expressive input[type='range'],
208
+ .range-interval.range-expressive > input[type='range'] {
209
+ --range-track-height: 12px;
210
+ --range-handle-size: 24px;
211
+ }
212
+
213
+ @media (forced-colors: active) {
214
+ .range-interval::before { background: CanvasText; }
215
+ }
216
+
217
+ /// Highlight the two outside segments, with visible end stops.
218
+ .range-interval.range-outside {
219
+ --range-outer-color: var(--md-sys-color-primary);
220
+ --range-inner-color: var(--range-muted-color, var(--md-sys-color-surface-variant));
221
+
222
+ &::before { clip-path: inset(0 var(--range-edge, 10px)); }
223
+ &::after {
224
+ content: '';
225
+ position: absolute;
226
+ inset-inline: calc(var(--range-edge, 10px) - 1px);
227
+ top: 50%;
228
+ height: 16px;
229
+ transform: translateY(-50%);
230
+ border-inline: 2px solid var(--md-sys-color-primary);
231
+ pointer-events: none;
232
+ }
233
+ &:has(input:disabled)::after { opacity: .38; }
234
+ }
235
+
236
+ /// Editable values use the flat, segmented appearance of number controls.
237
+ .range-control {
238
+ --range-muted-color: color-mix(in srgb, var(--md-sys-color-on-surface) 24%, transparent);
239
+ display: grid;
240
+ grid-template-columns: minmax(0, 4.5rem) minmax(0, 1fr) minmax(0, 4.5rem);
241
+ align-items: stretch;
242
+ gap: 0;
243
+ padding: 0;
244
+ border: 0;
245
+ border-bottom: 1px solid var(--md-sys-color-outline);
246
+ border-radius: 0;
247
+ background: var(--md-sys-color-surface-variant);
248
+ color: var(--md-sys-color-on-surface);
249
+ min-width: 0;
250
+
251
+ &.range-control-single { grid-template-columns: minmax(0, 4.5rem) minmax(0, 1fr); }
252
+ &:focus-within {
253
+ outline: 2px solid var(--md-sys-color-primary);
254
+ outline-offset: -2px;
255
+ }
256
+ &[data-range-variant='outlined'] {
257
+ border: 1px solid var(--md-sys-color-outline);
258
+ border-radius: 4px;
259
+ background: var(--md-sys-color-surface);
260
+ }
261
+ &[data-range-variant='rounded'] {
262
+ border: 1px solid var(--md-sys-color-outline);
263
+ border-radius: 16px;
264
+ }
265
+
266
+ > .range-interval,
267
+ > .range-control-track {
268
+ margin-inline: 16px;
269
+ align-self: center;
270
+ min-width: 0;
271
+
272
+ > input[type='range'] { height: 56px; margin-block: 0; }
273
+ }
274
+ > .range-control-value {
275
+ position: static;
276
+ display: flex;
277
+ flex-direction: column-reverse;
278
+ justify-content: center;
279
+ min-width: 0;
280
+ min-height: 56px;
281
+ margin: 0;
282
+ padding: 4px;
283
+ box-sizing: border-box;
284
+ border-radius: inherit;
285
+ background: var(--md-sys-color-primary-container);
286
+ color: var(--md-sys-color-on-primary-container);
287
+ font-size: 10px;
288
+ line-height: 1.2;
289
+ text-align: center;
290
+ transform: none;
291
+
292
+ &:first-child { border-start-end-radius: 0; border-end-end-radius: 0; }
293
+ &:last-child { border-start-start-radius: 0; border-end-start-radius: 0; }
294
+ > span { overflow-wrap: anywhere; }
295
+ > .range-control-number > input[type='number'] {
296
+ appearance: textfield;
297
+ box-sizing: border-box;
298
+ width: 100%;
299
+ min-width: 0;
300
+ height: 30px;
301
+ margin: 0;
302
+ padding: 0 2px;
303
+ border: 0;
304
+ border-radius: 0;
305
+ background: transparent;
306
+ color: inherit;
307
+ box-shadow: none;
308
+ font: inherit;
309
+ font-size: 16px;
310
+ text-align: center;
311
+ font-variant-numeric: tabular-nums;
312
+
313
+ &::-webkit-inner-spin-button,
314
+ &::-webkit-outer-spin-button { -webkit-appearance: none; margin: 0; }
315
+ color: transparent;
316
+ -webkit-text-fill-color: transparent;
317
+ &:focus {
318
+ color: inherit;
319
+ -webkit-text-fill-color: currentColor;
320
+ }
321
+ &:focus-visible { outline: 2px solid var(--md-sys-color-primary); }
322
+ &:disabled { opacity: .38; }
323
+ }
324
+ }
325
+ }
326
+
327
+ @media (forced-colors: active) {
328
+ .range-interval.range-outside::after { border-color: CanvasText; }
329
+ .range-control { border-color: CanvasText; }
330
+ }
331
+
332
+ /// Fixed viewport for the centered film-roll readout. Focus reveals the real input.
333
+ .range-control-number {
334
+ position: relative;
335
+ display: block;
336
+ width: 100%;
337
+ height: 30px;
338
+ min-width: 0;
339
+ font-size: 16px;
340
+ font-variant-numeric: tabular-nums;
341
+ text-align: center;
342
+
343
+ &:focus-within > .range-control-readout { visibility: hidden; }
344
+ &:has(input:disabled) > .range-control-readout { opacity: .38; }
345
+ }
346
+ .range-control-readout {
347
+ position: absolute;
348
+ inset: 0 2px;
349
+ overflow: hidden;
350
+ pointer-events: none;
351
+ white-space: nowrap;
352
+ direction: ltr;
353
+ }
354
+ .range-control-reel {
355
+ display: block;
356
+ > span { display: block; height: 30px; line-height: 30px; }
357
+ }