@blotch/dolan 22.2.4 → 22.2.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.
Files changed (124) hide show
  1. package/dist/commands/add.test.js +2 -0
  2. package/dist/commands/add.test.js.map +1 -1
  3. package/dist/commands/doctor.js +78 -21
  4. package/dist/commands/doctor.js.map +1 -1
  5. package/dist/commands/doctor.test.js +167 -26
  6. package/dist/commands/doctor.test.js.map +1 -1
  7. package/dist/commands/init.test.js +4 -7
  8. package/dist/commands/init.test.js.map +1 -1
  9. package/dist/commands/update.d.ts +3 -1
  10. package/dist/commands/update.js +78 -15
  11. package/dist/commands/update.js.map +1 -1
  12. package/dist/commands/update.test.js +257 -2
  13. package/dist/commands/update.test.js.map +1 -1
  14. package/dist/lockfile.d.ts +10 -0
  15. package/dist/lockfile.js +20 -1
  16. package/dist/lockfile.js.map +1 -1
  17. package/dist/lockfile.test.js +43 -3
  18. package/dist/lockfile.test.js.map +1 -1
  19. package/dist/registry.js +73 -11
  20. package/dist/registry.js.map +1 -1
  21. package/dist/registry.test.js +16 -3
  22. package/dist/registry.test.js.map +1 -1
  23. package/dist/test-utils.d.ts +24 -2
  24. package/dist/test-utils.js +41 -2
  25. package/dist/test-utils.js.map +1 -1
  26. package/dist/ui/boolean-inputs.test.ts +76 -0
  27. package/dist/ui/components/action-sheet/action-sheet.test.ts +370 -1
  28. package/dist/ui/components/action-sheet/action-sheet.ts +306 -28
  29. package/dist/ui/components/action-sheet/index.ts +1 -0
  30. package/dist/ui/components/alert-dialog/alert-dialog.test.ts +552 -8
  31. package/dist/ui/components/alert-dialog/alert-dialog.ts +644 -36
  32. package/dist/ui/components/alert-dialog/index.ts +5 -0
  33. package/dist/ui/components/area-chart/area-chart.ts +3 -2
  34. package/dist/ui/components/badge/badge.ts +2 -1
  35. package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +968 -3
  36. package/dist/ui/components/bottom-sheet/bottom-sheet.ts +1021 -95
  37. package/dist/ui/components/bottom-sheet/index.ts +7 -1
  38. package/dist/ui/components/button/button.test.ts +36 -1
  39. package/dist/ui/components/button/button.ts +17 -7
  40. package/dist/ui/components/button-group/button-group.test.ts +53 -9
  41. package/dist/ui/components/button-group/button-group.ts +67 -21
  42. package/dist/ui/components/card/card.test.ts +16 -0
  43. package/dist/ui/components/card/card.ts +2 -1
  44. package/dist/ui/components/chart/chart-presets.ts +6 -6
  45. package/dist/ui/components/chart/chart.test.ts +14 -0
  46. package/dist/ui/components/chart/chart.ts +10 -3
  47. package/dist/ui/components/checkbox/checkbox.ts +2 -1
  48. package/dist/ui/components/collapsible/collapsible.ts +2 -1
  49. package/dist/ui/components/dialog/dialog.ts +6 -6
  50. package/dist/ui/components/dock/dock-scroll.ts +9 -0
  51. package/dist/ui/components/dock/dock-search.test.ts +594 -0
  52. package/dist/ui/components/dock/dock.test.ts +4 -1
  53. package/dist/ui/components/dock/dock.ts +529 -51
  54. package/dist/ui/components/dynamic-color/dynamic-color.test.ts +144 -0
  55. package/dist/ui/components/dynamic-color/dynamic-color.ts +146 -0
  56. package/dist/ui/components/dynamic-color/index.ts +5 -0
  57. package/dist/ui/components/glass/glass.test.ts +39 -1
  58. package/dist/ui/components/glass/glass.ts +87 -17
  59. package/dist/ui/components/glass/index.ts +1 -0
  60. package/dist/ui/components/icon/icon-theme.test.ts +28 -0
  61. package/dist/ui/components/icon/icon-theme.ts +38 -7
  62. package/dist/ui/components/input/input.ts +2 -1
  63. package/dist/ui/components/label/label.ts +8 -2
  64. package/dist/ui/components/line-chart/line-chart.ts +3 -2
  65. package/dist/ui/components/list/list.ts +5 -4
  66. package/dist/ui/components/menu/menu-popup.test.ts +18 -14
  67. package/dist/ui/components/menu/menu-popup.ts +24 -3
  68. package/dist/ui/components/menu/menu.test.ts +99 -5
  69. package/dist/ui/components/menu/menu.ts +74 -34
  70. package/dist/ui/components/nav-drawer/nav-drawer.ts +3 -2
  71. package/dist/ui/components/navigation-bar/index.ts +1 -0
  72. package/dist/ui/components/navigation-bar/navigation-bar.test.ts +425 -0
  73. package/dist/ui/components/navigation-bar/navigation-bar.ts +411 -0
  74. package/dist/ui/components/popover/index.ts +9 -0
  75. package/dist/ui/components/popover/popover-panel.test.ts +735 -0
  76. package/dist/ui/components/popover/popover-panel.ts +603 -0
  77. package/dist/ui/components/popover/popover.test.ts +286 -0
  78. package/dist/ui/components/popover/popover.ts +207 -0
  79. package/dist/ui/components/progress/progress.ts +2 -1
  80. package/dist/ui/components/radio-group/radio-group.ts +2 -1
  81. package/dist/ui/components/scroll-area/scroll-area.ts +4 -3
  82. package/dist/ui/components/search-field/index.ts +15 -0
  83. package/dist/ui/components/search-field/keyboard.test.ts +147 -0
  84. package/dist/ui/components/search-field/keyboard.ts +123 -0
  85. package/dist/ui/components/search-field/search-field.test.ts +549 -0
  86. package/dist/ui/components/search-field/search-field.ts +628 -0
  87. package/dist/ui/components/segmented-control/index.ts +8 -0
  88. package/dist/ui/components/segmented-control/segmented-control.test.ts +861 -0
  89. package/dist/ui/components/segmented-control/segmented-control.ts +884 -0
  90. package/dist/ui/components/select/select.ts +2 -1
  91. package/dist/ui/components/sheet/sheet.test.ts +90 -3
  92. package/dist/ui/components/sheet/sheet.ts +59 -4
  93. package/dist/ui/components/slider/index.ts +1 -0
  94. package/dist/ui/components/slider/slider.test.ts +695 -0
  95. package/dist/ui/components/slider/slider.ts +901 -0
  96. package/dist/ui/components/switch/index.ts +1 -1
  97. package/dist/ui/components/switch/switch.test.ts +464 -48
  98. package/dist/ui/components/switch/switch.ts +613 -43
  99. package/dist/ui/components/tabs/index.ts +7 -1
  100. package/dist/ui/components/tabs/tabs.test.ts +278 -2
  101. package/dist/ui/components/tabs/tabs.ts +233 -30
  102. package/dist/ui/components/textarea/textarea.ts +2 -1
  103. package/dist/ui/components/toggle/index.ts +1 -0
  104. package/dist/ui/components/toggle/toggle.test.ts +310 -4
  105. package/dist/ui/components/toggle/toggle.ts +241 -26
  106. package/dist/ui/components/toolbar/bar-scroll.test.ts +310 -0
  107. package/dist/ui/components/toolbar/bar-scroll.ts +372 -0
  108. package/dist/ui/components/toolbar/index.ts +12 -0
  109. package/dist/ui/components/toolbar/toolbar.test.ts +438 -0
  110. package/dist/ui/components/toolbar/toolbar.ts +535 -0
  111. package/dist/utils/analyze.d.ts +10 -5
  112. package/dist/utils/analyze.js +3 -2
  113. package/dist/utils/analyze.js.map +1 -1
  114. package/dist/utils/installed.d.ts +13 -1
  115. package/dist/utils/installed.js +17 -0
  116. package/dist/utils/installed.js.map +1 -1
  117. package/dist/utils/installed.test.js +39 -1
  118. package/dist/utils/installed.test.js.map +1 -1
  119. package/dist/utils/tailwind.d.ts +25 -5
  120. package/dist/utils/tailwind.js +53 -14
  121. package/dist/utils/tailwind.js.map +1 -1
  122. package/dist/utils/tailwind.test.js +85 -22
  123. package/dist/utils/tailwind.test.js.map +1 -1
  124. package/package.json +1 -1
