kmaterialize 2.3.3-1.0.33 → 2.3.3-1.0.34
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.
- package/components/password-input/password-input.ts +112 -62
- package/components/password-input/passwordSpec.js +110 -0
- package/dist/css/materialize.colors.min.css +1 -1
- package/dist/css/materialize.css +1 -1
- package/dist/css/materialize.min.css +1 -1
- package/dist/js/materialize.cjs.js +79 -27
- package/dist/js/materialize.d.ts +21 -5
- package/dist/js/materialize.js +79 -27
- package/dist/js/materialize.min.js +3 -3
- package/dist/js/materialize.mjs +79 -27
- package/package.json +1 -1
|
@@ -1,72 +1,122 @@
|
|
|
1
1
|
import { Component, BaseOptions, InitElements, MElement } from '../../src/component';
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
export interface PasswordInputOptions extends BaseOptions {
|
|
4
|
+
/** Accessible name when the password is hidden. */
|
|
5
|
+
showLabel: string;
|
|
6
|
+
/** Accessible name when the password is visible. */
|
|
7
|
+
hideLabel: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
type PasswordInputElement = HTMLInputElement & { M_PasswordInput?: PasswordInput };
|
|
5
11
|
|
|
6
|
-
const _defaults: PasswordInputOptions = {
|
|
12
|
+
const _defaults: PasswordInputOptions = {
|
|
13
|
+
showLabel: 'Show password',
|
|
14
|
+
hideLabel: 'Hide password'
|
|
15
|
+
};
|
|
7
16
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
// enhancements in this batch this one needs no peer-loader/dynamic import.
|
|
17
|
+
/**
|
|
18
|
+
* Show or hide a password with an accessible suffix button.
|
|
19
|
+
* Existing data-password-toggle-icon wrappers remain supported.
|
|
20
|
+
*/
|
|
13
21
|
export class PasswordInput extends Component<PasswordInputOptions> {
|
|
14
|
-
|
|
15
|
-
|
|
22
|
+
declare el: PasswordInputElement;
|
|
23
|
+
private _suffixEl: HTMLElement | null;
|
|
24
|
+
private _stateObserver: MutationObserver;
|
|
25
|
+
private _originalAttributes: Map<string, string | null> = new Map();
|
|
26
|
+
|
|
27
|
+
constructor(el: HTMLInputElement, options: Partial<PasswordInputOptions>) {
|
|
28
|
+
super(el, options, PasswordInput);
|
|
29
|
+
this.el.M_PasswordInput = this;
|
|
30
|
+
this.options = { ...PasswordInput.defaults, ...options };
|
|
31
|
+
this._suffixEl = this.el.parentElement?.querySelector<HTMLElement>('[data-password-toggle-icon]') ?? null;
|
|
32
|
+
|
|
33
|
+
if (this._suffixEl) {
|
|
34
|
+
for (const name of ['type', 'role', 'tabindex', 'aria-label', 'aria-pressed', 'aria-controls', 'aria-disabled', 'disabled'])
|
|
35
|
+
this._originalAttributes.set(name, this._suffixEl.getAttribute(name));
|
|
36
|
+
|
|
37
|
+
if (this._suffixEl instanceof HTMLButtonElement) {
|
|
38
|
+
this._suffixEl.type = 'button';
|
|
39
|
+
} else {
|
|
40
|
+
this._suffixEl.setAttribute('role', 'button');
|
|
41
|
+
this._suffixEl.tabIndex = 0;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (this.el.id) this._suffixEl.setAttribute('aria-controls', this.el.id);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Read the actual input type, including an initially visible password.
|
|
48
|
+
this._syncState();
|
|
49
|
+
this._suffixEl?.addEventListener('click', this._handleToggleClick);
|
|
50
|
+
this._suffixEl?.addEventListener('keydown', this._handleToggleKeydown);
|
|
51
|
+
this._stateObserver = new MutationObserver(() => this._syncState());
|
|
52
|
+
this._stateObserver.observe(this.el, { attributes: true, attributeFilter: ['type', 'disabled'] });
|
|
53
|
+
for (let parent = this.el.parentElement; parent; parent = parent.parentElement) {
|
|
54
|
+
if (parent instanceof HTMLFieldSetElement)
|
|
55
|
+
this._stateObserver.observe(parent, { attributes: true, attributeFilter: ['disabled'] });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
16
58
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
59
|
+
static get defaults(): PasswordInputOptions {
|
|
60
|
+
return _defaults;
|
|
61
|
+
}
|
|
20
62
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
63
|
+
static init(el: HTMLInputElement, options?: Partial<PasswordInputOptions>): PasswordInput;
|
|
64
|
+
static init(els: InitElements<HTMLInputElement | MElement>, options?: Partial<PasswordInputOptions>): PasswordInput[];
|
|
65
|
+
static init(
|
|
66
|
+
els: HTMLInputElement | InitElements<HTMLInputElement | MElement>,
|
|
67
|
+
options: Partial<PasswordInputOptions> = {}
|
|
68
|
+
): PasswordInput | PasswordInput[] {
|
|
69
|
+
return super.init(els, options, PasswordInput);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
static getInstance(el: HTMLInputElement): PasswordInput {
|
|
73
|
+
return (el as PasswordInputElement).M_PasswordInput!;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** Remove listeners and restore the supplied button attributes. */
|
|
77
|
+
destroy() {
|
|
78
|
+
this._stateObserver.disconnect();
|
|
79
|
+
this._suffixEl?.removeEventListener('click', this._handleToggleClick);
|
|
80
|
+
this._suffixEl?.removeEventListener('keydown', this._handleToggleKeydown);
|
|
81
|
+
for (const [name, value] of this._originalAttributes) {
|
|
82
|
+
if (value === null) this._suffixEl?.removeAttribute(name);
|
|
83
|
+
else this._suffixEl?.setAttribute(name, value);
|
|
84
|
+
}
|
|
85
|
+
this.el.M_PasswordInput = undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Toggle without submitting the form or changing its value or selection. */
|
|
89
|
+
toggle() {
|
|
90
|
+
if (this.el.matches(':disabled')) return;
|
|
91
|
+
const start = this.el.selectionStart;
|
|
92
|
+
const end = this.el.selectionEnd;
|
|
93
|
+
const direction = this.el.selectionDirection;
|
|
94
|
+
this.el.type = this.el.type === 'password' ? 'text' : 'password';
|
|
95
|
+
if (start !== null && end !== null) this.el.setSelectionRange(start, end, direction ?? undefined);
|
|
96
|
+
this._syncState();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
private _syncState() {
|
|
100
|
+
const visible = this.el.type === 'text';
|
|
101
|
+
const disabled = this.el.matches(':disabled');
|
|
102
|
+
this.el.dataset.passwordVisible = visible ? '1' : '0';
|
|
103
|
+
this._suffixEl?.setAttribute('aria-label', visible ? this.options.hideLabel : this.options.showLabel);
|
|
104
|
+
this._suffixEl?.setAttribute('aria-pressed', String(visible));
|
|
105
|
+
this._suffixEl?.setAttribute('aria-disabled', String(disabled));
|
|
106
|
+
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';
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
private _handleToggleClick = (event: MouseEvent) => {
|
|
112
|
+
event.preventDefault();
|
|
113
|
+
this.toggle();
|
|
24
114
|
};
|
|
25
115
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
return _defaults;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
static init(el: HTMLInputElement, options?: Partial<PasswordInputOptions>): PasswordInput;
|
|
36
|
-
static init(
|
|
37
|
-
els: InitElements<HTMLInputElement | MElement>,
|
|
38
|
-
options?: Partial<PasswordInputOptions>
|
|
39
|
-
): PasswordInput[];
|
|
40
|
-
static init(
|
|
41
|
-
els: HTMLInputElement | InitElements<HTMLInputElement | MElement>,
|
|
42
|
-
options: Partial<PasswordInputOptions> = {}
|
|
43
|
-
): PasswordInput | PasswordInput[] {
|
|
44
|
-
return super.init(els, options, PasswordInput);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
static getInstance(el: HTMLInputElement): PasswordInput {
|
|
48
|
-
return (el as any).M_PasswordInput;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
destroy() {
|
|
52
|
-
this._removeEventHandlers();
|
|
53
|
-
(this.el as any).M_PasswordInput = undefined;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
_setupEventHandlers() {
|
|
57
|
-
this._suffixEl?.addEventListener('click', this._handleToggleClick);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
_removeEventHandlers() {
|
|
61
|
-
this._suffixEl?.removeEventListener('click', this._handleToggleClick);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
_handleToggleClick = () => {
|
|
65
|
-
const visible = this.el.dataset.passwordVisible === '1';
|
|
66
|
-
this.el.type = visible ? 'password' : 'text';
|
|
67
|
-
this.el.dataset.passwordVisible = visible ? '0' : '1';
|
|
68
|
-
|
|
69
|
-
const iconEl = this._suffixEl?.querySelector('i');
|
|
70
|
-
if (iconEl) iconEl.textContent = visible ? 'visibility' : 'visibility_off';
|
|
71
|
-
};
|
|
116
|
+
private _handleToggleKeydown = (event: KeyboardEvent) => {
|
|
117
|
+
// Native buttons already dispatch clicks for Enter and Space.
|
|
118
|
+
if (this._suffixEl instanceof HTMLButtonElement || !['Enter', ' '].includes(event.key)) return;
|
|
119
|
+
event.preventDefault();
|
|
120
|
+
this.toggle();
|
|
121
|
+
};
|
|
72
122
|
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
describe('PasswordInput', function() {
|
|
2
|
+
let fixture;
|
|
3
|
+
let input;
|
|
4
|
+
let button;
|
|
5
|
+
let instance;
|
|
6
|
+
|
|
7
|
+
beforeEach(function() {
|
|
8
|
+
fixture = document.createElement('form');
|
|
9
|
+
fixture.innerHTML = '<div class="input-field"><input id="password-test" type="password" value="example-secret" data-password-toggle><button class="suffix btn-flat p-0 transparent" data-password-toggle-icon><i class="material-icons">visibility</i></button></div>';
|
|
10
|
+
document.body.appendChild(fixture);
|
|
11
|
+
input = fixture.querySelector('input');
|
|
12
|
+
button = fixture.querySelector('button');
|
|
13
|
+
instance = M.PasswordInput.init(input);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(function() {
|
|
17
|
+
M.PasswordInput.getInstance(input)?.destroy();
|
|
18
|
+
fixture.remove();
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('toggles the supplied suffix button without submitting or changing the value', function() {
|
|
22
|
+
const submit = jasmine.createSpy('submit');
|
|
23
|
+
fixture.addEventListener('submit', submit);
|
|
24
|
+
input.setSelectionRange(2, 5);
|
|
25
|
+
expect(button.type).toBe('button');
|
|
26
|
+
expect(button.getAttribute('aria-controls')).toBe(input.id);
|
|
27
|
+
expect(button.getAttribute('aria-label')).toBe('Show password');
|
|
28
|
+
button.click();
|
|
29
|
+
expect(input.type).toBe('text');
|
|
30
|
+
expect(input.value).toBe('example-secret');
|
|
31
|
+
expect(input.selectionStart).toBe(2);
|
|
32
|
+
expect(input.selectionEnd).toBe(5);
|
|
33
|
+
expect(button.getAttribute('aria-label')).toBe('Hide password');
|
|
34
|
+
expect(button.getAttribute('aria-pressed')).toBe('true');
|
|
35
|
+
expect(button.querySelector('i').textContent).toBe('visibility_off');
|
|
36
|
+
button.click();
|
|
37
|
+
expect(input.type).toBe('password');
|
|
38
|
+
expect(input.dataset.passwordVisible).toBe('0');
|
|
39
|
+
expect(button.getAttribute('aria-pressed')).toBe('false');
|
|
40
|
+
expect(submit).not.toHaveBeenCalled();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('does not toggle a disabled input and synchronizes the button', async function() {
|
|
44
|
+
input.disabled = true;
|
|
45
|
+
instance.toggle();
|
|
46
|
+
await Promise.resolve();
|
|
47
|
+
expect(input.type).toBe('password');
|
|
48
|
+
expect(button.disabled).toBeTrue();
|
|
49
|
+
input.disabled = false;
|
|
50
|
+
await Promise.resolve();
|
|
51
|
+
expect(button.disabled).toBeFalse();
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it('follows a disabled fieldset', async function() {
|
|
55
|
+
instance.destroy();
|
|
56
|
+
const fieldset = document.createElement('fieldset');
|
|
57
|
+
fixture.appendChild(fieldset);
|
|
58
|
+
fieldset.appendChild(input.parentElement);
|
|
59
|
+
instance = M.PasswordInput.init(input);
|
|
60
|
+
fieldset.disabled = true;
|
|
61
|
+
await Promise.resolve();
|
|
62
|
+
expect(button.disabled).toBeTrue();
|
|
63
|
+
instance.toggle();
|
|
64
|
+
expect(input.type).toBe('password');
|
|
65
|
+
fieldset.disabled = false;
|
|
66
|
+
await Promise.resolve();
|
|
67
|
+
expect(button.disabled).toBeFalse();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it('uses the actual type for initially visible passwords and custom labels', function() {
|
|
71
|
+
instance.destroy();
|
|
72
|
+
input.type = 'text';
|
|
73
|
+
instance = M.PasswordInput.init(input, { showLabel: 'Afficher', hideLabel: 'Masquer' });
|
|
74
|
+
expect(button.getAttribute('aria-label')).toBe('Masquer');
|
|
75
|
+
button.click();
|
|
76
|
+
expect(input.type).toBe('password');
|
|
77
|
+
expect(button.getAttribute('aria-label')).toBe('Afficher');
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it('keeps readonly passwords revealable', function() {
|
|
81
|
+
input.readOnly = true;
|
|
82
|
+
button.click();
|
|
83
|
+
expect(input.type).toBe('text');
|
|
84
|
+
expect(input.readOnly).toBeTrue();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it('reinitializes without duplicate listeners and destroys cleanly', function() {
|
|
88
|
+
instance = M.PasswordInput.init(input);
|
|
89
|
+
button.click();
|
|
90
|
+
expect(input.type).toBe('text');
|
|
91
|
+
instance.destroy();
|
|
92
|
+
button.dispatchEvent(new MouseEvent('click', { bubbles: true, cancelable: true }));
|
|
93
|
+
expect(input.type).toBe('text');
|
|
94
|
+
expect(M.PasswordInput.getInstance(input)).toBeUndefined();
|
|
95
|
+
expect(button.hasAttribute('aria-pressed')).toBeFalse();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it('supports keyboard activation on legacy suffix wrappers', function() {
|
|
99
|
+
instance.destroy();
|
|
100
|
+
button.outerHTML = '<div data-password-toggle-icon><i>visibility</i></div>';
|
|
101
|
+
button = fixture.querySelector('[data-password-toggle-icon]');
|
|
102
|
+
instance = M.PasswordInput.init(input);
|
|
103
|
+
expect(button.getAttribute('role')).toBe('button');
|
|
104
|
+
expect(button.tabIndex).toBe(0);
|
|
105
|
+
button.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true }));
|
|
106
|
+
expect(input.type).toBe('text');
|
|
107
|
+
button.dispatchEvent(new KeyboardEvent('keydown', { key: ' ', bubbles: true, cancelable: true }));
|
|
108
|
+
expect(input.type).toBe('password');
|
|
109
|
+
});
|
|
110
|
+
});
|
package/dist/css/materialize.css
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Materialize v2.3.3-1.0.
|
|
2
|
+
* Materialize v2.3.3-1.0.34 (https://materializeweb.com)
|
|
3
3
|
* Copyright 2014-2026 Materialize
|
|
4
4
|
* MIT License (https://raw.githubusercontent.com/materializecss/materialize/master/LICENSE)
|
|
5
5
|
*/
|
|
@@ -10487,25 +10487,46 @@ class Toolbar extends Component {
|
|
|
10487
10487
|
}
|
|
10488
10488
|
}
|
|
10489
10489
|
|
|
10490
|
-
const _defaults$9 = {
|
|
10491
|
-
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10490
|
+
const _defaults$9 = {
|
|
10491
|
+
showLabel: 'Show password',
|
|
10492
|
+
hideLabel: 'Hide password'
|
|
10493
|
+
};
|
|
10494
|
+
/**
|
|
10495
|
+
* Show or hide a password with an accessible suffix button.
|
|
10496
|
+
* Existing data-password-toggle-icon wrappers remain supported.
|
|
10497
|
+
*/
|
|
10496
10498
|
class PasswordInput extends Component {
|
|
10497
10499
|
_suffixEl;
|
|
10500
|
+
_stateObserver;
|
|
10501
|
+
_originalAttributes = new Map();
|
|
10498
10502
|
constructor(el, options) {
|
|
10499
10503
|
super(el, options, PasswordInput);
|
|
10500
10504
|
this.el.M_PasswordInput = this;
|
|
10501
|
-
this.options = {
|
|
10502
|
-
...PasswordInput.defaults,
|
|
10503
|
-
...options
|
|
10504
|
-
};
|
|
10505
|
+
this.options = { ...PasswordInput.defaults, ...options };
|
|
10505
10506
|
this._suffixEl = this.el.parentElement?.querySelector('[data-password-toggle-icon]') ?? null;
|
|
10506
|
-
if (
|
|
10507
|
-
|
|
10508
|
-
|
|
10507
|
+
if (this._suffixEl) {
|
|
10508
|
+
for (const name of ['type', 'role', 'tabindex', 'aria-label', 'aria-pressed', 'aria-controls', 'aria-disabled', 'disabled'])
|
|
10509
|
+
this._originalAttributes.set(name, this._suffixEl.getAttribute(name));
|
|
10510
|
+
if (this._suffixEl instanceof HTMLButtonElement) {
|
|
10511
|
+
this._suffixEl.type = 'button';
|
|
10512
|
+
}
|
|
10513
|
+
else {
|
|
10514
|
+
this._suffixEl.setAttribute('role', 'button');
|
|
10515
|
+
this._suffixEl.tabIndex = 0;
|
|
10516
|
+
}
|
|
10517
|
+
if (this.el.id)
|
|
10518
|
+
this._suffixEl.setAttribute('aria-controls', this.el.id);
|
|
10519
|
+
}
|
|
10520
|
+
// Read the actual input type, including an initially visible password.
|
|
10521
|
+
this._syncState();
|
|
10522
|
+
this._suffixEl?.addEventListener('click', this._handleToggleClick);
|
|
10523
|
+
this._suffixEl?.addEventListener('keydown', this._handleToggleKeydown);
|
|
10524
|
+
this._stateObserver = new MutationObserver(() => this._syncState());
|
|
10525
|
+
this._stateObserver.observe(this.el, { attributes: true, attributeFilter: ['type', 'disabled'] });
|
|
10526
|
+
for (let parent = this.el.parentElement; parent; parent = parent.parentElement) {
|
|
10527
|
+
if (parent instanceof HTMLFieldSetElement)
|
|
10528
|
+
this._stateObserver.observe(parent, { attributes: true, attributeFilter: ['disabled'] });
|
|
10529
|
+
}
|
|
10509
10530
|
}
|
|
10510
10531
|
static get defaults() {
|
|
10511
10532
|
return _defaults$9;
|
|
@@ -10516,23 +10537,54 @@ class PasswordInput extends Component {
|
|
|
10516
10537
|
static getInstance(el) {
|
|
10517
10538
|
return el.M_PasswordInput;
|
|
10518
10539
|
}
|
|
10540
|
+
/** Remove listeners and restore the supplied button attributes. */
|
|
10519
10541
|
destroy() {
|
|
10520
|
-
this.
|
|
10542
|
+
this._stateObserver.disconnect();
|
|
10543
|
+
this._suffixEl?.removeEventListener('click', this._handleToggleClick);
|
|
10544
|
+
this._suffixEl?.removeEventListener('keydown', this._handleToggleKeydown);
|
|
10545
|
+
for (const [name, value] of this._originalAttributes) {
|
|
10546
|
+
if (value === null)
|
|
10547
|
+
this._suffixEl?.removeAttribute(name);
|
|
10548
|
+
else
|
|
10549
|
+
this._suffixEl?.setAttribute(name, value);
|
|
10550
|
+
}
|
|
10521
10551
|
this.el.M_PasswordInput = undefined;
|
|
10522
10552
|
}
|
|
10523
|
-
|
|
10524
|
-
|
|
10553
|
+
/** Toggle without submitting the form or changing its value or selection. */
|
|
10554
|
+
toggle() {
|
|
10555
|
+
if (this.el.matches(':disabled'))
|
|
10556
|
+
return;
|
|
10557
|
+
const start = this.el.selectionStart;
|
|
10558
|
+
const end = this.el.selectionEnd;
|
|
10559
|
+
const direction = this.el.selectionDirection;
|
|
10560
|
+
this.el.type = this.el.type === 'password' ? 'text' : 'password';
|
|
10561
|
+
if (start !== null && end !== null)
|
|
10562
|
+
this.el.setSelectionRange(start, end, direction ?? undefined);
|
|
10563
|
+
this._syncState();
|
|
10525
10564
|
}
|
|
10526
|
-
|
|
10527
|
-
this.
|
|
10565
|
+
_syncState() {
|
|
10566
|
+
const visible = this.el.type === 'text';
|
|
10567
|
+
const disabled = this.el.matches(':disabled');
|
|
10568
|
+
this.el.dataset.passwordVisible = visible ? '1' : '0';
|
|
10569
|
+
this._suffixEl?.setAttribute('aria-label', visible ? this.options.hideLabel : this.options.showLabel);
|
|
10570
|
+
this._suffixEl?.setAttribute('aria-pressed', String(visible));
|
|
10571
|
+
this._suffixEl?.setAttribute('aria-disabled', String(disabled));
|
|
10572
|
+
if (this._suffixEl instanceof HTMLButtonElement)
|
|
10573
|
+
this._suffixEl.disabled = disabled;
|
|
10574
|
+
const icon = this._suffixEl?.querySelector('i');
|
|
10575
|
+
if (icon)
|
|
10576
|
+
icon.textContent = visible ? 'visibility_off' : 'visibility';
|
|
10528
10577
|
}
|
|
10529
|
-
_handleToggleClick = () => {
|
|
10530
|
-
|
|
10531
|
-
this.
|
|
10532
|
-
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10578
|
+
_handleToggleClick = (event) => {
|
|
10579
|
+
event.preventDefault();
|
|
10580
|
+
this.toggle();
|
|
10581
|
+
};
|
|
10582
|
+
_handleToggleKeydown = (event) => {
|
|
10583
|
+
// Native buttons already dispatch clicks for Enter and Space.
|
|
10584
|
+
if (this._suffixEl instanceof HTMLButtonElement || !['Enter', ' '].includes(event.key))
|
|
10585
|
+
return;
|
|
10586
|
+
event.preventDefault();
|
|
10587
|
+
this.toggle();
|
|
10536
10588
|
};
|
|
10537
10589
|
}
|
|
10538
10590
|
|
|
@@ -15496,7 +15548,7 @@ class Editor {
|
|
|
15496
15548
|
}
|
|
15497
15549
|
}
|
|
15498
15550
|
|
|
15499
|
-
const version = '2.3.3-1.0.
|
|
15551
|
+
const version = '2.3.3-1.0.34';
|
|
15500
15552
|
/**
|
|
15501
15553
|
* Convenience helper matching v1's `M.toast({...})` call, since Toast is a
|
|
15502
15554
|
* class in v2 with no bare functional equivalent of its own.
|
package/dist/js/materialize.d.ts
CHANGED
|
@@ -2641,19 +2641,35 @@ declare class Toolbar extends Component<ToolbarOptions> {
|
|
|
2641
2641
|
}
|
|
2642
2642
|
|
|
2643
2643
|
interface PasswordInputOptions extends BaseOptions$1 {
|
|
2644
|
+
/** Accessible name when the password is hidden. */
|
|
2645
|
+
showLabel: string;
|
|
2646
|
+
/** Accessible name when the password is visible. */
|
|
2647
|
+
hideLabel: string;
|
|
2644
2648
|
}
|
|
2649
|
+
type PasswordInputElement = HTMLInputElement & {
|
|
2650
|
+
M_PasswordInput?: PasswordInput;
|
|
2651
|
+
};
|
|
2652
|
+
/**
|
|
2653
|
+
* Show or hide a password with an accessible suffix button.
|
|
2654
|
+
* Existing data-password-toggle-icon wrappers remain supported.
|
|
2655
|
+
*/
|
|
2645
2656
|
declare class PasswordInput extends Component<PasswordInputOptions> {
|
|
2646
|
-
el:
|
|
2657
|
+
el: PasswordInputElement;
|
|
2647
2658
|
private _suffixEl;
|
|
2659
|
+
private _stateObserver;
|
|
2660
|
+
private _originalAttributes;
|
|
2648
2661
|
constructor(el: HTMLInputElement, options: Partial<PasswordInputOptions>);
|
|
2649
2662
|
static get defaults(): PasswordInputOptions;
|
|
2650
2663
|
static init(el: HTMLInputElement, options?: Partial<PasswordInputOptions>): PasswordInput;
|
|
2651
2664
|
static init(els: InitElements<HTMLInputElement | MElement>, options?: Partial<PasswordInputOptions>): PasswordInput[];
|
|
2652
2665
|
static getInstance(el: HTMLInputElement): PasswordInput;
|
|
2666
|
+
/** Remove listeners and restore the supplied button attributes. */
|
|
2653
2667
|
destroy(): void;
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2668
|
+
/** Toggle without submitting the form or changing its value or selection. */
|
|
2669
|
+
toggle(): void;
|
|
2670
|
+
private _syncState;
|
|
2671
|
+
private _handleToggleClick;
|
|
2672
|
+
private _handleToggleKeydown;
|
|
2657
2673
|
}
|
|
2658
2674
|
|
|
2659
2675
|
interface NumberInputOptions extends BaseOptions$1 {
|
|
@@ -4027,7 +4043,7 @@ declare class Editor {
|
|
|
4027
4043
|
destroy(): void;
|
|
4028
4044
|
}
|
|
4029
4045
|
|
|
4030
|
-
declare const version = "2.3.3-1.0.
|
|
4046
|
+
declare const version = "2.3.3-1.0.34";
|
|
4031
4047
|
/**
|
|
4032
4048
|
* Convenience helper matching v1's `M.toast({...})` call, since Toast is a
|
|
4033
4049
|
* class in v2 with no bare functional equivalent of its own.
|
package/dist/js/materialize.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*!
|
|
2
|
-
* Materialize v2.3.3-1.0.
|
|
2
|
+
* Materialize v2.3.3-1.0.34 (https://materializeweb.com)
|
|
3
3
|
* Copyright 2014-2026 Materialize
|
|
4
4
|
* MIT License (https://raw.githubusercontent.com/materializecss/materialize/master/LICENSE)
|
|
5
5
|
*/
|
|
@@ -10488,25 +10488,46 @@ var M = (function (exports) {
|
|
|
10488
10488
|
}
|
|
10489
10489
|
}
|
|
10490
10490
|
|
|
10491
|
-
const _defaults$9 = {
|
|
10492
|
-
|
|
10493
|
-
|
|
10494
|
-
|
|
10495
|
-
|
|
10496
|
-
|
|
10491
|
+
const _defaults$9 = {
|
|
10492
|
+
showLabel: 'Show password',
|
|
10493
|
+
hideLabel: 'Hide password'
|
|
10494
|
+
};
|
|
10495
|
+
/**
|
|
10496
|
+
* Show or hide a password with an accessible suffix button.
|
|
10497
|
+
* Existing data-password-toggle-icon wrappers remain supported.
|
|
10498
|
+
*/
|
|
10497
10499
|
class PasswordInput extends Component {
|
|
10498
10500
|
_suffixEl;
|
|
10501
|
+
_stateObserver;
|
|
10502
|
+
_originalAttributes = new Map();
|
|
10499
10503
|
constructor(el, options) {
|
|
10500
10504
|
super(el, options, PasswordInput);
|
|
10501
10505
|
this.el.M_PasswordInput = this;
|
|
10502
|
-
this.options = {
|
|
10503
|
-
...PasswordInput.defaults,
|
|
10504
|
-
...options
|
|
10505
|
-
};
|
|
10506
|
+
this.options = { ...PasswordInput.defaults, ...options };
|
|
10506
10507
|
this._suffixEl = this.el.parentElement?.querySelector('[data-password-toggle-icon]') ?? null;
|
|
10507
|
-
if (
|
|
10508
|
-
|
|
10509
|
-
|
|
10508
|
+
if (this._suffixEl) {
|
|
10509
|
+
for (const name of ['type', 'role', 'tabindex', 'aria-label', 'aria-pressed', 'aria-controls', 'aria-disabled', 'disabled'])
|
|
10510
|
+
this._originalAttributes.set(name, this._suffixEl.getAttribute(name));
|
|
10511
|
+
if (this._suffixEl instanceof HTMLButtonElement) {
|
|
10512
|
+
this._suffixEl.type = 'button';
|
|
10513
|
+
}
|
|
10514
|
+
else {
|
|
10515
|
+
this._suffixEl.setAttribute('role', 'button');
|
|
10516
|
+
this._suffixEl.tabIndex = 0;
|
|
10517
|
+
}
|
|
10518
|
+
if (this.el.id)
|
|
10519
|
+
this._suffixEl.setAttribute('aria-controls', this.el.id);
|
|
10520
|
+
}
|
|
10521
|
+
// Read the actual input type, including an initially visible password.
|
|
10522
|
+
this._syncState();
|
|
10523
|
+
this._suffixEl?.addEventListener('click', this._handleToggleClick);
|
|
10524
|
+
this._suffixEl?.addEventListener('keydown', this._handleToggleKeydown);
|
|
10525
|
+
this._stateObserver = new MutationObserver(() => this._syncState());
|
|
10526
|
+
this._stateObserver.observe(this.el, { attributes: true, attributeFilter: ['type', 'disabled'] });
|
|
10527
|
+
for (let parent = this.el.parentElement; parent; parent = parent.parentElement) {
|
|
10528
|
+
if (parent instanceof HTMLFieldSetElement)
|
|
10529
|
+
this._stateObserver.observe(parent, { attributes: true, attributeFilter: ['disabled'] });
|
|
10530
|
+
}
|
|
10510
10531
|
}
|
|
10511
10532
|
static get defaults() {
|
|
10512
10533
|
return _defaults$9;
|
|
@@ -10517,23 +10538,54 @@ var M = (function (exports) {
|
|
|
10517
10538
|
static getInstance(el) {
|
|
10518
10539
|
return el.M_PasswordInput;
|
|
10519
10540
|
}
|
|
10541
|
+
/** Remove listeners and restore the supplied button attributes. */
|
|
10520
10542
|
destroy() {
|
|
10521
|
-
this.
|
|
10543
|
+
this._stateObserver.disconnect();
|
|
10544
|
+
this._suffixEl?.removeEventListener('click', this._handleToggleClick);
|
|
10545
|
+
this._suffixEl?.removeEventListener('keydown', this._handleToggleKeydown);
|
|
10546
|
+
for (const [name, value] of this._originalAttributes) {
|
|
10547
|
+
if (value === null)
|
|
10548
|
+
this._suffixEl?.removeAttribute(name);
|
|
10549
|
+
else
|
|
10550
|
+
this._suffixEl?.setAttribute(name, value);
|
|
10551
|
+
}
|
|
10522
10552
|
this.el.M_PasswordInput = undefined;
|
|
10523
10553
|
}
|
|
10524
|
-
|
|
10525
|
-
|
|
10554
|
+
/** Toggle without submitting the form or changing its value or selection. */
|
|
10555
|
+
toggle() {
|
|
10556
|
+
if (this.el.matches(':disabled'))
|
|
10557
|
+
return;
|
|
10558
|
+
const start = this.el.selectionStart;
|
|
10559
|
+
const end = this.el.selectionEnd;
|
|
10560
|
+
const direction = this.el.selectionDirection;
|
|
10561
|
+
this.el.type = this.el.type === 'password' ? 'text' : 'password';
|
|
10562
|
+
if (start !== null && end !== null)
|
|
10563
|
+
this.el.setSelectionRange(start, end, direction ?? undefined);
|
|
10564
|
+
this._syncState();
|
|
10526
10565
|
}
|
|
10527
|
-
|
|
10528
|
-
this.
|
|
10566
|
+
_syncState() {
|
|
10567
|
+
const visible = this.el.type === 'text';
|
|
10568
|
+
const disabled = this.el.matches(':disabled');
|
|
10569
|
+
this.el.dataset.passwordVisible = visible ? '1' : '0';
|
|
10570
|
+
this._suffixEl?.setAttribute('aria-label', visible ? this.options.hideLabel : this.options.showLabel);
|
|
10571
|
+
this._suffixEl?.setAttribute('aria-pressed', String(visible));
|
|
10572
|
+
this._suffixEl?.setAttribute('aria-disabled', String(disabled));
|
|
10573
|
+
if (this._suffixEl instanceof HTMLButtonElement)
|
|
10574
|
+
this._suffixEl.disabled = disabled;
|
|
10575
|
+
const icon = this._suffixEl?.querySelector('i');
|
|
10576
|
+
if (icon)
|
|
10577
|
+
icon.textContent = visible ? 'visibility_off' : 'visibility';
|
|
10529
10578
|
}
|
|
10530
|
-
_handleToggleClick = () => {
|
|
10531
|
-
|
|
10532
|
-
this.
|
|
10533
|
-
|
|
10534
|
-
|
|
10535
|
-
|
|
10536
|
-
|
|
10579
|
+
_handleToggleClick = (event) => {
|
|
10580
|
+
event.preventDefault();
|
|
10581
|
+
this.toggle();
|
|
10582
|
+
};
|
|
10583
|
+
_handleToggleKeydown = (event) => {
|
|
10584
|
+
// Native buttons already dispatch clicks for Enter and Space.
|
|
10585
|
+
if (this._suffixEl instanceof HTMLButtonElement || !['Enter', ' '].includes(event.key))
|
|
10586
|
+
return;
|
|
10587
|
+
event.preventDefault();
|
|
10588
|
+
this.toggle();
|
|
10537
10589
|
};
|
|
10538
10590
|
}
|
|
10539
10591
|
|
|
@@ -15497,7 +15549,7 @@ var M = (function (exports) {
|
|
|
15497
15549
|
}
|
|
15498
15550
|
}
|
|
15499
15551
|
|
|
15500
|
-
const version = '2.3.3-1.0.
|
|
15552
|
+
const version = '2.3.3-1.0.34';
|
|
15501
15553
|
/**
|
|
15502
15554
|
* Convenience helper matching v1's `M.toast({...})` call, since Toast is a
|
|
15503
15555
|
* class in v2 with no bare functional equivalent of its own.
|