@shortfuse/materialdesignweb 0.7.1 → 0.7.4

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.
@@ -1,3 +1,5 @@
1
+ import { cloneAttributeCallback } from '../core/CustomElement.js';
2
+
1
3
  import ControlMixin from './ControlMixin.js';
2
4
 
3
5
  /** @typedef {'align'|'useMap'} DeprecatedHTMLInputElementProperties */
@@ -19,317 +21,236 @@ const IMPLICIT_SUBMISSION_BLOCKING_TYPES = new Set([
19
21
  'number',
20
22
  ]);
21
23
 
24
+ const DOMString = { nullParser: String, empty: '' };
25
+
22
26
  /**
27
+ * @see https://html.spec.whatwg.org/multipage/input.html#htmlinputelement
23
28
  * @param {ReturnType<import('./StateMixin.js').default>} Base
24
29
  */
25
30
  export default function InputMixin(Base) {
26
- class Input extends Base.mixin(ControlMixin) {
27
- static inputTagName = 'input';
28
-
29
- static FORM_IPC_EVENT = 'mdw-input-changed';
30
-
31
- static GlobalListener = new EventTarget();
32
-
33
- static clonedContentAttributes = [
34
- ...super.clonedContentAttributes,
35
- 'aria-controls',
36
- 'accept', 'alt',
37
- // 'autocomplete',
38
- 'checked', 'dirname',
39
- // 'disabled',
40
- // 'form',
41
- 'formaction', 'formenctype', 'formmethod', 'formnovalidate', 'formTarget',
42
- 'height',
43
- // 'list',
44
- 'max', 'maxlength', 'min', 'minlength',
45
- 'multiple',
46
- // 'name',
47
- 'pattern', 'placeholder',
48
- // 'readonly',
49
- // 'required',
50
- 'size', 'src', 'step',
51
- // 'type',
52
- 'value',
53
- 'width',
54
- // 'align', 'usemap',
55
- ];
56
-
57
- static valueChangingContentAttributes = [
58
- ...super.valueChangingContentAttributes,
59
- 'checked', 'max', 'maxlength', 'min', 'maxlength',
60
- 'multiple', 'pattern', 'step', 'type', 'value',
61
- ];
62
-
63
- /** @type {InstanceType<typeof CustomElement>['propChangedCallback']} */
64
- propChangedCallback(name, oldValue, newValue) {
65
- super.propChangedCallback(name, oldValue, newValue);
66
- switch (name) {
67
- case 'indeterminate':
68
- this.#input.indeterminate = newValue;
69
- break;
70
- case 'type':
71
- this.#input.type = newValue;
72
- break;
73
- case '_formAction':
74
- this.formAction = newValue;
75
- break;
76
- case '_height':
77
- this.height = newValue;
78
- break;
79
- case '_width':
80
- this.width = newValue;
81
- break;
82
- case 'checked':
83
- if (!this.type) {
84
- console.warn('unknown type?', this);
85
- }
86
- switch (this.type) {
87
- case 'checkbox':
88
- case 'radio':
89
- if (newValue) {
90
- this.elementInternals.setFormValue(this.value ?? 'on');
91
- if (this.type === 'radio') {
92
- this._notifyRadioChange(this.name, this.value ?? 'on');
93
- }
94
- } else {
95
- this.elementInternals.setFormValue(null);
96
- }
97
- break;
98
- default:
31
+ return Base
32
+ .mixin(ControlMixin)
33
+ .extend()
34
+ .observe({
35
+ accept: DOMString,
36
+ alt: DOMString,
37
+ dirName: { attr: 'dirname', ...DOMString },
38
+ _formAction: { attr: 'formaction' },
39
+ formEnctype: { attr: 'formenctype', ...DOMString },
40
+ formMethod: { attr: 'formmethod', ...DOMString },
41
+ formNoValidate: { attr: 'formNoValidate', type: 'boolean' },
42
+ formTarget: { attr: 'formtarget', ...DOMString },
43
+ _height: { attr: 'height', type: 'integer' },
44
+ indeterminate: { type: 'boolean', reflect: false },
45
+ max: DOMString,
46
+ maxLength: { attr: 'maxlength', type: 'integer', empty: -1 },
47
+ min: DOMString,
48
+ minLength: { attr: 'minlength', type: 'integer', empty: -1 },
49
+ multiple: 'boolean',
50
+ pattern: DOMString,
51
+ placeholder: DOMString,
52
+ size: { type: 'integer', empty: 20 },
53
+ src: DOMString,
54
+ step: DOMString,
55
+ // [CEReactions] attribute [LegacyNullToEmptyString] DOMString value;
56
+ _width: { attr: 'width', type: 'integer' },
57
+ })
58
+ .define({
59
+ // Alias for typescript
60
+ _input() { return /** @type {HTMLInputElement} */ (this.refs.control); },
61
+ })
62
+ .overrides({
63
+ controlTagName: 'input',
64
+ })
65
+ .on({
66
+ composed({ inline }) {
67
+ const { label, control } = this.refs;
68
+ // Expose [selected] to .checked
69
+ label.setAttribute('selected', '{checked}');
70
+ label.setAttribute('invalid', '{_invalid}');
71
+ label.setAttribute('indeterminate', '{indeterminate}');
72
+
73
+ control.setAttribute('checked', '{defaultChecked}');
74
+ control.setAttribute('height', '{_height}');
75
+ control.setAttribute('width', '{_width}');
76
+ control.setAttribute('value', '{_defaultValue}');
77
+ },
78
+
79
+ // TODO: Bind multiple
80
+ typeChanged() { this.onValueChangingContentAttribute(); },
81
+ defaultCheckedChanged() {
82
+ this._checked = this._input.checked;
83
+ },
84
+ minChanged() { this.onValueChangingContentAttribute(); },
85
+ minLengthChanged() { this.onValueChangingContentAttribute(); },
86
+ maxChanged() { this.onValueChangingContentAttribute(); },
87
+ maxLengthChanged() { this.onValueChangingContentAttribute(); },
88
+ multipleChanged() { this.onValueChangingContentAttribute(); },
89
+ patternChanged() { this.onValueChangingContentAttribute(); },
90
+ stepChanged() { this.onValueChangingContentAttribute(); },
91
+ defaultValueChanged() { this.onValueChangingContentAttribute(); },
92
+ _formResetChanged(oldValue, newValue) {
93
+ if (!newValue) return;
94
+ console.log('form reset');
95
+ const input = this._input;
96
+ input.value = this.defaultValue;
97
+ input.checked = this.defaultChecked;
98
+ this._value = input.value;
99
+ this._checked = input.checked;
100
+ this._checkedDirty = false;
101
+ },
102
+ attrs: {
103
+ accept: cloneAttributeCallback('accept', 'control'),
104
+ alt: cloneAttributeCallback('alt', 'control'),
105
+ dirname: cloneAttributeCallback('dirname', 'control'),
106
+ formenctype: cloneAttributeCallback('formenctype', 'control'),
107
+ formmethod: cloneAttributeCallback('formmethod', 'control'),
108
+ formnovalidate: cloneAttributeCallback('formnovalidate', 'control'),
109
+ formTarget: cloneAttributeCallback('formTarget', 'control'),
110
+ max: cloneAttributeCallback('max', 'control'),
111
+ maxlength: cloneAttributeCallback('maxlength', 'control'),
112
+ min: cloneAttributeCallback('min', 'control'),
113
+ minlength: cloneAttributeCallback('minlength', 'control'),
114
+ multiple: cloneAttributeCallback('multiple', 'control'),
115
+ pattern: cloneAttributeCallback('pattern', 'control'),
116
+ placeholder: cloneAttributeCallback('placeholder', 'control'),
117
+ size: cloneAttributeCallback('size', 'control'),
118
+ src: cloneAttributeCallback('src', 'control'),
119
+ step: cloneAttributeCallback('step', 'control'),
120
+ },
121
+ })
122
+ .overrides({
123
+ _onSetChecked(checked) {
124
+ // Apply user value to input and read back result to apply control to parse
125
+ this._input.checked = checked;
126
+ this._checked = this._input.checked;
127
+ },
128
+ _onSetValue(value) {
129
+ // Apply user value to input and read back result to apply control to parse
130
+ this._input.value = value;
131
+ this._value = this._input.value;
132
+ },
133
+ })
134
+ .methods({
135
+ /**
136
+ * @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#implicit-submission
137
+ * @param {Event} event
138
+ * @return {void}
139
+ */
140
+ performImplicitSubmission(event) {
141
+ const form = this.form;
142
+ if (!form) return;
143
+ /** @type {HTMLInputElement} */
144
+ let defaultButton;
145
+ const submissionBlockers = new Set();
146
+ for (const element of /** @type {HTMLCollectionOf<HTMLInputElement>} */ (form.elements)) {
147
+ if (element.type === 'submit' && !element.disabled && !element.matches(':disabled')) {
148
+ defaultButton ??= element;
149
+ break;
99
150
  }
100
- // Reinvoke change event for components tracking 'checked';
101
- // this.propChangedCallback('checked', oldValue, newValue);
102
- break;
103
- default:
104
- }
105
- }
106
151
 
107
- /** @type {CustomElement['attributeChangedCallback']} */
108
- attributeChangedCallback(name, oldValue, newValue) {
109
- super.attributeChangedCallback(name, oldValue, newValue);
110
- switch (name) {
111
- case 'aria-label':
112
- if (newValue == null) {
113
- this.#input.removeAttribute(name);
114
- if (!this.hasAttribute('aria-labelledby')) {
115
- this.#input.setAttribute('aria-labelledby', 'slot');
116
- }
117
- } else {
118
- this.#input.setAttribute(name, newValue);
119
- if (!this.hasAttribute('aria-labelledby')) {
120
- this.#input.removeAttribute('aria-labelledby');
121
- }
152
+ if (IMPLICIT_SUBMISSION_BLOCKING_TYPES.has(element.type)) {
153
+ submissionBlockers.add(element);
122
154
  }
123
- break;
124
- default:
125
- }
126
- }
127
-
128
- get #input() { return /** @type {HTMLInputElement} */ (this.refs.control); }
129
-
130
- static {
131
- this.on({
132
- composed() {
133
- const { label } = this.refs;
134
- // Expose [selected] to .checked
135
- label.setAttribute('selected', '{checked}');
136
- label.setAttribute('invalid', '{_invalid}');
137
- label.setAttribute('indeterminate', '{indeterminate}');
138
- },
139
- });
140
- this.childEvents({
141
- control: {
142
- keydown(event) {
143
- if (event.defaultPrevented) return;
144
- if (event.key !== 'Enter') return;
145
- if (/** @type {HTMLInputElement} */ (event.currentTarget).type === 'submit') return;
146
- this.performImplicitSubmission(event);
147
- },
148
- change(event) {
149
- if (this.disabledState) {
150
- event.preventDefault();
151
- event.stopImmediatePropagation();
152
- return;
153
- }
154
- const input = /** @type {HTMLInputElement} */ (event.currentTarget);
155
- this.checked = input.checked;
156
- },
157
- },
158
- });
159
- }
160
-
161
- /**
162
- * @see https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#implicit-submission
163
- * @param {Event} event
164
- * @return {void}
165
- */
166
- performImplicitSubmission(event) {
167
- const form = this.form;
168
- if (!form) return;
169
- /** @type {HTMLInputElement} */
170
- let defaultButton;
171
- const submissionBlockers = new Set();
172
- for (const element of /** @type {HTMLCollectionOf<HTMLInputElement>} */ (form.elements)) {
173
- if (element.type === 'submit' && !element.disabled && !element.matches(':disabled')) {
174
- defaultButton ??= element;
175
- break;
176
155
  }
177
-
178
- if (IMPLICIT_SUBMISSION_BLOCKING_TYPES.has(element.type)) {
179
- submissionBlockers.add(element);
156
+ if (defaultButton) {
157
+ defaultButton.click();
158
+ return;
180
159
  }
181
- }
182
- if (defaultButton) {
183
- defaultButton.click();
184
- return;
185
- }
186
- if (submissionBlockers.size > 1) return;
187
- this.form.submit();
188
- }
189
-
190
- /** @param {CustomEvent<[string, string]>} event */
191
- formIPCEvent(event) {
192
- if (event.target instanceof HTMLFormElement && event.target !== this.form) {
193
- console.warn('Control.formIPCEvent: Abort from wrong form');
194
- return;
195
- }
196
- if (this.type !== 'radio') {
197
- console.warn('Control.formIPCEvent: Abort from not radio');
198
- return;
199
- }
200
- const [name, value] = event.detail;
201
- if (this.name !== name) return;
202
- if (value === this.value) {
203
- // console.log('Control.formIPCEvent: Continue match', this.name, this.value);
204
- } else {
205
- this.checked = false;
206
- }
207
- }
208
-
209
- formResetCallback() {
210
- this.#input.value = this.defaultValue;
211
- this.#input.checked = this.defaultChecked;
212
- this._value = this.#input.value;
213
- this._checked = this.#input.checked;
214
- this._checkedDirty = false;
215
-
216
- super.formResetCallback();
217
- }
218
-
219
- get files() { return this.#input.files; }
220
-
221
- get select() { return this.#input.select; }
222
-
223
- get selectionDirection() { return this.#input.selectionDirection; }
224
-
225
- set selectionDirection(value) { this.#input.selectionDirection = value; }
226
-
227
- get selectionEnd() { return this.#input.selectionEnd; }
228
-
229
- set selectionEnd(value) { this.#input.selectionEnd = value; }
230
-
231
- get selectionStart() { return this.#input.selectionStart; }
232
-
233
- set selectionStart(value) { this.#input.selectionStart = value; }
234
-
235
- get setRangeText() { return this.#input.setRangeText; }
236
-
237
- get setSelectionRange() { return this.#input.setSelectionRange; }
238
-
239
- get showPicker() { return this.#input.showPicker; }
240
-
241
- get stepDown() { return this.#input.stepDown; }
242
-
243
- get stepUp() { return this.#input.stepUp; }
244
-
245
- get valueAsDate() { return this.#input.valueAsDate; }
246
-
247
- set valueAsDate(value) {
248
- this.#input.valueAsDate = value;
249
- this.value = this.#input.value;
250
- }
251
-
252
- get valueAsNumber() { return this.#input.valueAsNumber; }
160
+ if (submissionBlockers.size > 1) return;
161
+ this.form.submit();
162
+ },
163
+
164
+ })
165
+ .childEvents({
166
+ control: {
167
+ keydown(event) {
168
+ if (event.defaultPrevented) return;
169
+ if (event.key !== 'Enter') return;
170
+ if (/** @type {HTMLInputElement} */ (event.currentTarget).type === 'submit') return;
171
+ this.performImplicitSubmission(event);
172
+ },
173
+ change(event) {
174
+ if (this.disabledState) {
175
+ event.preventDefault();
176
+ event.stopImmediatePropagation();
177
+ return;
178
+ }
179
+ const input = /** @type {HTMLInputElement} */ (event.currentTarget);
180
+ console.debug('InputMixin: Will fire checked via change event');
181
+ this._checkedDirty = true;
182
+ this._checked = input.checked;
183
+ },
184
+ },
185
+ })
186
+ .define({
187
+ files() { return this._input.files; },
253
188
 
254
- set valueAsNumber(value) {
255
- this.#input.valueAsNumber = value;
256
- this.value = this.#input.value;
257
- }
189
+ select() { return this._input.select; },
258
190
 
259
- get height() { return this.#input.height; }
191
+ selectionDirection: {
192
+ get() { return this._input.selectionDirection; },
193
+ set(value) { this._input.selectionDirection = value; },
194
+ },
260
195
 
261
- set height(value) {
262
- this.#input.height = value;
263
- this._height = value;
264
- }
196
+ selectionEnd: {
197
+ get() { return this._input.selectionEnd; },
198
+ set(value) { this._input.selectionEnd = value; },
199
+ },
265
200
 
266
- get formAction() { return this.#input.formAction; }
201
+ selectionStart: {
202
+ get() { return this._input.selectionStart; },
203
+ set(value) { this._input.selectionStart = value; },
204
+ },
267
205
 
268
- set formAction(value) {
269
- this.#input.formAction = value;
270
- this._formAction = value;
271
- }
206
+ setRangeText() { return this._input.setRangeText; },
272
207
 
273
- get width() { return this.#input.width; }
208
+ setSelectionRange() { return this._input.setSelectionRange; },
274
209
 
275
- set width(value) {
276
- this.#input.width = value;
277
- this._width = value;
278
- }
279
- }
210
+ showPicker() { return this._input.showPicker; },
280
211
 
281
- Input.prototype.ariaControls = Input.prop('ariaControls');
212
+ stepDown() { return this._input.stepDown; },
282
213
 
283
- // https://html.spec.whatwg.org/multipage/input.html#htmlinputelement
214
+ stepUp() { return this._input.stepUp; },
284
215
 
285
- const DOMString = { nullParser: String, value: '' };
216
+ valueAsDate: {
217
+ get() { return this._input.valueAsDate; },
218
+ set(value) {
219
+ this._input.valueAsDate = value;
220
+ this.value = this._input.value;
221
+ },
222
+ },
286
223
 
287
- Input.prototype.accept = Input.prop('accept', DOMString);
288
- Input.prototype.alt = Input.prop('alt', DOMString);
289
- Input.prototype.defaultChecked = Input.prop('defaultChecked', { attr: 'checked', type: 'boolean' });
290
- Input.prototype._checkedDirty = Input.prop('_checkedDirty', 'boolean');
291
- // attribute boolean checked;
292
- Input.prototype._checked = Input.prop('_checked', 'boolean');
224
+ valueAsNumber: {
225
+ get() { return this._input.valueAsNumber; },
226
+ set(value) {
227
+ this._input.valueAsNumber = value;
228
+ this.value = this._input.value;
229
+ },
230
+ },
293
231
 
294
- // Exposed property based other watched properties
295
- Input.prototype.checked = Input.prop('checked', {
296
- reflect: false,
297
- type: 'boolean',
298
- get({ _checkedDirty, defaultChecked, _checked }) {
299
- if (!_checkedDirty) return defaultChecked;
300
- return _checked;
301
- },
302
- set(value) {
303
- this._checked = value;
304
- this._checkedDirty = true;
305
- },
306
- changedCallback(oldValue, newValue) {
307
- this.shadowRoot.getElementById('control').checked = newValue;
308
- },
309
- });
232
+ height: {
233
+ get() { return this._input.height; },
234
+ set(value) {
235
+ this._input.height = value;
236
+ this._height = value;
237
+ },
238
+ },
310
239
 
311
- Input.prototype.dirName = Input.prop('dirName', { attr: 'dirname', ...DOMString });
312
- Input.prototype._formAction = Input.prop('_formAction', { attr: 'formaction' });
313
- Input.prototype.formEnctype = Input.prop('formEnctype', { attr: 'formenctype', ...DOMString });
314
- Input.prototype.formMethod = Input.prop('formMethod', { attr: 'formmethod', ...DOMString });
315
- Input.prototype.formNoValidate = Input.prop('formnovalidate', { attr: 'formNoValidate', type: 'boolean' });
316
- Input.prototype.formTarget = Input.prop('formTarget', { attr: 'formtarget', ...DOMString });
317
- Input.prototype._height = Input.prop('_height', { attr: 'height', type: 'integer' });
318
- Input.prototype.indeterminate = Input.prop('indeterminate', { type: 'boolean', reflect: false });
319
- Input.prototype.max = Input.prop('max', DOMString);
320
- Input.prototype.maxLength = Input.prop('maxLength', { attr: 'maxlength', type: 'integer', empty: -1 });
321
- Input.prototype.min = Input.prop('min', DOMString);
322
- Input.prototype.minLength = Input.prop('minLength', { attr: 'minlength', type: 'integer', empty: -1 });
323
- Input.prototype.multiple = Input.prop('multiple', 'boolean');
324
- Input.prototype.pattern = Input.prop('pattern', DOMString);
325
- Input.prototype.placeholder = Input.prop('placeholder', DOMString);
326
- Input.prototype.size = Input.prop('size', { type: 'integer', empty: 20 });
327
- Input.prototype.src = Input.prop('src', DOMString);
328
- Input.prototype.step = Input.prop('step', DOMString);
329
- Input.prototype.type = Input.prop('type', DOMString);
330
- Input.prototype.defaultValue = Input.prop('defaultValue', { attr: 'value', ...DOMString });
331
- // [CEReactions] attribute [LegacyNullToEmptyString] DOMString value;
332
- Input.prototype._width = Input.prop('_width', { attr: 'width', type: 'integer' });
240
+ formAction: {
241
+ get() { return this._input.formAction; },
242
+ set(value) {
243
+ this._input.formAction = value;
244
+ this._formAction = value;
245
+ },
246
+ },
333
247
 
334
- return Input.tsClassFix();
248
+ width: {
249
+ get() { return this._input.width; },
250
+ set(value) {
251
+ this._input.width = value;
252
+ this._width = value;
253
+ },
254
+ },
255
+ });
335
256
  }
@@ -87,6 +87,7 @@ export default function TextFieldMixin(Base) {
87
87
  `,
88
88
  );
89
89
 
90
+ outline.setAttribute('label', '{label}');
90
91
  outline.setAttribute('invalid', '{invalid}');
91
92
  outline.setAttribute('errored', '{erroredState}');
92
93
  outlineLeft.after(html`
@@ -189,6 +190,18 @@ export default function TextFieldMixin(Base) {
189
190
  color: rgb(var(--mdw-color__on-surface));
190
191
  }
191
192
 
193
+ /** Guard against bleed */
194
+ :host([label][outlined]) {
195
+ --mdw-shape__size__top-start-size: min(var(--mdw-shape__size), 12px);
196
+ --mdw-shape__size__bottom-start-size: min(var(--mdw-shape__size), 12px);
197
+ --mdw-shape__size__top-end-size: min(var(--mdw-shape__size), 12px);
198
+ --mdw-shape__size__bottom-end-size: min(var(--mdw-shape__size), 12px);
199
+ }
200
+
201
+ #label[label][outlined] {
202
+ -webkit-mask-box-image-width: min(var(--mdw-shape__size), 12px);
203
+ }
204
+
192
205
  #label {
193
206
  position: relative;
194
207
 
@@ -324,7 +337,7 @@ export default function TextFieldMixin(Base) {
324
337
  }
325
338
 
326
339
  #state {
327
- --mdw-state__focus-opacity: 0;
340
+ --mdw-state__focused-opacity: 0;
328
341
  --mdw-state__pressed-opacity: 0;
329
342
  }
330
343
 
@@ -446,15 +459,6 @@ export default function TextFieldMixin(Base) {
446
459
  transform: translateY(-50%);
447
460
  }
448
461
 
449
- #label[label][outlined] {
450
- /** Guard against bleed */
451
- --mdw-shape__size__top-start-size: min(var(--mdw-shape__size), 12px);
452
- --mdw-shape__size__bottom-start-size: min(var(--mdw-shape__size), 12px);
453
- --mdw-shape__size__top-end-size: min(var(--mdw-shape__size), 12px);
454
- --mdw-shape__size__bottom-end-size: min(var(--mdw-shape__size), 12px);
455
- -webkit-mask-box-image-width: min(var(--mdw-shape__size), 12px);
456
- }
457
-
458
462
  :is(#prefix, #suffix):is([focused], [populated]) {
459
463
  opacity: 1;
460
464
  }
@@ -505,11 +509,13 @@ export default function TextFieldMixin(Base) {
505
509
  #outline {
506
510
  display: grid;
507
511
  grid-auto-flow: column;
508
- grid-template-columns: 12px minmax(0,auto) minmax(12px, 1fr);
512
+ grid-template-columns: 1fr 0 1fr;
509
513
 
510
514
  color: rgb(var(--mdw-color__outline));
515
+ }
511
516
 
512
-
517
+ #outline[label] {
518
+ grid-template-columns: 12px minmax(0,auto) minmax(12px, 1fr);
513
519
  }
514
520
 
515
521
  #outline[hovered] {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@shortfuse/materialdesignweb",
3
- "version": "0.7.1",
3
+ "version": "0.7.4",
4
4
  "description": "Material Design for Web",
5
5
  "type": "module",
6
6
  "bin": {
@@ -14,7 +14,8 @@
14
14
  "watch": "node build/esbuild.js docs/demo.js --outdir=docs --watch --metafile",
15
15
  "serve": "node build/esbuild.js docs/demo.js --outdir=docs --watch --metafile --serve --live",
16
16
  "pretest": "eslint --ignore-path .gitignore",
17
- "test": "c8 tap --no-coverage"
17
+ "test": "web-test-runner \"test/**/*.test.js\" --node-resolve",
18
+ "test:watch": "web-test-runner \"test/**/*.test.js\" --node-resolve --watch"
18
19
  },
19
20
  "browser": "dist/index.min.js",
20
21
  "repository": {
@@ -59,10 +60,14 @@
59
60
  },
60
61
  "homepage": "https://github.com/clshortfuse/materialdesignweb#readme",
61
62
  "devDependencies": {
63
+ "@esm-bundle/chai": "^4.3.4-fix.0",
62
64
  "@types/html-minifier-terser": "^7.0.0",
65
+ "@types/mocha": "^10.0.1",
63
66
  "@types/tap": "^15.0.7",
64
67
  "@typescript-eslint/eslint-plugin": "^5.30.7",
65
68
  "@typescript-eslint/parser": "^5.30.7",
69
+ "@web/test-runner": "^0.15.2",
70
+ "@web/test-runner-playwright": "^0.9.0",
66
71
  "acorn": "^8.8.2",
67
72
  "acorn-walk": "^8.2.0",
68
73
  "browserslist-to-esbuild": "^1.1.1",
@@ -86,8 +91,6 @@
86
91
  "stylelint-no-unsupported-browser-features": "github:clshortfuse/stylelint-no-unsupported-browser-features#0a51157",
87
92
  "stylelint-order": "^6.0.2",
88
93
  "stylelint-use-logical-spec": "^5.0.0",
89
- "tap": "^16.3.0",
90
- "typescript": "^5.0.2",
91
- "webdriverio": "^7.23.0"
94
+ "typescript": "^5.0.2"
92
95
  }
93
96
  }