@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.
Files changed (95) hide show
  1. package/.github/workflows/eleventy_build.yml +5 -5
  2. package/.github/workflows/js_build_and_deploy.yaml +1 -1
  3. package/dist/custom-elements.json +2636 -421
  4. package/dist/vscode.html-custom-data.json +96 -16
  5. package/dist/web-types.json +268 -31
  6. package/dist/zn.d.ts +517 -340
  7. package/dist/zn.min.css +1 -1
  8. package/dist/zn.min.js +942 -863
  9. package/docs/eleventy.config.cjs +4 -2
  10. package/docs/pages/components/header.md +13 -0
  11. package/docs/pages/components/inline-edit.md +20 -0
  12. package/docs/pages/components/item.md +12 -1
  13. package/docs/pages/components/page.md +149 -0
  14. package/docs/pages/components/progress-bar.md +16 -0
  15. package/docs/pages/components/select.md +25 -0
  16. package/docs/pages/components/split-pane.md +6 -7
  17. package/docs/pages/components/tabs.md +59 -9
  18. package/package.json +8 -5
  19. package/scripts/build.js +40 -19
  20. package/scss/_global-spacing.scss +12 -1
  21. package/scss/_root.scss +34 -2
  22. package/scss/shared/layout.scss +15 -2
  23. package/scss/themes/_light.scss +1 -1
  24. package/src/components/absolute-container/absolute-container.component.ts +9 -25
  25. package/src/components/animated-button/animated-button.test.ts +8 -8
  26. package/src/components/button/button.component.ts +3 -1
  27. package/src/components/button/button.scss +5 -0
  28. package/src/components/button-menu/button-menu.component.ts +36 -21
  29. package/src/components/button-menu/button-menu.test.ts +55 -1
  30. package/src/components/chart/chart.component.ts +7 -7
  31. package/src/components/collapsible/collapsible.component.ts +7 -14
  32. package/src/components/cols/cols.scss +1 -1
  33. package/src/components/content-block/content-block.component.ts +11 -33
  34. package/src/components/data-select/data-select.component.ts +7 -11
  35. package/src/components/data-table/data-table.component.ts +10 -14
  36. package/src/components/editor/modules/toolbar/toolbar.component.ts +5 -9
  37. package/src/components/expanding-action/expanding-action.component.ts +37 -43
  38. package/src/components/form-group/form-group.component.ts +8 -3
  39. package/src/components/header/header.component.ts +6 -5
  40. package/src/components/header/header.scss +31 -25
  41. package/src/components/inline-edit/inline-edit.component.ts +13 -1
  42. package/src/components/inline-edit/inline-edit.test.ts +50 -0
  43. package/src/components/input/input.test.ts +77 -0
  44. package/src/components/input-group/input-group.test.ts +2 -10
  45. package/src/components/item/item.component.ts +12 -6
  46. package/src/components/item/item.scss +1 -1
  47. package/src/components/item/item.test.ts +8 -0
  48. package/src/components/navbar/navbar.component.ts +222 -48
  49. package/src/components/navbar/navbar.scss +134 -21
  50. package/src/components/navbar/navbar.test.ts +114 -9
  51. package/src/components/option/option.scss +5 -7
  52. package/src/components/option/option.test.ts +30 -0
  53. package/src/components/page/index.ts +13 -0
  54. package/src/components/page/page.component.ts +406 -0
  55. package/src/components/page/page.scss +325 -0
  56. package/src/components/page/page.test.ts +397 -0
  57. package/src/components/page-nav/page-nav.scss +7 -5
  58. package/src/components/pane/pane.component.ts +2 -2
  59. package/src/components/pane/pane.scss +0 -1
  60. package/src/components/pane/pane.test.ts +31 -0
  61. package/src/components/panel/panel.scss +1 -0
  62. package/src/components/progress-bar/progress-bar.component.ts +5 -2
  63. package/src/components/progress-bar/progress-bar.scss +17 -1
  64. package/src/components/progress-bar/progress-bar.test.ts +12 -0
  65. package/src/components/scroll-container/scroll-container.component.ts +22 -21
  66. package/src/components/select/select.component.ts +240 -38
  67. package/src/components/select/select.scss +15 -0
  68. package/src/components/select/select.test.ts +477 -0
  69. package/src/components/settings-container/settings-container.component.ts +15 -19
  70. package/src/components/settings-container/settings-container.scss +8 -8
  71. package/src/components/sidebar/sidebar.component.ts +11 -11
  72. package/src/components/simple-chart/simple-chart.component.ts +6 -7
  73. package/src/components/sp/sp.scss +46 -15
  74. package/src/components/sp/sp.test.ts +15 -0
  75. package/src/components/split-pane/split-pane.component.ts +153 -26
  76. package/src/components/split-pane/split-pane.scss +89 -13
  77. package/src/components/split-pane/split-pane.test.ts +187 -0
  78. package/src/components/style/style.component.ts +8 -0
  79. package/src/components/tab/index.ts +13 -0
  80. package/src/components/tab/tab.component.ts +25 -0
  81. package/src/components/tab/tab.scss +7 -0
  82. package/src/components/table/table.component.ts +7 -2
  83. package/src/components/tabs/tabs.component.ts +75 -70
  84. package/src/components/tabs/tabs.scss +1 -1
  85. package/src/components/textarea/textarea.component.ts +5 -8
  86. package/src/components/translation-group/translation-group.component.ts +15 -14
  87. package/src/components/translations/translations.component.ts +18 -17
  88. package/src/components/translations/translations.scss +1 -0
  89. package/src/components/translations/translations.test.ts +3 -2
  90. package/src/internal/form.ts +234 -0
  91. package/src/internal/theme.ts +26 -46
  92. package/src/internal/zinc-element.ts +12 -7
  93. package/src/utilities/form.ts +1 -0
  94. package/src/wc.scss +1 -1
  95. 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: MutationObserver | null = null;
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 = new MutationObserver(mutations => {
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-spacing-medium);
44
- right: var(--zn-spacing-medium);
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-spacing-medium);
49
+ top: var(--zn-base-gap);
50
50
  right: auto;
51
- left: var(--zn-spacing-medium);
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-spacing-medium) + var(--zn-scroll-footer-height, 0px));
57
- right: var(--zn-spacing-medium);
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-spacing-medium) + var(--zn-scroll-footer-height, 0px));
63
- left: var(--zn-spacing-medium);
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.bind(this), 200);
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.observerDom();
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?: 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