@@ -1,13 +1,16 @@
1
- import { Component, signal } from '@angular/core';
1
+ import { Component, signal, viewChild } from '@angular/core';
2
2
  import {
3
3
  fireEvent,
4
4
  render,
5
5
  waitForUpdate,
6
6
  } from '@blotch/angular-lynx-testing-library';
7
- import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
8
- import { describe, expect, it } from 'vitest';
7
+ import { LYNX_ELEMENTS, LynxAccessibilitySettings } from '@blotch/angular-lynx';
8
+ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
9
9
  import { fireLynxEvent } from '../../../test-utils/fire-lynx-event';
10
+ import { UiIcon } from '../icon/icon';
11
+ import { ICON_LIGHT_COLORS, UI_ICON_COLOR } from '../icon/icon-theme';
10
12
  import {
13
+ type ToggleGroupVariant,
11
14
  type ToggleSize,
12
15
  type ToggleVariant,
13
16
  UiToggle,
@@ -61,9 +64,10 @@ class ToggleHost {
61
64
  class ToggleGroupHost {
62
65
  readonly type = signal<'single' | 'multiple'>('single');
63
66
  readonly align = signal<string[]>([]);
64
- readonly variant = signal<ToggleVariant>('default');
67
+ readonly variant = signal<ToggleGroupVariant>('default');
65
68
  readonly size = signal<ToggleSize>('default');
66
69
  readonly disabled = signal(false);
70
+ readonly group = viewChild.required(UiToggleGroup);
67
71
  }
68
72
 
69
73
  /**
@@ -264,6 +268,14 @@ describe('UiToggleGroup', () => {
264
268
  expect(host.align()).toEqual([]);
265
269
  });
266
270
 
271
+ it("ignores toggle() from code while it's disabled", async () => {
272
+ const { host } = await renderGroup();
273
+ host.disabled.set(true);
274
+ await waitForUpdate();
275
+ host.group().toggle('left');
276
+ expect(host.align()).toEqual([]);
277
+ });
278
+
267
279
  it("styles every item with the group's variant and size, and its own class", async () => {
268
280
  const { host, item } = await renderGroup();
269
281
  expect(item('Left').classList).toContain('h-10');
@@ -291,3 +303,297 @@ describe('UiToggleGroup', () => {
291
303
  expectNoPressFeedback(item('Center'));
292
304
  });
293
305
  });
306
+
307
+ @Component({
308
+ imports: [UiToggleGroup, UiToggleGroupItem, UiIcon, LYNX_ELEMENTS],
309
+ template: `
310
+ <ui-toggle-group
311
+ [variant]="variant()"
312
+ [type]="type()"
313
+ [value]="range()"
314
+ (valueChange)="range.set($event)"
315
+ [disabled]="disabled()"
316
+ >
317
+ @for (option of options(); track option) {
318
+ <ui-toggle-group-item
319
+ [value]="option"
320
+ [accessibility-label]="option"
321
+ [class]="option === 'Month' ? 'flex-[2]' : ''"
322
+ ><text class="text-foreground">{{
323
+ option
324
+ }}</text></ui-toggle-group-item
325
+ >
326
+ }
327
+ <ui-toggle-group-item value="Star"
328
+ ><ui-icon name="star"
329
+ /></ui-toggle-group-item>
330
+ </ui-toggle-group>
331
+ `,
332
+ })
333
+ class SegmentedGroupHost {
334
+ readonly variant = signal<ToggleGroupVariant>('segmented');
335
+ readonly type = signal<'single' | 'multiple'>('single');
336
+ readonly range = signal<string[]>(['Week']);
337
+ readonly options = signal(['Day', 'Week', 'Month']);
338
+ readonly disabled = signal(false);
339
+ }
340
+
341
+ /**
342
+ * A group inside something that colors its icons, such as glass.
343
+ */
344
+ @Component({
345
+ imports: [UiToggleGroup, UiToggleGroupItem, UiIcon, LYNX_ELEMENTS],
346
+ providers: [
347
+ { provide: UI_ICON_COLOR, useValue: signal('var(--primary)').asReadonly() },
348
+ ],
349
+ template: `
350
+ <ui-toggle-group [variant]="variant()">
351
+ <ui-toggle-group-item value="star"
352
+ ><ui-icon name="star"
353
+ /></ui-toggle-group-item>
354
+ </ui-toggle-group>
355
+ `,
356
+ })
357
+ class TintedGroupHost {
358
+ readonly variant = signal<ToggleGroupVariant>('default');
359
+ }
360
+
361
+ /** Accessibility settings a test can change, as the host app would. */
362
+ const settings = {
363
+ reduceMotion: signal(false),
364
+ reduceTransparency: signal(false),
365
+ increaseContrast: signal(false),
366
+ };
367
+
368
+ const IOS_26 = { platform: 'iOS', osVersion: '26.0' };
369
+ const ANDROID = { platform: 'Android', osVersion: '15' };
370
+
371
+ describe('UiToggleGroup, segmented', () => {
372
+ // The indicator's and the icon's selectors aren't Lynx elements, so the
373
+ // renderer falls back to a <view> for them and warns.
374
+ beforeEach(() => {
375
+ vi.spyOn(console, 'warn').mockImplementation(() => {});
376
+ });
377
+
378
+ afterEach(() => {
379
+ vi.restoreAllMocks();
380
+ vi.unstubAllGlobals();
381
+ settings.reduceMotion.set(false);
382
+ });
383
+
384
+ const renderSegmented = async (
385
+ platform: { platform: string; osVersion: string } = IOS_26,
386
+ ) => {
387
+ vi.stubGlobal('SystemInfo', platform);
388
+ const result = await render(SegmentedGroupHost, {
389
+ providers: [{ provide: LynxAccessibilitySettings, useValue: settings }],
390
+ });
391
+ /**
392
+ * An item's own view: around its label, or around the star icon.
393
+ */
394
+ const item = (label: string) =>
395
+ label === 'Star'
396
+ ? result.container.querySelector('svg')!.parentElement!.parentElement!
397
+ : viewOf(result.getByText(label));
398
+ /**
399
+ * An item's host, the track's flex item.
400
+ */
401
+ const slot = (label: string) => item(label).parentElement!;
402
+ const track = () => slot('Day').parentElement!;
403
+ /**
404
+ * The indicator's frame, which moves between the items.
405
+ */
406
+ const frame = () =>
407
+ track().querySelector<HTMLElement>('.h-full .bottom-0.top-0');
408
+ const measure = async () => {
409
+ for (const label of ['Day', 'Week', 'Month', 'Star']) {
410
+ fireEvent.layoutchange(slot(label), {
411
+ detail: { width: label === 'Month' ? 160 : 80 },
412
+ });
413
+ }
414
+ await waitForUpdate();
415
+ };
416
+ return {
417
+ ...result,
418
+ host: result.componentRef.instance as SegmentedGroupHost,
419
+ item,
420
+ slot,
421
+ track,
422
+ frame,
423
+ measure,
424
+ tap: async (label: string) => {
425
+ fireEvent.tap(item(label));
426
+ await waitForUpdate();
427
+ },
428
+ };
429
+ };
430
+
431
+ it("draws the platform's segmented control, its items sharing the track", async () => {
432
+ const { track, slot, item, frame } = await renderSegmented();
433
+ expect(track().classList).toContain('rounded-full');
434
+ expect(track().classList).toContain('bg-muted');
435
+ expect(slot('Day').classList).toContain('flex-1');
436
+ // An item's own class sizes its slot too.
437
+ expect(slot('Month').classList).toContain('flex-[2]');
438
+ expect(slot('Month').classList).not.toContain('flex-1');
439
+ expect(item('Day').classList).toContain('justify-center');
440
+ expect(frame()).not.toBeNull();
441
+ });
442
+
443
+ it('keeps one item selected: tapping it again leaves it selected', async () => {
444
+ const { host, tap } = await renderSegmented();
445
+ await tap('Day');
446
+ expect(host.range()).toEqual(['Day']);
447
+ await tap('Day');
448
+ expect(host.range()).toEqual(['Day']);
449
+ });
450
+
451
+ it('marks the selected item selected for screen readers', async () => {
452
+ const { getByRole, tap } = await renderSegmented();
453
+ expect(getByRole('button', { name: 'Week', selected: true })).toBeTruthy();
454
+ expect(getByRole('button', { name: 'Day', selected: false })).toBeTruthy();
455
+ await tap('Day');
456
+ expect(getByRole('button', { name: 'Day', selected: true })).toBeTruthy();
457
+ });
458
+
459
+ it('moves the indicator to the item it was dragged to', async () => {
460
+ const { host, item, frame, measure } = await renderSegmented();
461
+ await measure();
462
+ expect(frame()!.style.left).toBe('80px');
463
+
464
+ fireEvent.touchstart(item('Week'), { touches: [{ pageX: 120 }] });
465
+ fireEvent.touchmove(item('Week'), { touches: [{ pageX: 220 }] });
466
+ fireEvent.touchend(item('Week'));
467
+ // Lynx may send a tap as the finger lifts: the drag chose already.
468
+ fireEvent.tap(item('Week'));
469
+ await waitForUpdate();
470
+ expect(host.range()).toEqual(['Month']);
471
+ expect(frame()!.style.left).toBe('160px');
472
+ expect(frame()!.style.width).toBe('160px');
473
+ });
474
+
475
+ it('lets go of a drag the touch is taken from, or the mouse leaves', async () => {
476
+ const { host, item, track, frame, measure } = await renderSegmented();
477
+ await measure();
478
+
479
+ fireEvent.touchstart(item('Week'), { touches: [{ pageX: 120 }] });
480
+ fireEvent.touchmove(item('Week'), { touches: [{ pageX: 220 }] });
481
+ fireEvent.touchcancel(item('Week'));
482
+ await waitForUpdate();
483
+ expect(frame()!.style.left).toBe('80px');
484
+
485
+ fireEvent.mousedown(item('Week'), { pageX: 120 });
486
+ fireEvent.mousemove(item('Week'), { pageX: 220 });
487
+ fireLynxEvent(item('Week'), 'mouseleave');
488
+ await waitForUpdate();
489
+ expect(frame()!.style.left).toBe('180px');
490
+ fireLynxEvent(track(), 'mouseleave');
491
+ await waitForUpdate();
492
+ expect(frame()!.style.left).toBe('80px');
493
+
494
+ fireEvent.mousedown(item('Week'), { pageX: 120 });
495
+ fireEvent.mousemove(item('Day'), { pageX: 20 });
496
+ fireEvent.mouseup(item('Day'));
497
+ await waitForUpdate();
498
+ expect(host.range()).toEqual(['Day']);
499
+ });
500
+
501
+ it("ignores touches while it's disabled", async () => {
502
+ const { host, item, frame, measure } = await renderSegmented();
503
+ await measure();
504
+ host.disabled.set(true);
505
+ await waitForUpdate();
506
+ fireEvent.touchstart(item('Week'), { touches: [{ pageX: 120 }] });
507
+ fireEvent.touchmove(item('Week'), { touches: [{ pageX: 220 }] });
508
+ fireEvent.touchend(item('Week'));
509
+ fireEvent.tap(item('Day'));
510
+ await waitForUpdate();
511
+ expect(host.range()).toEqual(['Week']);
512
+ expect(frame()!.style.left).toBe('80px');
513
+ });
514
+
515
+ it('gives each pressed item its own thumb in a multiple selection', async () => {
516
+ const { host, item, frame, tap } = await renderSegmented();
517
+ host.type.set('multiple');
518
+ await waitForUpdate();
519
+ expect(frame()).toBeNull();
520
+ expect(item('Week').classList).toContain('bg-background');
521
+
522
+ await tap('Day');
523
+ expect(host.range()).toEqual(['Week', 'Day']);
524
+ expect(item('Day').classList).toContain('bg-background');
525
+ await tap('Day');
526
+ expect(host.range()).toEqual(['Week']);
527
+ });
528
+
529
+ it("is Material's connected button group on Android, its icons in the fill's content color", async () => {
530
+ const { host, item, getByText, container, tap } =
531
+ await renderSegmented(ANDROID);
532
+ expect(item('Week').classList).toContain('bg-primary');
533
+ expect(item('Week').style.transition).toContain('background-color');
534
+ expect(item('Day').classList).toContain('bg-secondary');
535
+ const star = () =>
536
+ container.querySelector('svg')!.getAttribute('content') ?? '';
537
+ expect(star()).toContain(ICON_LIGHT_COLORS['secondary-foreground']);
538
+
539
+ await tap('Star');
540
+ expect(host.range()).toEqual(['Star']);
541
+ expect(star()).toContain(ICON_LIGHT_COLORS['primary-foreground']);
542
+
543
+ // A press shows Material's state layer.
544
+ fireEvent.touchstart(item('Day'), { touches: [{ pageX: 20 }] });
545
+ await waitForUpdate();
546
+ expect(item('Day').querySelector('.opacity-10')).not.toBeNull();
547
+ fireEvent.touchend(item('Day'));
548
+ await waitForUpdate();
549
+ expect(item('Day').querySelector('.opacity-10')).toBeNull();
550
+ expect(getByText('Day')).toBeTruthy();
551
+ });
552
+
553
+ it("rounds the group's ends on Android, as items come and go", async () => {
554
+ const { host, item } = await renderSegmented(ANDROID);
555
+ expect(item('Day').classList).toContain('rounded-l-[20px]');
556
+ expect(item('Month').classList).toContain('rounded-[8px]');
557
+ expect(item('Star').classList).toContain('rounded-r-[20px]');
558
+
559
+ // The items before a new one are redrawn before it has a value, so each
560
+ // finds its place from the items rather than their values.
561
+ host.options.set(['Day', 'Week', 'Month', 'Year']);
562
+ await waitForUpdate();
563
+ expect(item('Year').classList).toContain('rounded-[8px]');
564
+ expect(item('Star').classList).toContain('rounded-r-[20px]');
565
+
566
+ host.options.set(['Month']);
567
+ await waitForUpdate();
568
+ expect(item('Month').classList).toContain('rounded-l-[20px]');
569
+ });
570
+
571
+ it('leaves an icon the color around the group in the other variants', async () => {
572
+ vi.stubGlobal('SystemInfo', IOS_26);
573
+ const tinted = await render(TintedGroupHost);
574
+ const star = () =>
575
+ tinted.container.querySelector('svg')!.getAttribute('content') ?? '';
576
+ expect(star()).toContain(ICON_LIGHT_COLORS['primary']);
577
+
578
+ // A segmented control on iOS leaves it alone too.
579
+ (tinted.componentRef.instance as TintedGroupHost).variant.set('segmented');
580
+ await waitForUpdate();
581
+ expect(star()).toContain(ICON_LIGHT_COLORS['primary']);
582
+ tinted.unmount();
583
+
584
+ // With nothing around it, an icon takes its own default.
585
+ const plain = await renderSegmented();
586
+ expect(
587
+ plain.container.querySelector('svg')!.getAttribute('content'),
588
+ ).toContain(ICON_LIGHT_COLORS['foreground']);
589
+ });
590
+
591
+ it('goes back to the toggle look when the variant changes', async () => {
592
+ const { host, item, frame } = await renderSegmented();
593
+ host.variant.set('outline');
594
+ await waitForUpdate();
595
+ expect(frame()).toBeNull();
596
+ expect(item('Week').classList).toContain('border-border');
597
+ expect(item('Week').classList).toContain('bg-accent');
598
+ });
599
+ });