@shortfuse/materialdesignweb 0.7.2 → 0.7.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.
- package/components/Button.js +1 -2
- package/components/Checkbox.js +0 -1
- package/components/FilterChip.js +0 -1
- package/components/ListSelect.js +1 -1
- package/components/Radio.js +0 -2
- package/components/SegmentedButton.js +0 -1
- package/components/Select.js +12 -24
- package/components/Slider.js +1 -2
- package/components/Switch.js +0 -1
- package/components/TextArea.js +206 -219
- package/core/CustomElement.js +23 -5
- package/core/ICustomElement.d.ts +8 -5
- package/core/observe.js +17 -19
- package/core/typings.d.ts +2 -1
- package/dist/index.min.js +72 -69
- package/dist/index.min.js.map +4 -4
- package/dist/meta.json +1 -1
- package/mixins/ControlMixin.js +193 -247
- package/mixins/FormAssociatedMixin.js +166 -30
- package/mixins/InputMixin.js +211 -290
- package/mixins/TextFieldMixin.js +18 -13
- package/package.json +8 -5
package/mixins/ControlMixin.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/* https://html.spec.whatwg.org/multipage/form-control-infrastructure.html */
|
|
2
2
|
|
|
3
|
+
import { cloneAttributeCallback } from '../core/CustomElement.js';
|
|
4
|
+
|
|
3
5
|
import FormAssociatedMixin from './FormAssociatedMixin.js';
|
|
4
6
|
|
|
5
7
|
/** @typedef {import('../core/CustomElement.js').default} CustomElement */
|
|
@@ -12,264 +14,208 @@ import FormAssociatedMixin from './FormAssociatedMixin.js';
|
|
|
12
14
|
* @param {ReturnType<import('./StateMixin.js').default>} Base
|
|
13
15
|
*/
|
|
14
16
|
export default function ControlMixin(Base) {
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
/** @type {string[]} */
|
|
31
|
-
static clonedContentAttributes = [
|
|
32
|
-
'autocomplete', 'name', 'readonly', 'required',
|
|
33
|
-
];
|
|
34
|
-
|
|
35
|
-
/** @type {string[]} */
|
|
36
|
-
static valueChangingContentAttributes = [];
|
|
37
|
-
|
|
38
|
-
static {
|
|
39
|
-
// eslint-disable-next-line no-unused-expressions
|
|
40
|
-
this.css`
|
|
41
|
-
|
|
42
|
-
:host {
|
|
43
|
-
display: inline-flex;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/* Remove Firefox inner */
|
|
47
|
-
:host(::-moz-focus-inner) {
|
|
48
|
-
border: 0;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
#label {
|
|
52
|
-
display: contents;
|
|
53
|
-
|
|
54
|
-
pointer-events: none;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
#control {
|
|
58
|
-
/* Control is the touch target */
|
|
59
|
-
/* Firefox requires at least 1px "visible" for screen reading */
|
|
60
|
-
/* Safari will not allow interaction with 0 opacity */
|
|
61
|
-
/* Chrome will not focus with visibility:hidden */
|
|
62
|
-
|
|
63
|
-
position: absolute;
|
|
64
|
-
inset: 50%;
|
|
65
|
-
/* --mdw-device-pixel-ratio: 1; */
|
|
66
|
-
|
|
67
|
-
block-size: 100%;
|
|
68
|
-
min-block-size: 48px;
|
|
69
|
-
inline-size:100%;
|
|
70
|
-
min-inline-size: 48px;
|
|
71
|
-
margin: 0;
|
|
72
|
-
border: 0;
|
|
73
|
-
padding: 0;
|
|
74
|
-
|
|
75
|
-
-webkit-appearance: none;
|
|
76
|
-
-moz-appearance: none;
|
|
77
|
-
appearance: none;
|
|
78
|
-
|
|
79
|
-
cursor: auto;
|
|
80
|
-
outline: none;
|
|
81
|
-
|
|
82
|
-
pointer-events: auto;
|
|
83
|
-
|
|
84
|
-
transform: translateX(-50%) translateY(-50%);
|
|
85
|
-
|
|
86
|
-
/* Safari and Chrome will emit two click events if not at top of stack */
|
|
87
|
-
/* Allows up to 3 other layers (eg: ripple, outline) */
|
|
88
|
-
z-index: 4;
|
|
89
|
-
|
|
90
|
-
background-color: transparent;
|
|
91
|
-
|
|
92
|
-
border-radius: 0;
|
|
93
|
-
color: transparent;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
#control::-moz-focus-inner {
|
|
97
|
-
border: 0;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
`;
|
|
101
|
-
}
|
|
17
|
+
return Base
|
|
18
|
+
.mixin(FormAssociatedMixin)
|
|
19
|
+
.extend()
|
|
20
|
+
.observe({
|
|
21
|
+
ariaLabel: 'string',
|
|
22
|
+
})
|
|
23
|
+
.set({
|
|
24
|
+
delegatesFocus: true,
|
|
25
|
+
focusableOnDisabled: false,
|
|
26
|
+
controlTagName: 'input',
|
|
27
|
+
controlVoidElement: true,
|
|
28
|
+
})
|
|
29
|
+
.methods({
|
|
30
|
+
onValueChangingContentAttribute() {
|
|
31
|
+
const control = /** @type {HTMLControlElement} */ (this.refs.control);
|
|
102
32
|
|
|
103
|
-
/** @param {any[]} args */
|
|
104
|
-
constructor(...args) {
|
|
105
|
-
super(...args);
|
|
106
|
-
/** @type {string} */
|
|
107
|
-
this._value = this._control.value;
|
|
108
|
-
// Expose this element as focusable
|
|
109
|
-
if (!this.hasAttribute('tabindex')) {
|
|
110
|
-
this.tabIndex = 0;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/** @type {CustomElement['attributeChangedCallback']} */
|
|
115
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
116
|
-
super.attributeChangedCallback(name, oldValue, newValue);
|
|
117
|
-
if (this.static.clonedContentAttributes.includes(name)) {
|
|
118
|
-
if (newValue == null) {
|
|
119
|
-
this._control.removeAttribute(name);
|
|
120
|
-
} else {
|
|
121
|
-
this._control.setAttribute(name, newValue);
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
if (this.static.valueChangingContentAttributes.includes(name)) {
|
|
126
33
|
if (!this.hasAttribute('value')) {
|
|
127
34
|
// Force HTMLInputElement to recalculate default
|
|
128
35
|
// Unintended effect of incrementally changing attributes (eg: range)
|
|
129
|
-
|
|
36
|
+
control.removeAttribute('value'); // Firefox will not run steps unless value is changed (remove first)
|
|
37
|
+
control.setAttribute('value', ''); // Chrome needs to know to reset
|
|
130
38
|
}
|
|
131
39
|
// Changing control attribute may change the value (eg: min/max)
|
|
132
|
-
this._value =
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* @param {Partial<this>} data
|
|
141
|
-
* @return {string}
|
|
142
|
-
*/
|
|
143
|
-
computeAriaLabelledBy({ ariaLabel }) {
|
|
144
|
-
if (ariaLabel) return null;
|
|
145
|
-
return '#slot';
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
get stateTargetElement() { return this._control; }
|
|
149
|
-
|
|
150
|
-
click() {
|
|
40
|
+
this._value = control.value;
|
|
41
|
+
},
|
|
42
|
+
/** @type {HTMLElement['focus']} */
|
|
43
|
+
focus(...options) {
|
|
44
|
+
this.refs.control.focus(...options);
|
|
45
|
+
},
|
|
151
46
|
/** Redirect click requests to control itself */
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
47
|
+
click() {
|
|
48
|
+
console.log('ControlMixin: Click');
|
|
49
|
+
this.refs.control.click();
|
|
50
|
+
},
|
|
51
|
+
})
|
|
52
|
+
.define({
|
|
53
|
+
stateTargetElement() { return this.refs.control; },
|
|
54
|
+
form() { return this.elementInternals.form; },
|
|
55
|
+
validity() { return this.elementInternals.validity; },
|
|
56
|
+
validationMessage() { return this.elementInternals.validationMessage; },
|
|
57
|
+
willValidate() { return this.elementInternals.willValidate; },
|
|
58
|
+
labels() { return this.elementInternals.labels; },
|
|
59
|
+
})
|
|
60
|
+
.methods({
|
|
61
|
+
checkValidity() {
|
|
62
|
+
const control = /** @type {HTMLControlElement} */ (this.refs.control);
|
|
63
|
+
const validityState = control.checkValidity();
|
|
64
|
+
/** @type {Partial<ValidityState>} */
|
|
65
|
+
const newValidity = {};
|
|
66
|
+
|
|
67
|
+
// eslint-disable-next-line guard-for-in
|
|
68
|
+
for (const key in control.validity) {
|
|
69
|
+
// @ts-ignore Skip cast
|
|
70
|
+
newValidity[key] = control.validity[key];
|
|
71
|
+
}
|
|
72
|
+
this.elementInternals.setValidity(newValidity, control.validationMessage);
|
|
73
|
+
this._invalid = !validityState;
|
|
74
|
+
this._validationMessage = control.validationMessage;
|
|
75
|
+
this._badInput = control.validity.badInput;
|
|
76
|
+
return validityState;
|
|
77
|
+
},
|
|
78
|
+
reportValidity() {
|
|
79
|
+
this.checkValidity();
|
|
80
|
+
/** @type {HTMLControlElement} */ (this.refs.control).reportValidity();
|
|
81
|
+
return this.elementInternals.reportValidity();
|
|
82
|
+
},
|
|
83
|
+
/**
|
|
84
|
+
* @param {string} error
|
|
85
|
+
* @return {void}
|
|
86
|
+
*/
|
|
87
|
+
setCustomValidity(error) {
|
|
88
|
+
/** @type {HTMLControlElement} */ (this.refs.control).setCustomValidity(error);
|
|
89
|
+
this.checkValidity();
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
})
|
|
93
|
+
.on({
|
|
94
|
+
// Wait until controlTagName is settled before templating
|
|
95
|
+
composed({ template, html }) {
|
|
96
|
+
template.append(html`
|
|
97
|
+
<label id=label disabled={disabledState}>
|
|
98
|
+
<${this.controlTagName} id=control
|
|
99
|
+
aria-labelledby=${({ ariaLabel }) => (ariaLabel ? null : '#slot')}
|
|
100
|
+
aria-label={ariaLabel}
|
|
101
|
+
type={type}
|
|
102
|
+
>${this.controlVoidElement ? '' : `</${this.controlTagName}>`}
|
|
103
|
+
</label>
|
|
104
|
+
`);
|
|
105
|
+
},
|
|
106
|
+
disabledStateChanged(oldValue, newValue) {
|
|
107
|
+
const control = /** @type {HTMLControlElement} */ (this.refs.control);
|
|
108
|
+
control.setAttribute('aria-disabled', `${newValue}`);
|
|
109
|
+
if (!this.focusableOnDisabled) {
|
|
110
|
+
control.disabled = newValue;
|
|
111
|
+
if (newValue) {
|
|
112
|
+
this.tabIndex = 0;
|
|
113
|
+
} else {
|
|
114
|
+
this.removeAttribute('tabindex');
|
|
175
115
|
}
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
control
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
constructed() {
|
|
119
|
+
const control = /** @type {HTMLControlElement} */ (this.refs.control);
|
|
120
|
+
this._value = control.value;
|
|
121
|
+
},
|
|
122
|
+
connected() {
|
|
123
|
+
// Expose this element as focusable
|
|
124
|
+
if (!this.hasAttribute('tabindex')) {
|
|
125
|
+
this.tabIndex = 0;
|
|
126
|
+
}
|
|
127
|
+
},
|
|
128
|
+
attrs: {
|
|
129
|
+
autocomplete: cloneAttributeCallback('autocomplete', 'control'),
|
|
130
|
+
name: cloneAttributeCallback('name', 'control'),
|
|
131
|
+
readonly: cloneAttributeCallback('readonly', 'control'),
|
|
132
|
+
required: cloneAttributeCallback('required', 'control'),
|
|
133
|
+
},
|
|
134
|
+
})
|
|
135
|
+
.childEvents({
|
|
136
|
+
control: {
|
|
137
|
+
input({ currentTarget }) {
|
|
138
|
+
console.debug('ControlMixin: input');
|
|
139
|
+
const control = /** @type {HTMLControlElement} */ (currentTarget);
|
|
140
|
+
if (this.validity.valid) {
|
|
183
141
|
// Track internally
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
142
|
+
control.checkValidity();
|
|
143
|
+
this._badInput = control.validity.badInput;
|
|
144
|
+
} else {
|
|
187
145
|
// Perform check in case user has validated
|
|
188
|
-
this.checkValidity();
|
|
189
|
-
}
|
|
190
|
-
this._value = control.value;
|
|
191
|
-
},
|
|
192
|
-
change({ currentTarget }) {
|
|
193
|
-
const control = /** @type {HTMLControlElement} */ (currentTarget);
|
|
194
|
-
this._value = control.value;
|
|
195
146
|
this.checkValidity();
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
},
|
|
147
|
+
}
|
|
148
|
+
this._value = control.value;
|
|
199
149
|
},
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
return /** @type {T} */ (/** @type {unknown} */ (super.static));
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
get form() { return this.elementInternals.form; }
|
|
218
|
-
|
|
219
|
-
// get name() { return this.getAttribute('name'); }
|
|
220
|
-
get value() {
|
|
221
|
-
return this._value;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
set value(v) {
|
|
225
|
-
this._valueDirty = true;
|
|
226
|
-
this._control.value = v;
|
|
227
|
-
this._value = this._control.value;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
get validity() { return this.elementInternals.validity; }
|
|
231
|
-
|
|
232
|
-
get validationMessage() { return this.elementInternals.validationMessage; }
|
|
233
|
-
|
|
234
|
-
get willValidate() { return this.elementInternals.willValidate; }
|
|
235
|
-
|
|
236
|
-
checkValidity() {
|
|
237
|
-
const validityState = this._control.checkValidity();
|
|
238
|
-
/** @type {Partial<ValidityState>} */
|
|
239
|
-
const newValidity = {};
|
|
240
|
-
|
|
241
|
-
// eslint-disable-next-line guard-for-in
|
|
242
|
-
for (const key in this._control.validity) {
|
|
243
|
-
// @ts-ignore Skip cast
|
|
244
|
-
newValidity[key] = this._control.validity[key];
|
|
150
|
+
change({ currentTarget }) {
|
|
151
|
+
console.debug('ControlMixin: change');
|
|
152
|
+
const control = /** @type {HTMLControlElement} */ (currentTarget);
|
|
153
|
+
this._valueDirty = true;
|
|
154
|
+
this._value = control.value;
|
|
155
|
+
this.checkValidity();
|
|
156
|
+
// Change event is NOT composed. Needs to escape shadow DOM
|
|
157
|
+
this.dispatchEvent(new Event('change', { bubbles: true }));
|
|
158
|
+
},
|
|
159
|
+
},
|
|
160
|
+
})
|
|
161
|
+
.css`
|
|
162
|
+
:host {
|
|
163
|
+
display: inline-flex;
|
|
245
164
|
}
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
165
|
+
|
|
166
|
+
/* Remove Firefox inner */
|
|
167
|
+
:host(::-moz-focus-inner) {
|
|
168
|
+
border: 0;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
#label {
|
|
172
|
+
display: contents;
|
|
173
|
+
|
|
174
|
+
pointer-events: none;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
#control {
|
|
178
|
+
/* Control is the touch target */
|
|
179
|
+
/* Firefox requires at least 1px "visible" for screen reading */
|
|
180
|
+
/* Safari will not allow interaction with 0 opacity */
|
|
181
|
+
/* Chrome will not focus with visibility:hidden */
|
|
182
|
+
|
|
183
|
+
position: absolute;
|
|
184
|
+
inset: 50%;
|
|
185
|
+
/* --mdw-device-pixel-ratio: 1; */
|
|
186
|
+
|
|
187
|
+
block-size: 100%;
|
|
188
|
+
min-block-size: 48px;
|
|
189
|
+
inline-size:100%;
|
|
190
|
+
min-inline-size: 48px;
|
|
191
|
+
margin: 0;
|
|
192
|
+
border: 0;
|
|
193
|
+
padding: 0;
|
|
194
|
+
|
|
195
|
+
-webkit-appearance: none;
|
|
196
|
+
-moz-appearance: none;
|
|
197
|
+
appearance: none;
|
|
198
|
+
|
|
199
|
+
cursor: auto;
|
|
200
|
+
outline: none;
|
|
201
|
+
|
|
202
|
+
pointer-events: auto;
|
|
203
|
+
|
|
204
|
+
transform: translateX(-50%) translateY(-50%);
|
|
205
|
+
|
|
206
|
+
/* Safari and Chrome will emit two click events if not at top of stack */
|
|
207
|
+
/* Allows up to 3 other layers (eg: ripple, outline) */
|
|
208
|
+
z-index: 4;
|
|
209
|
+
|
|
210
|
+
background-color: transparent;
|
|
211
|
+
|
|
212
|
+
border-radius: 0;
|
|
213
|
+
color: transparent;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
#control::-moz-focus-inner {
|
|
217
|
+
border: 0;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
`;
|
|
275
221
|
}
|