@kubex/zinc 1.0.113 → 1.0.114
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/.github/workflows/eleventy_build.yml +5 -5
- package/.github/workflows/js_build_and_deploy.yaml +1 -1
- package/dist/custom-elements.json +2636 -421
- package/dist/vscode.html-custom-data.json +96 -16
- package/dist/web-types.json +268 -31
- package/dist/zn.d.ts +517 -340
- package/dist/zn.min.css +1 -1
- package/dist/zn.min.js +942 -863
- package/docs/eleventy.config.cjs +4 -2
- package/docs/pages/components/header.md +13 -0
- package/docs/pages/components/inline-edit.md +20 -0
- package/docs/pages/components/item.md +12 -1
- package/docs/pages/components/page.md +149 -0
- package/docs/pages/components/progress-bar.md +16 -0
- package/docs/pages/components/select.md +25 -0
- package/docs/pages/components/split-pane.md +6 -7
- package/docs/pages/components/tabs.md +59 -9
- package/package.json +8 -5
- package/scripts/build.js +40 -19
- package/scss/_global-spacing.scss +12 -1
- package/scss/_root.scss +34 -2
- package/scss/shared/layout.scss +15 -2
- package/scss/themes/_light.scss +1 -1
- package/src/components/absolute-container/absolute-container.component.ts +9 -25
- package/src/components/animated-button/animated-button.test.ts +8 -8
- package/src/components/button/button.component.ts +3 -1
- package/src/components/button/button.scss +5 -0
- package/src/components/button-menu/button-menu.component.ts +36 -21
- package/src/components/button-menu/button-menu.test.ts +55 -1
- package/src/components/chart/chart.component.ts +7 -7
- package/src/components/collapsible/collapsible.component.ts +7 -14
- package/src/components/cols/cols.scss +1 -1
- package/src/components/content-block/content-block.component.ts +11 -33
- package/src/components/data-select/data-select.component.ts +7 -11
- package/src/components/data-table/data-table.component.ts +10 -14
- package/src/components/editor/modules/toolbar/toolbar.component.ts +5 -9
- package/src/components/expanding-action/expanding-action.component.ts +37 -43
- package/src/components/form-group/form-group.component.ts +8 -3
- package/src/components/header/header.component.ts +6 -5
- package/src/components/header/header.scss +31 -25
- package/src/components/inline-edit/inline-edit.component.ts +13 -1
- package/src/components/inline-edit/inline-edit.test.ts +50 -0
- package/src/components/input/input.test.ts +77 -0
- package/src/components/input-group/input-group.test.ts +2 -10
- package/src/components/item/item.component.ts +12 -6
- package/src/components/item/item.scss +1 -1
- package/src/components/item/item.test.ts +8 -0
- package/src/components/navbar/navbar.component.ts +222 -48
- package/src/components/navbar/navbar.scss +134 -21
- package/src/components/navbar/navbar.test.ts +114 -9
- package/src/components/option/option.scss +5 -7
- package/src/components/option/option.test.ts +30 -0
- package/src/components/page/index.ts +13 -0
- package/src/components/page/page.component.ts +406 -0
- package/src/components/page/page.scss +325 -0
- package/src/components/page/page.test.ts +397 -0
- package/src/components/page-nav/page-nav.scss +7 -5
- package/src/components/pane/pane.component.ts +2 -2
- package/src/components/pane/pane.scss +0 -1
- package/src/components/pane/pane.test.ts +31 -0
- package/src/components/panel/panel.scss +1 -0
- package/src/components/progress-bar/progress-bar.component.ts +5 -2
- package/src/components/progress-bar/progress-bar.scss +17 -1
- package/src/components/progress-bar/progress-bar.test.ts +12 -0
- package/src/components/scroll-container/scroll-container.component.ts +22 -21
- package/src/components/select/select.component.ts +240 -38
- package/src/components/select/select.scss +15 -0
- package/src/components/select/select.test.ts +477 -0
- package/src/components/settings-container/settings-container.component.ts +15 -19
- package/src/components/settings-container/settings-container.scss +8 -8
- package/src/components/sidebar/sidebar.component.ts +11 -11
- package/src/components/simple-chart/simple-chart.component.ts +6 -7
- package/src/components/sp/sp.scss +46 -15
- package/src/components/sp/sp.test.ts +15 -0
- package/src/components/split-pane/split-pane.component.ts +153 -26
- package/src/components/split-pane/split-pane.scss +89 -13
- package/src/components/split-pane/split-pane.test.ts +187 -0
- package/src/components/style/style.component.ts +8 -0
- package/src/components/tab/index.ts +13 -0
- package/src/components/tab/tab.component.ts +25 -0
- package/src/components/tab/tab.scss +7 -0
- package/src/components/table/table.component.ts +7 -2
- package/src/components/tabs/tabs.component.ts +75 -70
- package/src/components/tabs/tabs.scss +1 -1
- package/src/components/textarea/textarea.component.ts +5 -8
- package/src/components/translation-group/translation-group.component.ts +15 -14
- package/src/components/translations/translations.component.ts +18 -17
- package/src/components/translations/translations.scss +1 -0
- package/src/components/translations/translations.test.ts +3 -2
- package/src/internal/form.ts +234 -0
- package/src/internal/theme.ts +26 -46
- package/src/internal/zinc-element.ts +12 -7
- package/src/utilities/form.ts +1 -0
- package/src/wc.scss +1 -1
- package/src/zinc.ts +3 -0
|
@@ -176,4 +176,481 @@ describe('<zn-select>', () => {
|
|
|
176
176
|
});
|
|
177
177
|
|
|
178
178
|
});
|
|
179
|
+
|
|
180
|
+
describe('free-text', () => {
|
|
181
|
+
const type = (el: ZnSelect, text: string) => {
|
|
182
|
+
const displayInput = el.shadowRoot!.querySelector<HTMLInputElement>('.select__display-input')!;
|
|
183
|
+
displayInput.value = text;
|
|
184
|
+
displayInput.dispatchEvent(new Event('input'));
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
const pressEnter = (el: ZnSelect) => {
|
|
188
|
+
const displayInput = el.shadowRoot!.querySelector<HTMLInputElement>('.select__display-input')!;
|
|
189
|
+
displayInput.dispatchEvent(new KeyboardEvent('keydown', {
|
|
190
|
+
key: 'Enter',
|
|
191
|
+
bubbles: true,
|
|
192
|
+
composed: true,
|
|
193
|
+
cancelable: true
|
|
194
|
+
}));
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
it('commits typed text not in the list as the value on Enter', async () => {
|
|
198
|
+
const el = await fixture<ZnSelect>(html`
|
|
199
|
+
<zn-select free-text>
|
|
200
|
+
<zn-option value="apple">Apple</zn-option>
|
|
201
|
+
</zn-select>
|
|
202
|
+
`);
|
|
203
|
+
await el.updateComplete;
|
|
204
|
+
await el.show();
|
|
205
|
+
await el.updateComplete;
|
|
206
|
+
|
|
207
|
+
type(el, 'kiwi');
|
|
208
|
+
await el.updateComplete;
|
|
209
|
+
pressEnter(el);
|
|
210
|
+
await el.updateComplete;
|
|
211
|
+
|
|
212
|
+
expect(el.value).to.equal('kiwi');
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
it('adds typed values as tags in multiple mode, keeping the dropdown open', async () => {
|
|
216
|
+
const el = await fixture<ZnSelect>(html`
|
|
217
|
+
<zn-select free-text multiple></zn-select>
|
|
218
|
+
`);
|
|
219
|
+
await el.updateComplete;
|
|
220
|
+
await el.show();
|
|
221
|
+
await el.updateComplete;
|
|
222
|
+
|
|
223
|
+
type(el, 'kiwi');
|
|
224
|
+
await el.updateComplete;
|
|
225
|
+
pressEnter(el);
|
|
226
|
+
await el.updateComplete;
|
|
227
|
+
|
|
228
|
+
type(el, 'mango');
|
|
229
|
+
await el.updateComplete;
|
|
230
|
+
pressEnter(el);
|
|
231
|
+
await el.updateComplete;
|
|
232
|
+
|
|
233
|
+
expect(el.value).to.deep.equal(['kiwi', 'mango']);
|
|
234
|
+
expect(el.open).to.be.true;
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it('selects the existing option instead of creating a duplicate on exact match', async () => {
|
|
238
|
+
const el = await fixture<ZnSelect>(html`
|
|
239
|
+
<zn-select free-text>
|
|
240
|
+
<zn-option value="apple">Apple</zn-option>
|
|
241
|
+
</zn-select>
|
|
242
|
+
`);
|
|
243
|
+
await el.updateComplete;
|
|
244
|
+
await el.show();
|
|
245
|
+
await el.updateComplete;
|
|
246
|
+
|
|
247
|
+
type(el, 'Apple'); // case-insensitive match of the existing label
|
|
248
|
+
await el.updateComplete;
|
|
249
|
+
pressEnter(el);
|
|
250
|
+
await el.updateComplete;
|
|
251
|
+
|
|
252
|
+
expect(el.value).to.equal('apple');
|
|
253
|
+
expect(el.querySelectorAll('zn-option').length).to.equal(1);
|
|
254
|
+
expect(el.querySelector('[data-free-text]')).to.be.null;
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
it('shows an Add row for non-matching text and commits it on click', async () => {
|
|
258
|
+
const el = await fixture<ZnSelect>(html`
|
|
259
|
+
<zn-select free-text>
|
|
260
|
+
<zn-option value="apple">Apple</zn-option>
|
|
261
|
+
</zn-select>
|
|
262
|
+
`);
|
|
263
|
+
await el.updateComplete;
|
|
264
|
+
await el.show();
|
|
265
|
+
await el.updateComplete;
|
|
266
|
+
|
|
267
|
+
type(el, 'kiwi');
|
|
268
|
+
await el.updateComplete;
|
|
269
|
+
|
|
270
|
+
const addRow = el.shadowRoot!.querySelector<HTMLElement>('.select__add-option');
|
|
271
|
+
expect(addRow, 'Add row should be visible for non-matching text').to.exist;
|
|
272
|
+
|
|
273
|
+
addRow!.dispatchEvent(new MouseEvent('mousedown', {bubbles: true, composed: true, cancelable: true}));
|
|
274
|
+
addRow!.dispatchEvent(new MouseEvent('mouseup', {bubbles: true, composed: true}));
|
|
275
|
+
await el.updateComplete;
|
|
276
|
+
|
|
277
|
+
expect(el.value).to.equal('kiwi');
|
|
278
|
+
});
|
|
279
|
+
|
|
280
|
+
it('commits the Add row on click without also selecting an option', async () => {
|
|
281
|
+
const el = await fixture<ZnSelect>(html`
|
|
282
|
+
<zn-select free-text multiple>
|
|
283
|
+
<zn-option value="apple">Apple</zn-option>
|
|
284
|
+
<zn-option value="banana">Banana</zn-option>
|
|
285
|
+
</zn-select>
|
|
286
|
+
`);
|
|
287
|
+
await el.updateComplete;
|
|
288
|
+
await el.show();
|
|
289
|
+
await el.updateComplete;
|
|
290
|
+
|
|
291
|
+
// "ap" filters to Apple (still visible) and offers Add "ap" at the top of the listbox
|
|
292
|
+
type(el, 'ap');
|
|
293
|
+
await el.updateComplete;
|
|
294
|
+
|
|
295
|
+
const addRow = el.shadowRoot!.querySelector<HTMLElement>('.select__add-option')!;
|
|
296
|
+
addRow.dispatchEvent(new MouseEvent('mousedown', {bubbles: true, composed: true, cancelable: true}));
|
|
297
|
+
await el.updateComplete;
|
|
298
|
+
|
|
299
|
+
// The mouseup hits whatever now sits under the cursor. If committing on mousedown removed the Add
|
|
300
|
+
// row (the bug), the first option shifts up into that spot and would be wrongly selected on mouseup.
|
|
301
|
+
const underCursor = el.shadowRoot!.querySelector<HTMLElement>('.select__add-option')
|
|
302
|
+
?? el.querySelector<HTMLElement>('zn-option');
|
|
303
|
+
underCursor!.dispatchEvent(new MouseEvent('mouseup', {bubbles: true, composed: true}));
|
|
304
|
+
await el.updateComplete;
|
|
305
|
+
|
|
306
|
+
// Only the typed value is committed — the first option ("apple") must not be selected too
|
|
307
|
+
expect(el.value).to.deep.equal(['ap']);
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('keeps the Add row text in sync as the user keeps typing', async () => {
|
|
311
|
+
const el = await fixture<ZnSelect>(html`
|
|
312
|
+
<zn-select free-text>
|
|
313
|
+
<zn-option value="apple">Apple</zn-option>
|
|
314
|
+
</zn-select>
|
|
315
|
+
`);
|
|
316
|
+
await el.updateComplete;
|
|
317
|
+
await el.show();
|
|
318
|
+
await el.updateComplete;
|
|
319
|
+
|
|
320
|
+
type(el, 'te');
|
|
321
|
+
await el.updateComplete;
|
|
322
|
+
expect(el.shadowRoot!.querySelector('.select__add-option')!.textContent!.trim())
|
|
323
|
+
.to.equal('Add "te"');
|
|
324
|
+
|
|
325
|
+
// Continue typing without crossing the match/no-match boundary
|
|
326
|
+
type(el, 'testssdfs');
|
|
327
|
+
await el.updateComplete;
|
|
328
|
+
expect(el.shadowRoot!.querySelector('.select__add-option')!.textContent!.trim())
|
|
329
|
+
.to.equal('Add "testssdfs"');
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it('does not show the Add row when the text exactly matches an option', async () => {
|
|
333
|
+
const el = await fixture<ZnSelect>(html`
|
|
334
|
+
<zn-select free-text>
|
|
335
|
+
<zn-option value="apple">Apple</zn-option>
|
|
336
|
+
</zn-select>
|
|
337
|
+
`);
|
|
338
|
+
await el.updateComplete;
|
|
339
|
+
await el.show();
|
|
340
|
+
await el.updateComplete;
|
|
341
|
+
|
|
342
|
+
type(el, 'Apple');
|
|
343
|
+
await el.updateComplete;
|
|
344
|
+
|
|
345
|
+
expect(el.shadowRoot!.querySelector('.select__add-option')).to.be.null;
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
it('commits pending typed text when the dropdown closes', async () => {
|
|
349
|
+
const el = await fixture<ZnSelect>(html`<zn-select free-text></zn-select>`);
|
|
350
|
+
await el.updateComplete;
|
|
351
|
+
await el.show();
|
|
352
|
+
await el.updateComplete;
|
|
353
|
+
|
|
354
|
+
type(el, 'kiwi');
|
|
355
|
+
await el.updateComplete;
|
|
356
|
+
|
|
357
|
+
await el.hide();
|
|
358
|
+
await el.updateComplete;
|
|
359
|
+
|
|
360
|
+
expect(el.value).to.equal('kiwi');
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it('removes the backing option from the DOM when a custom tag is removed', async () => {
|
|
364
|
+
const el = await fixture<ZnSelect>(html`<zn-select free-text multiple></zn-select>`);
|
|
365
|
+
await el.updateComplete;
|
|
366
|
+
await el.show();
|
|
367
|
+
await el.updateComplete;
|
|
368
|
+
|
|
369
|
+
type(el, 'kiwi');
|
|
370
|
+
await el.updateComplete;
|
|
371
|
+
pressEnter(el);
|
|
372
|
+
await el.updateComplete;
|
|
373
|
+
|
|
374
|
+
expect(el.querySelectorAll('[data-free-text]').length).to.equal(1);
|
|
375
|
+
|
|
376
|
+
const chip = el.shadowRoot!.querySelector('.select__tags zn-chip')!;
|
|
377
|
+
chip.dispatchEvent(new CustomEvent('zn-remove', {bubbles: true, composed: true}));
|
|
378
|
+
await el.updateComplete;
|
|
379
|
+
|
|
380
|
+
expect(el.querySelectorAll('[data-free-text]').length).to.equal(0);
|
|
381
|
+
expect(el.value).to.deep.equal([]);
|
|
382
|
+
});
|
|
383
|
+
|
|
384
|
+
it('materialises an unmatched initial value as a custom option', async () => {
|
|
385
|
+
const el = await fixture<ZnSelect>(html`
|
|
386
|
+
<zn-select free-text value="custom-thing">
|
|
387
|
+
<zn-option value="apple">Apple</zn-option>
|
|
388
|
+
</zn-select>
|
|
389
|
+
`);
|
|
390
|
+
await el.updateComplete;
|
|
391
|
+
|
|
392
|
+
expect(el.value).to.equal('custom-thing');
|
|
393
|
+
const created = el.querySelector('[data-free-text]');
|
|
394
|
+
expect(created, 'a backing option should be created').to.exist;
|
|
395
|
+
expect(created!.getAttribute('value')).to.equal('custom-thing');
|
|
396
|
+
expect(el.displayLabel).to.equal('custom-thing');
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
it('does not add a duplicate when the same custom value is committed twice', async () => {
|
|
400
|
+
const el = await fixture<ZnSelect>(html`<zn-select free-text multiple></zn-select>`);
|
|
401
|
+
await el.updateComplete;
|
|
402
|
+
await el.show();
|
|
403
|
+
await el.updateComplete;
|
|
404
|
+
|
|
405
|
+
type(el, 'kiwi');
|
|
406
|
+
await el.updateComplete;
|
|
407
|
+
pressEnter(el);
|
|
408
|
+
await el.updateComplete;
|
|
409
|
+
|
|
410
|
+
type(el, 'kiwi');
|
|
411
|
+
await el.updateComplete;
|
|
412
|
+
pressEnter(el);
|
|
413
|
+
await el.updateComplete;
|
|
414
|
+
|
|
415
|
+
expect(el.value).to.deep.equal(['kiwi']);
|
|
416
|
+
expect(el.querySelectorAll('[data-free-text]').length).to.equal(1);
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
it('enables an editable, filtering input on its own (without search)', async () => {
|
|
420
|
+
const el = await fixture<ZnSelect>(html`
|
|
421
|
+
<zn-select free-text>
|
|
422
|
+
<zn-option value="apple">Apple</zn-option>
|
|
423
|
+
<zn-option value="banana">Banana</zn-option>
|
|
424
|
+
</zn-select>
|
|
425
|
+
`);
|
|
426
|
+
await el.updateComplete;
|
|
427
|
+
await el.show();
|
|
428
|
+
await el.updateComplete;
|
|
429
|
+
|
|
430
|
+
const displayInput = el.shadowRoot!.querySelector<HTMLInputElement>('.select__display-input')!;
|
|
431
|
+
expect(displayInput.readOnly).to.be.false;
|
|
432
|
+
|
|
433
|
+
type(el, 'app');
|
|
434
|
+
await el.updateComplete;
|
|
435
|
+
|
|
436
|
+
const visible = [...el.querySelectorAll<ZnOption>('zn-option')].filter(o => !o.hidden);
|
|
437
|
+
expect(visible.map(o => o.value)).to.deep.equal(['apple']);
|
|
438
|
+
});
|
|
439
|
+
|
|
440
|
+
it('submits committed free-text values as form data', async () => {
|
|
441
|
+
const form = await fixture<HTMLFormElement>(html`
|
|
442
|
+
<form>
|
|
443
|
+
<zn-select name="fruits" free-text multiple></zn-select>
|
|
444
|
+
</form>
|
|
445
|
+
`);
|
|
446
|
+
const el = form.querySelector<ZnSelect>('zn-select')!;
|
|
447
|
+
await el.updateComplete;
|
|
448
|
+
await el.show();
|
|
449
|
+
await el.updateComplete;
|
|
450
|
+
|
|
451
|
+
type(el, 'kiwi');
|
|
452
|
+
await el.updateComplete;
|
|
453
|
+
pressEnter(el);
|
|
454
|
+
await el.updateComplete;
|
|
455
|
+
|
|
456
|
+
type(el, 'mango');
|
|
457
|
+
await el.updateComplete;
|
|
458
|
+
pressEnter(el);
|
|
459
|
+
await el.updateComplete;
|
|
460
|
+
|
|
461
|
+
const formData = new FormData(form);
|
|
462
|
+
expect(formData.getAll('fruits')).to.deep.equal(['kiwi', 'mango']);
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
it('cancels uncommitted free-text on Escape instead of committing it', async () => {
|
|
466
|
+
const el = await fixture<ZnSelect>(html`<zn-select free-text></zn-select>`);
|
|
467
|
+
await el.updateComplete;
|
|
468
|
+
await el.show();
|
|
469
|
+
await el.updateComplete;
|
|
470
|
+
|
|
471
|
+
type(el, 'kiwi');
|
|
472
|
+
await el.updateComplete;
|
|
473
|
+
|
|
474
|
+
const displayInput = el.shadowRoot!.querySelector<HTMLInputElement>('.select__display-input')!;
|
|
475
|
+
displayInput.dispatchEvent(new KeyboardEvent('keydown', {
|
|
476
|
+
key: 'Escape',
|
|
477
|
+
bubbles: true,
|
|
478
|
+
composed: true,
|
|
479
|
+
cancelable: true
|
|
480
|
+
}));
|
|
481
|
+
await el.updateComplete;
|
|
482
|
+
|
|
483
|
+
expect(el.value).to.equal('');
|
|
484
|
+
});
|
|
485
|
+
|
|
486
|
+
it('removes a previous custom entry when a different option is selected (single)', async () => {
|
|
487
|
+
const el = await fixture<ZnSelect>(html`
|
|
488
|
+
<zn-select free-text>
|
|
489
|
+
<zn-option value="apple">Apple</zn-option>
|
|
490
|
+
</zn-select>
|
|
491
|
+
`);
|
|
492
|
+
await el.updateComplete;
|
|
493
|
+
await el.show();
|
|
494
|
+
await el.updateComplete;
|
|
495
|
+
|
|
496
|
+
type(el, 'kiwi');
|
|
497
|
+
await el.updateComplete;
|
|
498
|
+
pressEnter(el);
|
|
499
|
+
await el.updateComplete;
|
|
500
|
+
expect(el.value).to.equal('kiwi');
|
|
501
|
+
expect(el.querySelectorAll('[data-free-text]').length).to.equal(1);
|
|
502
|
+
|
|
503
|
+
// Reopen and pick a real option
|
|
504
|
+
await el.show();
|
|
505
|
+
await el.updateComplete;
|
|
506
|
+
const apple = el.querySelector<ZnOption>('zn-option[value="apple"]')!;
|
|
507
|
+
apple.dispatchEvent(new MouseEvent('mouseup', {bubbles: true, composed: true}));
|
|
508
|
+
await el.updateComplete;
|
|
509
|
+
|
|
510
|
+
expect(el.value).to.equal('apple');
|
|
511
|
+
expect(el.querySelectorAll('[data-free-text]').length).to.equal(0);
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
it('shows a committed custom option as a selected row in the listbox', async () => {
|
|
515
|
+
const el = await fixture<ZnSelect>(html`
|
|
516
|
+
<zn-select free-text>
|
|
517
|
+
<zn-option value="apple">Apple</zn-option>
|
|
518
|
+
</zn-select>
|
|
519
|
+
`);
|
|
520
|
+
await el.updateComplete;
|
|
521
|
+
await el.show();
|
|
522
|
+
await el.updateComplete;
|
|
523
|
+
|
|
524
|
+
type(el, 'kiwi');
|
|
525
|
+
await el.updateComplete;
|
|
526
|
+
pressEnter(el);
|
|
527
|
+
await el.updateComplete;
|
|
528
|
+
|
|
529
|
+
// Reopen the dropdown
|
|
530
|
+
await el.show();
|
|
531
|
+
await el.updateComplete;
|
|
532
|
+
|
|
533
|
+
const custom = el.querySelector<ZnOption>('[data-free-text]')!;
|
|
534
|
+
expect(custom, 'custom option should exist').to.exist;
|
|
535
|
+
expect(custom.hidden, 'custom option should be visible so it can be deselected').to.be.false;
|
|
536
|
+
expect(custom.selected, 'custom option should be selected').to.be.true;
|
|
537
|
+
});
|
|
538
|
+
|
|
539
|
+
it('removes a committed custom option when it is deselected from the dropdown', async () => {
|
|
540
|
+
const el = await fixture<ZnSelect>(html`<zn-select free-text multiple></zn-select>`);
|
|
541
|
+
await el.updateComplete;
|
|
542
|
+
await el.show();
|
|
543
|
+
await el.updateComplete;
|
|
544
|
+
|
|
545
|
+
type(el, 'kiwi');
|
|
546
|
+
await el.updateComplete;
|
|
547
|
+
pressEnter(el);
|
|
548
|
+
await el.updateComplete;
|
|
549
|
+
expect(el.value).to.deep.equal(['kiwi']);
|
|
550
|
+
|
|
551
|
+
// Click the custom option's row in the listbox to deselect it
|
|
552
|
+
const custom = el.querySelector<ZnOption>('[data-free-text]')!;
|
|
553
|
+
custom.dispatchEvent(new MouseEvent('mouseup', {bubbles: true, composed: true}));
|
|
554
|
+
await el.updateComplete;
|
|
555
|
+
|
|
556
|
+
expect(el.value).to.deep.equal([]);
|
|
557
|
+
expect(el.querySelectorAll('[data-free-text]').length).to.equal(0);
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it('can re-add a free-text value after it was removed', async () => {
|
|
561
|
+
const el = await fixture<ZnSelect>(html`<zn-select free-text multiple></zn-select>`);
|
|
562
|
+
await el.updateComplete;
|
|
563
|
+
await el.show();
|
|
564
|
+
await el.updateComplete;
|
|
565
|
+
|
|
566
|
+
// Add "foo"
|
|
567
|
+
type(el, 'foo');
|
|
568
|
+
await el.updateComplete;
|
|
569
|
+
pressEnter(el);
|
|
570
|
+
await el.updateComplete;
|
|
571
|
+
expect(el.value).to.deep.equal(['foo']);
|
|
572
|
+
|
|
573
|
+
// Remove it via the tag's remove button
|
|
574
|
+
const chip = el.shadowRoot!.querySelector('.select__tags zn-chip')!;
|
|
575
|
+
chip.dispatchEvent(new CustomEvent('zn-remove', {bubbles: true, composed: true}));
|
|
576
|
+
await el.updateComplete;
|
|
577
|
+
expect(el.value).to.deep.equal([]);
|
|
578
|
+
expect(el.querySelectorAll('[data-free-text]').length).to.equal(0);
|
|
579
|
+
|
|
580
|
+
// Add the same value again
|
|
581
|
+
type(el, 'foo');
|
|
582
|
+
await el.updateComplete;
|
|
583
|
+
pressEnter(el);
|
|
584
|
+
await el.updateComplete;
|
|
585
|
+
expect(el.value).to.deep.equal(['foo']);
|
|
586
|
+
expect(el.querySelectorAll('[data-free-text]').length).to.equal(1);
|
|
587
|
+
});
|
|
588
|
+
|
|
589
|
+
it('does not put the "N selected" summary in the input placeholder in multiple mode', async () => {
|
|
590
|
+
const el = await fixture<ZnSelect>(html`
|
|
591
|
+
<zn-select free-text multiple>
|
|
592
|
+
<zn-option value="apple">Apple</zn-option>
|
|
593
|
+
</zn-select>
|
|
594
|
+
`);
|
|
595
|
+
await el.updateComplete;
|
|
596
|
+
await el.show();
|
|
597
|
+
await el.updateComplete;
|
|
598
|
+
|
|
599
|
+
// Select an option — in multiple+search the dropdown stays open
|
|
600
|
+
const apple = el.querySelector<ZnOption>('zn-option[value="apple"]')!;
|
|
601
|
+
apple.dispatchEvent(new MouseEvent('mouseup', {bubbles: true, composed: true}));
|
|
602
|
+
await el.updateComplete;
|
|
603
|
+
|
|
604
|
+
const input = el.shadowRoot!.querySelector<HTMLInputElement>('.select__display-input')!;
|
|
605
|
+
// The tags convey the selection; the input placeholder must not also show "1 option selected"
|
|
606
|
+
expect(input.placeholder).to.equal('');
|
|
607
|
+
});
|
|
608
|
+
|
|
609
|
+
it('lets keyboard navigation reach the Add row over a closer-matching option', async () => {
|
|
610
|
+
const el = await fixture<ZnSelect>(html`
|
|
611
|
+
<zn-select free-text>
|
|
612
|
+
<zn-option value="apple">Apple</zn-option>
|
|
613
|
+
</zn-select>
|
|
614
|
+
`);
|
|
615
|
+
await el.updateComplete;
|
|
616
|
+
await el.show();
|
|
617
|
+
await el.updateComplete;
|
|
618
|
+
|
|
619
|
+
type(el, 'app'); // matches Apple AND offers Add "app"
|
|
620
|
+
await el.updateComplete;
|
|
621
|
+
|
|
622
|
+
// ArrowDown highlights the Add row (it sits at the top of the list)
|
|
623
|
+
const input = el.shadowRoot!.querySelector<HTMLInputElement>('.select__display-input')!;
|
|
624
|
+
input.dispatchEvent(new KeyboardEvent('keydown', {
|
|
625
|
+
key: 'ArrowDown',
|
|
626
|
+
bubbles: true,
|
|
627
|
+
composed: true,
|
|
628
|
+
cancelable: true
|
|
629
|
+
}));
|
|
630
|
+
await el.updateComplete;
|
|
631
|
+
|
|
632
|
+
// Enter commits the typed value, not the matching option
|
|
633
|
+
pressEnter(el);
|
|
634
|
+
await el.updateComplete;
|
|
635
|
+
|
|
636
|
+
expect(el.value).to.equal('app');
|
|
637
|
+
});
|
|
638
|
+
|
|
639
|
+
it('renders the Add row text at the small option font size', async () => {
|
|
640
|
+
const el = await fixture<ZnSelect>(html`
|
|
641
|
+
<zn-select free-text style="--zn-font-size-small: 11px; --zn-font-size-medium: 99px;">
|
|
642
|
+
<zn-option value="apple">Apple</zn-option>
|
|
643
|
+
</zn-select>
|
|
644
|
+
`);
|
|
645
|
+
await el.updateComplete;
|
|
646
|
+
await el.show();
|
|
647
|
+
await el.updateComplete;
|
|
648
|
+
|
|
649
|
+
type(el, 'kiwi');
|
|
650
|
+
await el.updateComplete;
|
|
651
|
+
|
|
652
|
+
const addRow = el.shadowRoot!.querySelector<HTMLElement>('.select__add-option')!;
|
|
653
|
+
expect(getComputedStyle(addRow).fontSize).to.equal('11px');
|
|
654
|
+
});
|
|
655
|
+
});
|
|
179
656
|
});
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {classMap} from "lit/directives/class-map.js";
|
|
2
2
|
import {type CSSResultGroup, html, unsafeCSS} from 'lit';
|
|
3
3
|
import {deepQuerySelectorAll} from "../../utilities/query";
|
|
4
|
+
import {MutationController} from '@lit-labs/observers/mutation-controller.js';
|
|
4
5
|
import {property, state} from "lit/decorators.js";
|
|
5
6
|
import {Store} from "../../internal/storage";
|
|
6
7
|
import {type ZnChangeEvent} from "../../events/zn-change";
|
|
@@ -43,7 +44,19 @@ export default class ZnSettingsContainer extends ZincElement {
|
|
|
43
44
|
@property({attribute: 'store-key'}) storeKey: string;
|
|
44
45
|
@property({attribute: 'no-scroll'}) noScroll: boolean;
|
|
45
46
|
|
|
46
|
-
private _mutationObserver
|
|
47
|
+
private readonly _mutationObserver = new MutationController(this, {
|
|
48
|
+
target: null,
|
|
49
|
+
config: {childList: true, subtree: true, attributes: true},
|
|
50
|
+
callback: mutations => {
|
|
51
|
+
for (const mutation of mutations) {
|
|
52
|
+
if (mutation.type === 'attributes' && mutation.attributeName === 'hidden') {
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
this.scheduleUpdateFilters();
|
|
56
|
+
break;
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
});
|
|
47
60
|
|
|
48
61
|
private _updateFiltersScheduled = false;
|
|
49
62
|
|
|
@@ -72,24 +85,7 @@ export default class ZnSettingsContainer extends ZincElement {
|
|
|
72
85
|
|
|
73
86
|
this.updateFilters();
|
|
74
87
|
|
|
75
|
-
this._mutationObserver
|
|
76
|
-
for (const mutation of mutations) {
|
|
77
|
-
if (mutation.type === 'attributes' && mutation.attributeName === 'hidden') {
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
this.scheduleUpdateFilters();
|
|
81
|
-
break;
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
this._mutationObserver.observe(this, {childList: true, subtree: true, attributes: true});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
disconnectedCallback() {
|
|
88
|
-
super.disconnectedCallback();
|
|
89
|
-
if (this._mutationObserver) {
|
|
90
|
-
this._mutationObserver.disconnect();
|
|
91
|
-
this._mutationObserver = null;
|
|
92
|
-
}
|
|
88
|
+
this._mutationObserver.observe(this);
|
|
93
89
|
}
|
|
94
90
|
|
|
95
91
|
// Debounced scheduling to avoid excessive updates on rapid DOM mutations
|
|
@@ -40,27 +40,27 @@
|
|
|
40
40
|
}
|
|
41
41
|
|
|
42
42
|
&--top-end {
|
|
43
|
-
top: var(--zn-
|
|
44
|
-
right: var(--zn-
|
|
43
|
+
top: var(--zn-base-gap);
|
|
44
|
+
right: calc(var(--zn-base-gap) + var(--zn-gutter));
|
|
45
45
|
bottom: auto;
|
|
46
46
|
}
|
|
47
47
|
|
|
48
48
|
&--top-start {
|
|
49
|
-
top: var(--zn-
|
|
49
|
+
top: var(--zn-base-gap);
|
|
50
50
|
right: auto;
|
|
51
|
-
left: var(--zn-
|
|
51
|
+
left: calc(var(--zn-base-gap) + var(--zn-gutter));
|
|
52
52
|
bottom: auto;
|
|
53
53
|
}
|
|
54
54
|
|
|
55
55
|
&--bottom-end {
|
|
56
|
-
bottom: calc(var(--zn-
|
|
57
|
-
right: var(--zn-
|
|
56
|
+
bottom: calc(var(--zn-base-gap) + var(--zn-scroll-footer-height, 0px));
|
|
57
|
+
right: calc(var(--zn-base-gap) + var(--zn-gutter));
|
|
58
58
|
top: auto;
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
&--bottom-start {
|
|
62
|
-
bottom: calc(var(--zn-
|
|
63
|
-
left: var(--zn-
|
|
62
|
+
bottom: calc(var(--zn-base-gap) + var(--zn-scroll-footer-height, 0px));
|
|
63
|
+
left: calc(var(--zn-base-gap) + var(--zn-gutter));
|
|
64
64
|
top: auto;
|
|
65
65
|
}
|
|
66
66
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import {MutationController} from '@lit-labs/observers/mutation-controller.js';
|
|
1
2
|
import {property} from 'lit/decorators.js';
|
|
2
3
|
import {type CSSResultGroup, html, unsafeCSS} from 'lit';
|
|
3
4
|
import ZincElement from '../../internal/zinc-element';
|
|
@@ -30,30 +31,29 @@ export default class ZnSidebar extends ZincElement {
|
|
|
30
31
|
@property({attribute: 'start-scrolled', type: Boolean, reflect: true}) startScrolled: boolean = false;
|
|
31
32
|
@property({type: Boolean}) wide: boolean = false;
|
|
32
33
|
|
|
34
|
+
private domObserver = new MutationController(this, {
|
|
35
|
+
target: null,
|
|
36
|
+
config: {childList: true, subtree: true},
|
|
37
|
+
callback: () => {
|
|
38
|
+
setTimeout(() => this.scrollBottom(), 10);
|
|
39
|
+
},
|
|
40
|
+
});
|
|
41
|
+
|
|
33
42
|
constructor() {
|
|
34
43
|
super();
|
|
35
44
|
this.addEventListener('scroll-to-bottom', () => {
|
|
36
|
-
setTimeout(this.scrollBottom
|
|
45
|
+
setTimeout(() => this.scrollBottom(), 200);
|
|
37
46
|
});
|
|
38
47
|
}
|
|
39
48
|
|
|
40
49
|
connectedCallback() {
|
|
41
50
|
super.connectedCallback();
|
|
42
51
|
if (this.startScrolled) {
|
|
43
|
-
this.
|
|
52
|
+
this.domObserver.observe(this);
|
|
44
53
|
this.scrollBottom();
|
|
45
54
|
}
|
|
46
55
|
}
|
|
47
56
|
|
|
48
|
-
observerDom() {
|
|
49
|
-
// observe the DOM for changes
|
|
50
|
-
const observer = new MutationObserver((_) => {
|
|
51
|
-
setTimeout(this.scrollBottom.bind(this), 10);
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
observer.observe(this, {childList: true, subtree: true});
|
|
55
|
-
}
|
|
56
|
-
|
|
57
57
|
scrollBottom() {
|
|
58
58
|
const container = this.shadowRoot?.getElementById('primary-content');
|
|
59
59
|
if (container) {
|
|
@@ -4,6 +4,7 @@ import { CanvasRenderer } from 'echarts/renderers';
|
|
|
4
4
|
import { type CSSResultGroup, html, unsafeCSS } from 'lit';
|
|
5
5
|
import { GridComponent, TooltipComponent } from 'echarts/components';
|
|
6
6
|
import { property } from 'lit/decorators.js';
|
|
7
|
+
import { ResizeController } from '@lit-labs/observers/resize-controller.js';
|
|
7
8
|
import styles from './simple-chart.scss';
|
|
8
9
|
import ZincElement from '../../internal/zinc-element';
|
|
9
10
|
import type { ECharts } from 'echarts/core';
|
|
@@ -40,7 +41,11 @@ export default class ZnSimpleChart extends ZincElement {
|
|
|
40
41
|
}) enableAnimations: boolean | number = false;
|
|
41
42
|
|
|
42
43
|
private chart?: ECharts;
|
|
43
|
-
private resizeObserver
|
|
44
|
+
private readonly resizeObserver = new ResizeController(this, {
|
|
45
|
+
target: null,
|
|
46
|
+
skipInitial: true,
|
|
47
|
+
callback: () => this.chart?.resize(),
|
|
48
|
+
});
|
|
44
49
|
|
|
45
50
|
firstUpdated() {
|
|
46
51
|
const host = this.shadowRoot?.getElementById('chart') as HTMLElement | null;
|
|
@@ -77,17 +82,11 @@ export default class ZnSimpleChart extends ZincElement {
|
|
|
77
82
|
},
|
|
78
83
|
}],
|
|
79
84
|
});
|
|
80
|
-
let firstResize = true;
|
|
81
|
-
this.resizeObserver = new ResizeObserver(() => {
|
|
82
|
-
if (firstResize) { firstResize = false; return; }
|
|
83
|
-
this.chart?.resize();
|
|
84
|
-
});
|
|
85
85
|
this.resizeObserver.observe(host);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
88
|
disconnectedCallback() {
|
|
89
89
|
super.disconnectedCallback();
|
|
90
|
-
this.resizeObserver?.disconnect();
|
|
91
90
|
this.chart?.dispose();
|
|
92
91
|
}
|
|
93
92
|
|