@blotch/dolan 22.2.3 → 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.
- package/dist/commands/add.js +9 -1
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +38 -3
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/doctor.js +258 -21
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +425 -11
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/init.d.ts +2 -2
- package/dist/commands/init.js +38 -18
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +105 -2
- package/dist/commands/init.test.js.map +1 -1
- package/dist/commands/theme.js +1 -1
- package/dist/commands/theme.js.map +1 -1
- package/dist/commands/update.d.ts +7 -0
- package/dist/commands/update.js +194 -50
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +570 -3
- package/dist/commands/update.test.js.map +1 -1
- package/dist/lockfile.d.ts +17 -0
- package/dist/lockfile.js +56 -1
- package/dist/lockfile.js.map +1 -1
- package/dist/lockfile.test.d.ts +1 -0
- package/dist/lockfile.test.js +65 -0
- package/dist/lockfile.test.js.map +1 -0
- package/dist/registry.d.ts +6 -0
- package/dist/registry.js +119 -11
- package/dist/registry.js.map +1 -1
- package/dist/registry.test.js +61 -6
- package/dist/registry.test.js.map +1 -1
- package/dist/test-utils.d.ts +27 -0
- package/dist/test-utils.js +45 -1
- package/dist/test-utils.js.map +1 -1
- package/dist/testing/fire-lynx-event.d.ts +14 -0
- package/dist/testing/fire-lynx-event.js +19 -0
- package/dist/testing/fire-lynx-event.js.map +1 -0
- package/dist/testing/fire-lynx-event.test.d.ts +1 -0
- package/dist/testing/fire-lynx-event.test.js +44 -0
- package/dist/testing/fire-lynx-event.test.js.map +1 -0
- package/dist/testing/lynx-main-thread.d.ts +22 -0
- package/dist/testing/lynx-main-thread.js +11 -0
- package/dist/testing/lynx-main-thread.js.map +1 -0
- package/dist/testing/lynx-main-thread.test.d.ts +1 -0
- package/dist/testing/lynx-main-thread.test.js +24 -0
- package/dist/testing/lynx-main-thread.test.js.map +1 -0
- package/dist/testing/set-input-signal.d.ts +5 -4
- package/dist/testing/set-input-signal.js +5 -4
- package/dist/testing/set-input-signal.js.map +1 -1
- package/dist/ui/boolean-inputs.test.ts +76 -0
- package/dist/ui/components/accordion/accordion.test.ts +158 -144
- package/dist/ui/components/accordion/accordion.ts +19 -14
- package/dist/ui/components/action-sheet/action-sheet.test.ts +637 -183
- package/dist/ui/components/action-sheet/action-sheet.ts +339 -55
- package/dist/ui/components/action-sheet/index.ts +1 -0
- package/dist/ui/components/alert/alert.test.ts +67 -19
- package/dist/ui/components/alert/alert.ts +14 -16
- package/dist/ui/components/alert-dialog/alert-dialog.test.ts +740 -129
- package/dist/ui/components/alert-dialog/alert-dialog.ts +672 -58
- package/dist/ui/components/alert-dialog/index.ts +5 -0
- package/dist/ui/components/area-chart/area-chart.test.ts +81 -1
- package/dist/ui/components/area-chart/area-chart.ts +3 -2
- package/dist/ui/components/avatar/avatar.test.ts +89 -45
- package/dist/ui/components/badge/badge.test.ts +57 -23
- package/dist/ui/components/badge/badge.ts +14 -15
- package/dist/ui/components/bar-chart/bar-chart.test.ts +82 -1
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +1314 -273
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +1046 -119
- package/dist/ui/components/bottom-sheet/index.ts +7 -1
- package/dist/ui/components/button/button.test.ts +646 -2
- package/dist/ui/components/button/button.ts +443 -64
- package/dist/ui/components/button/index.ts +2 -0
- package/dist/ui/components/button-group/button-group.test.ts +528 -2
- package/dist/ui/components/button-group/button-group.ts +428 -70
- package/dist/ui/components/button-group/index.ts +3 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +82 -1
- package/dist/ui/components/card/card.test.ts +97 -61
- package/dist/ui/components/card/card.ts +2 -1
- package/dist/ui/components/chart/chart-presets.test.ts +197 -1
- package/dist/ui/components/chart/chart-presets.ts +14 -7
- package/dist/ui/components/chart/chart-theme.ts +12 -0
- package/dist/ui/components/chart/chart.test.ts +1031 -0
- package/dist/ui/components/chart/chart.ts +66 -46
- package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
- package/dist/ui/components/checkbox/checkbox.ts +25 -24
- package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
- package/dist/ui/components/collapsible/collapsible.ts +21 -15
- package/dist/ui/components/context-menu/context-menu.test.ts +224 -0
- package/dist/ui/components/context-menu/context-menu.ts +110 -0
- package/dist/ui/components/context-menu/index.ts +1 -0
- package/dist/ui/components/dialog/dialog.test.ts +210 -152
- package/dist/ui/components/dialog/dialog.ts +35 -29
- package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
- package/dist/ui/components/dock/dock-scroll.ts +142 -0
- package/dist/ui/components/dock/dock-search.test.ts +594 -0
- package/dist/ui/components/dock/dock.test.ts +752 -0
- package/dist/ui/components/dock/dock.ts +1438 -0
- package/dist/ui/components/dock/index.ts +2 -0
- package/dist/ui/components/dynamic-color/dynamic-color.test.ts +144 -0
- package/dist/ui/components/dynamic-color/dynamic-color.ts +146 -0
- package/dist/ui/components/dynamic-color/index.ts +5 -0
- package/dist/ui/components/empty-state/empty-state.test.ts +28 -39
- package/dist/ui/components/glass/concentric-radius.test.ts +13 -0
- package/dist/ui/components/glass/concentric-radius.ts +17 -0
- package/dist/ui/components/glass/glass-design.test.ts +78 -0
- package/dist/ui/components/glass/glass-design.ts +119 -0
- package/dist/ui/components/glass/glass.test.ts +376 -0
- package/dist/ui/components/glass/glass.ts +430 -0
- package/dist/ui/components/glass/index.ts +19 -0
- package/dist/ui/components/icon/icon-theme.test.ts +64 -0
- package/dist/ui/components/icon/icon-theme.ts +70 -16
- package/dist/ui/components/icon/icon.test.ts +52 -41
- package/dist/ui/components/input/input.test.ts +178 -101
- package/dist/ui/components/input/input.ts +17 -13
- package/dist/ui/components/label/label.test.ts +11 -24
- package/dist/ui/components/label/label.ts +8 -2
- package/dist/ui/components/line-chart/line-chart.test.ts +76 -1
- package/dist/ui/components/line-chart/line-chart.ts +3 -2
- package/dist/ui/components/list/list.test.ts +80 -49
- package/dist/ui/components/list/list.ts +5 -4
- package/dist/ui/components/menu/index.ts +25 -0
- package/dist/ui/components/menu/menu-placement.test.ts +153 -0
- package/dist/ui/components/menu/menu-placement.ts +159 -0
- package/dist/ui/components/menu/menu-popup.test.ts +636 -0
- package/dist/ui/components/menu/menu-popup.ts +565 -0
- package/dist/ui/components/menu/menu.test.ts +523 -0
- package/dist/ui/components/menu/menu.ts +455 -0
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +425 -260
- package/dist/ui/components/nav-drawer/nav-drawer.ts +75 -58
- package/dist/ui/components/navigation-bar/index.ts +1 -0
- package/dist/ui/components/navigation-bar/navigation-bar.test.ts +425 -0
- package/dist/ui/components/navigation-bar/navigation-bar.ts +411 -0
- package/dist/ui/components/pagination/pagination.test.ts +136 -86
- package/dist/ui/components/popover/index.ts +9 -0
- package/dist/ui/components/popover/popover-panel.test.ts +735 -0
- package/dist/ui/components/popover/popover-panel.ts +603 -0
- package/dist/ui/components/popover/popover.test.ts +286 -0
- package/dist/ui/components/popover/popover.ts +207 -0
- package/dist/ui/components/progress/progress.test.ts +102 -46
- package/dist/ui/components/progress/progress.ts +54 -55
- package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
- package/dist/ui/components/radio-group/radio-group.ts +26 -25
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +74 -1
- package/dist/ui/components/scroll-area/scroll-area.test.ts +72 -36
- package/dist/ui/components/scroll-area/scroll-area.ts +4 -3
- package/dist/ui/components/search-field/index.ts +15 -0
- package/dist/ui/components/search-field/keyboard.test.ts +147 -0
- package/dist/ui/components/search-field/keyboard.ts +123 -0
- package/dist/ui/components/search-field/search-field.test.ts +549 -0
- package/dist/ui/components/search-field/search-field.ts +628 -0
- package/dist/ui/components/segmented-control/index.ts +8 -0
- package/dist/ui/components/segmented-control/segmented-control.test.ts +861 -0
- package/dist/ui/components/segmented-control/segmented-control.ts +884 -0
- package/dist/ui/components/select/select.test.ts +138 -78
- package/dist/ui/components/select/select.ts +19 -12
- package/dist/ui/components/separator/separator.test.ts +18 -17
- package/dist/ui/components/sheet/sheet.test.ts +151 -11
- package/dist/ui/components/sheet/sheet.ts +68 -5
- package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
- package/dist/ui/components/skeleton/skeleton.ts +10 -16
- package/dist/ui/components/slider/index.ts +1 -0
- package/dist/ui/components/slider/slider.test.ts +695 -0
- package/dist/ui/components/slider/slider.ts +901 -0
- package/dist/ui/components/spinner/spinner.test.ts +64 -46
- package/dist/ui/components/spinner/spinner.ts +16 -19
- package/dist/ui/components/switch/index.ts +1 -1
- package/dist/ui/components/switch/switch.test.ts +534 -48
- package/dist/ui/components/switch/switch.ts +613 -48
- package/dist/ui/components/tab-bar/index.ts +1 -0
- package/dist/ui/components/tab-bar/tab-bar.test.ts +280 -0
- package/dist/ui/components/tab-bar/tab-bar.ts +230 -0
- package/dist/ui/components/tabs/index.ts +7 -1
- package/dist/ui/components/tabs/tabs.test.ts +484 -150
- package/dist/ui/components/tabs/tabs.ts +344 -149
- package/dist/ui/components/textarea/textarea.test.ts +206 -100
- package/dist/ui/components/textarea/textarea.ts +16 -13
- package/dist/ui/components/toast/toast.test.ts +434 -350
- package/dist/ui/components/toast/toast.ts +42 -51
- package/dist/ui/components/toggle/index.ts +1 -0
- package/dist/ui/components/toggle/toggle.test.ts +559 -146
- package/dist/ui/components/toggle/toggle.ts +241 -26
- package/dist/ui/components/toolbar/bar-scroll.test.ts +310 -0
- package/dist/ui/components/toolbar/bar-scroll.ts +372 -0
- package/dist/ui/components/toolbar/index.ts +12 -0
- package/dist/ui/components/toolbar/toolbar.test.ts +438 -0
- package/dist/ui/components/toolbar/toolbar.ts +535 -0
- package/dist/ui/theme/chart-colors.test.ts +8 -3
- package/dist/ui/theme/dark.css +38 -2
- package/dist/ui/theme/default.css +55 -7
- package/dist/ui/theme/icon-colors.test.ts +8 -3
- package/dist/ui/theme/rgb-channels.test.ts +84 -0
- package/dist/ui/theme/tailwind-build.test.ts +114 -0
- package/dist/ui/theme/tailwind-css.test.ts +83 -0
- package/dist/ui/theme/tailwind-plugin.test.ts +42 -2
- package/dist/ui/theme/tailwind-plugin.ts +93 -29
- package/dist/ui/theme/tailwind.css +113 -0
- package/dist/ui/utils/animate.test.ts +13 -1
- package/dist/utils/analyze.d.ts +19 -0
- package/dist/utils/analyze.js +16 -0
- package/dist/utils/analyze.js.map +1 -1
- package/dist/utils/analyze.test.js +14 -1
- package/dist/utils/analyze.test.js.map +1 -1
- package/dist/utils/installed.d.ts +25 -1
- package/dist/utils/installed.js +33 -0
- package/dist/utils/installed.js.map +1 -1
- package/dist/utils/installed.test.js +55 -1
- package/dist/utils/installed.test.js.map +1 -1
- package/dist/utils/resolve-paths.d.ts +4 -4
- package/dist/utils/resolve-paths.js +5 -7
- package/dist/utils/resolve-paths.js.map +1 -1
- package/dist/utils/resolve-paths.test.js +9 -5
- package/dist/utils/resolve-paths.test.js.map +1 -1
- package/dist/utils/tailwind.d.ts +45 -0
- package/dist/utils/tailwind.js +90 -0
- package/dist/utils/tailwind.js.map +1 -0
- package/dist/utils/tailwind.test.d.ts +1 -0
- package/dist/utils/tailwind.test.js +134 -0
- package/dist/utils/tailwind.test.js.map +1 -0
- package/package.json +9 -1
- package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -63
- package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -58
- package/dist/ui/components/button/button-render.test.ts +0 -132
- package/dist/ui/components/button-group/button-group-render.test.ts +0 -146
- package/dist/ui/components/candlestick-chart/candlestick-chart-render.test.ts +0 -59
- package/dist/ui/components/chart/chart-render.test.ts +0 -730
- package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -83
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -64
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
Directive,
|
|
5
5
|
TemplateRef,
|
|
6
6
|
ViewEncapsulation,
|
|
7
|
+
booleanAttribute,
|
|
7
8
|
computed,
|
|
8
9
|
contentChild,
|
|
9
10
|
effect,
|
|
@@ -46,6 +47,7 @@ import type {
|
|
|
46
47
|
} from '@tanstack/charts/types';
|
|
47
48
|
|
|
48
49
|
import { cn } from '../../utils/cn';
|
|
50
|
+
import { UI_THEME_COLORS } from '../icon/icon-theme';
|
|
49
51
|
import { adjacentFocusPoint, createChartFocusModel } from './chart-focus';
|
|
50
52
|
import {
|
|
51
53
|
type ChartLabel,
|
|
@@ -131,6 +133,13 @@ export class UiChartTooltip {
|
|
|
131
133
|
}
|
|
132
134
|
}
|
|
133
135
|
|
|
136
|
+
/**
|
|
137
|
+
* `UiChartTooltip`, for `contentChild()`. A call rather than the class itself:
|
|
138
|
+
* for a class, Angular's test build keeps an arrow function that nothing calls,
|
|
139
|
+
* which an app's coverage report would show as uncovered.
|
|
140
|
+
*/
|
|
141
|
+
const tooltipDirectiveType = () => UiChartTooltip;
|
|
142
|
+
|
|
134
143
|
/** Default height, like the browser's 150px replaced-element default, but roomier. */
|
|
135
144
|
const DEFAULT_HEIGHT = 200;
|
|
136
145
|
/** A touch that moves less than this is a tap, not a drag. */
|
|
@@ -335,9 +344,9 @@ export class UiChart {
|
|
|
335
344
|
* light unless the app says otherwise; see `ChartColorSchemeOption`.
|
|
336
345
|
*/
|
|
337
346
|
readonly colorScheme = input<Exclude<ChartColorSchemeOption, Function>>();
|
|
338
|
-
readonly zoomable = input(false);
|
|
347
|
+
readonly zoomable = input(false, { transform: booleanAttribute });
|
|
339
348
|
readonly zoomAxes = input<ChartZoomAxes>('xy');
|
|
340
|
-
readonly showZoomControls = input(true);
|
|
349
|
+
readonly showZoomControls = input(true, { transform: booleanAttribute });
|
|
341
350
|
readonly minZoom = input(1);
|
|
342
351
|
readonly maxZoom = input(8);
|
|
343
352
|
/** Factor each zoom button press zooms by. */
|
|
@@ -352,10 +361,11 @@ export class UiChart {
|
|
|
352
361
|
/** The point a tap landed on, or `null` for a tap on empty space. */
|
|
353
362
|
readonly pointSelect = output<ChartPoint | null>();
|
|
354
363
|
|
|
355
|
-
protected readonly tooltipTemplate = contentChild(
|
|
364
|
+
protected readonly tooltipTemplate = contentChild(tooltipDirectiveType());
|
|
356
365
|
|
|
357
366
|
readonly #theme = inject(UI_CHART_THEME);
|
|
358
367
|
readonly #appearance = inject(LynxTheme);
|
|
368
|
+
readonly #replacedColors = inject(UI_THEME_COLORS, { optional: true });
|
|
359
369
|
readonly #idPrefix = `chart${nextChartId++}`;
|
|
360
370
|
readonly #runtime = createChartRuntime({ defaultTheme: this.#theme.chart });
|
|
361
371
|
readonly #measureText = lynxTextMeasurer();
|
|
@@ -405,6 +415,15 @@ export class UiChart {
|
|
|
405
415
|
});
|
|
406
416
|
});
|
|
407
417
|
|
|
418
|
+
/**
|
|
419
|
+
* The scene, for what the template draws inside `@if (scene())`: those
|
|
420
|
+
* bindings, and the values below derived for them, are only read while there
|
|
421
|
+
* is one, so they need no fallback for a chart without a size.
|
|
422
|
+
*/
|
|
423
|
+
#drawnScene(): ChartScene<any, any, any> {
|
|
424
|
+
return this.scene()!;
|
|
425
|
+
}
|
|
426
|
+
|
|
408
427
|
readonly #focusModel = computed(() => {
|
|
409
428
|
const scene = this.scene();
|
|
410
429
|
return scene ? createChartFocusModel(scene, this.definition()) : null;
|
|
@@ -424,24 +443,23 @@ export class UiChart {
|
|
|
424
443
|
});
|
|
425
444
|
|
|
426
445
|
/** The scene with mark states (e.g. dim the unfocused bars) resolved. */
|
|
427
|
-
readonly #presented = computed(
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
446
|
+
readonly #presented = computed(
|
|
447
|
+
() =>
|
|
448
|
+
resolveMarkStateScene(
|
|
449
|
+
this.#drawnScene(),
|
|
450
|
+
this.#focusState(),
|
|
451
|
+
this.#pointer(),
|
|
452
|
+
).scene,
|
|
453
|
+
);
|
|
433
454
|
|
|
434
|
-
readonly #focusPresentation = computed(() =>
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
)
|
|
443
|
-
: { under: [], over: [] };
|
|
444
|
-
});
|
|
455
|
+
readonly #focusPresentation = computed(() =>
|
|
456
|
+
resolveFocusPresentation(
|
|
457
|
+
this.#presented(),
|
|
458
|
+
this.#focusState(),
|
|
459
|
+
this.#pointer(),
|
|
460
|
+
null,
|
|
461
|
+
),
|
|
462
|
+
);
|
|
445
463
|
|
|
446
464
|
readonly #colors = computed(() => {
|
|
447
465
|
const option = this.colorScheme() ?? this.#theme.colorScheme ?? 'light';
|
|
@@ -453,16 +471,20 @@ export class UiChart {
|
|
|
453
471
|
: typeof option === 'function'
|
|
454
472
|
? option()
|
|
455
473
|
: option;
|
|
456
|
-
|
|
474
|
+
// Colors set at runtime above the chart, such as Android's wallpaper
|
|
475
|
+
// colors, take the place of the theme's (see UI_THEME_COLORS).
|
|
476
|
+
const replaced = this.#replacedColors?.()[scheme];
|
|
477
|
+
const colors = scheme === 'dark' ? this.#theme.dark : this.#theme.light;
|
|
478
|
+
return replaced ? { ...colors, ...replaced } : colors;
|
|
457
479
|
});
|
|
458
480
|
|
|
459
481
|
readonly #svgOptions = computed(() => {
|
|
460
|
-
const scene = this
|
|
482
|
+
const scene = this.#drawnScene();
|
|
461
483
|
const colors = this.#colors();
|
|
462
484
|
return {
|
|
463
|
-
width: scene
|
|
464
|
-
height: scene
|
|
465
|
-
gradients: scene
|
|
485
|
+
width: scene.width,
|
|
486
|
+
height: scene.height,
|
|
487
|
+
gradients: scene.gradients,
|
|
466
488
|
idPrefix: this.#idPrefix,
|
|
467
489
|
resolvePaint: (paint: string) => resolveChartPaint(paint, colors),
|
|
468
490
|
};
|
|
@@ -475,8 +497,7 @@ export class UiChart {
|
|
|
475
497
|
);
|
|
476
498
|
protected readonly marksSvg = computed(
|
|
477
499
|
() =>
|
|
478
|
-
renderChartSvg(this.#presented()
|
|
479
|
-
EMPTY_SVG,
|
|
500
|
+
renderChartSvg(this.#presented().nodes, this.#svgOptions()) || EMPTY_SVG,
|
|
480
501
|
);
|
|
481
502
|
protected readonly overSvg = computed(
|
|
482
503
|
() =>
|
|
@@ -485,18 +506,14 @@ export class UiChart {
|
|
|
485
506
|
);
|
|
486
507
|
|
|
487
508
|
protected readonly layerStyle = computed(() => {
|
|
488
|
-
const scene = this
|
|
489
|
-
return `position: absolute; left: 0px; top: 0px; width: ${scene
|
|
509
|
+
const scene = this.#drawnScene();
|
|
510
|
+
return `position: absolute; left: 0px; top: 0px; width: ${scene.width}px; height: ${scene.height}px;`;
|
|
490
511
|
});
|
|
491
512
|
|
|
492
513
|
readonly #labels = computed<readonly ChartLabel[]>(() => {
|
|
493
514
|
const presentation = this.#focusPresentation();
|
|
494
515
|
return collectChartLabels(
|
|
495
|
-
[
|
|
496
|
-
...presentation.under,
|
|
497
|
-
...(this.#presented()?.nodes ?? []),
|
|
498
|
-
...presentation.over,
|
|
499
|
-
],
|
|
516
|
+
[...presentation.under, ...this.#presented().nodes, ...presentation.over],
|
|
500
517
|
{
|
|
501
518
|
defaultColor: this.#theme.chart.foreground ?? 'var(--foreground)',
|
|
502
519
|
focus: this.#focusState(),
|
|
@@ -600,18 +617,21 @@ export class UiChart {
|
|
|
600
617
|
protected readonly zoomButtonClass =
|
|
601
618
|
'flex h-7 w-7 items-center justify-center rounded-md border border-border bg-card';
|
|
602
619
|
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
620
|
+
/**
|
|
621
|
+
* Keeps the focus on the same datum when a new scene (data, size or zoom
|
|
622
|
+
* changed) brings new point objects, as TanStack's hosts do. A field rather
|
|
623
|
+
* than a constructor: Angular's test build keeps a function for a
|
|
624
|
+
* constructor that nothing calls, which an app's coverage report would show
|
|
625
|
+
* as uncovered.
|
|
626
|
+
*/
|
|
627
|
+
protected readonly restoringFocus = effect(() => {
|
|
628
|
+
const model = this.#focusModel();
|
|
629
|
+
const previous = untracked(this.#focused);
|
|
630
|
+
if (!model || previous.length === 0) return;
|
|
631
|
+
const restored = model.restore(previous[0]!);
|
|
632
|
+
this.#focused.set(restored ? model.group(restored) : EMPTY_POINTS);
|
|
633
|
+
if (!restored) this.#pinned.set(false);
|
|
634
|
+
});
|
|
615
635
|
|
|
616
636
|
/**
|
|
617
637
|
* ---------------------------------------------------------------------------
|
|
@@ -1,59 +1,141 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import {
|
|
3
|
+
fireEvent,
|
|
4
|
+
render,
|
|
5
|
+
waitForUpdate,
|
|
6
|
+
} from '@blotch/angular-lynx-testing-library';
|
|
7
|
+
import {
|
|
8
|
+
type MockInstance,
|
|
9
|
+
afterEach,
|
|
10
|
+
beforeEach,
|
|
11
|
+
describe,
|
|
12
|
+
expect,
|
|
13
|
+
it,
|
|
14
|
+
vi,
|
|
15
|
+
} from 'vitest';
|
|
16
|
+
import {
|
|
17
|
+
type LynxMainThread,
|
|
18
|
+
lynxMainThread,
|
|
19
|
+
} from '../../../test-utils/lynx-main-thread';
|
|
4
20
|
import { UiCheckbox } from './checkbox';
|
|
5
21
|
|
|
22
|
+
@Component({
|
|
23
|
+
imports: [UiCheckbox],
|
|
24
|
+
template: `
|
|
25
|
+
<ui-checkbox
|
|
26
|
+
[(checked)]="checked"
|
|
27
|
+
[disabled]="disabled()"
|
|
28
|
+
class="mr-2"
|
|
29
|
+
(changed)="changes.push($event)"
|
|
30
|
+
/>
|
|
31
|
+
`,
|
|
32
|
+
})
|
|
33
|
+
class CheckboxHost {
|
|
34
|
+
readonly checked = signal(false);
|
|
35
|
+
readonly disabled = signal(false);
|
|
36
|
+
readonly changes: boolean[] = [];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const renderCheckbox = async () => {
|
|
40
|
+
const result = await render(CheckboxHost);
|
|
41
|
+
return {
|
|
42
|
+
...result,
|
|
43
|
+
host: result.componentRef.instance as CheckboxHost,
|
|
44
|
+
/** The 44pt tap target around the box. */
|
|
45
|
+
target: () => result.container.querySelector('.h-11')!,
|
|
46
|
+
box: () => result.container.querySelector('.rounded-sm')!,
|
|
47
|
+
checkmark: () => result.getByText('✓'),
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/** `__ElementAnimate`'s first argument for starting an animation. */
|
|
52
|
+
const START = 0;
|
|
53
|
+
/** `__ElementAnimate`'s first argument for canceling one. */
|
|
54
|
+
const CANCEL = 3;
|
|
55
|
+
|
|
6
56
|
describe('UiCheckbox', () => {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
57
|
+
// Watched per test: the testing library resets the main thread after each.
|
|
58
|
+
let animate: MockInstance<LynxMainThread['__ElementAnimate']>;
|
|
59
|
+
beforeEach(() => {
|
|
60
|
+
animate = vi.spyOn(lynxMainThread(), '__ElementAnimate');
|
|
10
61
|
});
|
|
62
|
+
afterEach(() => animate.mockRestore());
|
|
11
63
|
|
|
12
|
-
it('
|
|
13
|
-
const {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
inst.toggle();
|
|
19
|
-
expect(inst.checked()).toBe(true);
|
|
20
|
-
inst.toggle();
|
|
21
|
-
expect(inst.checked()).toBe(false);
|
|
22
|
-
expect(changes).toEqual([true, false]);
|
|
64
|
+
it('renders an empty box with the given class', async () => {
|
|
65
|
+
const { box, checkmark } = await renderCheckbox();
|
|
66
|
+
expect(box().classList).toContain('bg-transparent');
|
|
67
|
+
expect(box().classList).toContain('mr-2');
|
|
68
|
+
expect(checkmark().getAttribute('style')).toContain('opacity: 0');
|
|
23
69
|
});
|
|
24
70
|
|
|
25
|
-
it('
|
|
26
|
-
const {
|
|
27
|
-
const inst = componentRef.instance as UiCheckbox;
|
|
28
|
-
setInputSignal(inst.disabled, true);
|
|
29
|
-
const changed = vi.fn();
|
|
30
|
-
inst.changed.subscribe(changed);
|
|
71
|
+
it('checks and unchecks on tap, emitting `changed`', async () => {
|
|
72
|
+
const { host, target, box, checkmark } = await renderCheckbox();
|
|
31
73
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
expect(
|
|
74
|
+
fireEvent.tap(target());
|
|
75
|
+
await waitForUpdate();
|
|
76
|
+
expect(host.checked()).toBe(true);
|
|
77
|
+
expect(box().classList).toContain('bg-primary');
|
|
78
|
+
expect(checkmark().getAttribute('style')).toContain('opacity: 1');
|
|
79
|
+
|
|
80
|
+
fireEvent.tap(target());
|
|
81
|
+
await waitForUpdate();
|
|
82
|
+
expect(host.checked()).toBe(false);
|
|
83
|
+
expect(host.changes).toEqual([true, false]);
|
|
35
84
|
});
|
|
36
85
|
|
|
37
|
-
it('
|
|
38
|
-
const {
|
|
39
|
-
|
|
40
|
-
|
|
86
|
+
it("doesn't animate on mount, then pops the checkmark in and out", async () => {
|
|
87
|
+
const { target, checkmark } = await renderCheckbox();
|
|
88
|
+
expect(animate).not.toHaveBeenCalled();
|
|
89
|
+
|
|
90
|
+
fireEvent.tap(target());
|
|
41
91
|
await waitForUpdate();
|
|
92
|
+
expect(animate).toHaveBeenCalledWith(checkmark(), [
|
|
93
|
+
START,
|
|
94
|
+
expect.any(String),
|
|
95
|
+
[
|
|
96
|
+
{ transform: 'scale(0.6)', opacity: 0 },
|
|
97
|
+
{ transform: 'scale(1)', opacity: 1 },
|
|
98
|
+
],
|
|
99
|
+
expect.objectContaining({ fill: 'forwards' }),
|
|
100
|
+
]);
|
|
42
101
|
|
|
43
|
-
//
|
|
44
|
-
|
|
45
|
-
|
|
102
|
+
// Unchecking cuts the pop-in short and pops the checkmark out.
|
|
103
|
+
fireEvent.tap(target());
|
|
104
|
+
await waitForUpdate();
|
|
105
|
+
expect(animate).toHaveBeenCalledWith(checkmark(), [
|
|
106
|
+
CANCEL,
|
|
107
|
+
expect.any(String),
|
|
108
|
+
]);
|
|
109
|
+
expect(animate).toHaveBeenLastCalledWith(checkmark(), [
|
|
110
|
+
START,
|
|
111
|
+
expect.any(String),
|
|
112
|
+
[
|
|
113
|
+
{ transform: 'scale(1)', opacity: 1 },
|
|
114
|
+
{ transform: 'scale(0.8)', opacity: 0 },
|
|
115
|
+
],
|
|
116
|
+
expect.objectContaining({ fill: 'forwards' }),
|
|
117
|
+
]);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("doesn't animate two taps in one frame, which leave it as it was", async () => {
|
|
121
|
+
const { host, target } = await renderCheckbox();
|
|
122
|
+
fireEvent.tap(target());
|
|
123
|
+
fireEvent.tap(target());
|
|
124
|
+
await waitForUpdate();
|
|
125
|
+
expect(host.checked()).toBe(false);
|
|
126
|
+
expect(host.changes).toEqual([true, false]);
|
|
127
|
+
expect(animate).not.toHaveBeenCalled();
|
|
46
128
|
});
|
|
47
129
|
|
|
48
|
-
it('
|
|
49
|
-
const {
|
|
50
|
-
|
|
130
|
+
it('dims and ignores taps while disabled', async () => {
|
|
131
|
+
const { host, target, box } = await renderCheckbox();
|
|
132
|
+
host.disabled.set(true);
|
|
133
|
+
await waitForUpdate();
|
|
134
|
+
expect(box().classList).toContain('opacity-50');
|
|
135
|
+
|
|
136
|
+
fireEvent.tap(target());
|
|
51
137
|
await waitForUpdate();
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
const hasDim = Array.from(boxes).some((el) =>
|
|
55
|
-
el.getAttribute('class')?.includes('opacity-50'),
|
|
56
|
-
);
|
|
57
|
-
expect(hasDim).toBe(true);
|
|
138
|
+
expect(host.checked()).toBe(false);
|
|
139
|
+
expect(host.changes).toEqual([]);
|
|
58
140
|
});
|
|
59
141
|
});
|
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
type ElementRef,
|
|
3
3
|
Component,
|
|
4
4
|
ViewEncapsulation,
|
|
5
|
+
booleanAttribute,
|
|
5
6
|
computed,
|
|
6
7
|
effect,
|
|
7
8
|
input,
|
|
@@ -38,39 +39,39 @@ import { cn } from '../../utils/cn';
|
|
|
38
39
|
})
|
|
39
40
|
export class UiCheckbox {
|
|
40
41
|
readonly checked = model(false);
|
|
41
|
-
readonly disabled = input(false);
|
|
42
|
+
readonly disabled = input(false, { transform: booleanAttribute });
|
|
42
43
|
readonly userClass = input<string>('', { alias: 'class' });
|
|
43
44
|
|
|
44
45
|
readonly changed = output<boolean>();
|
|
45
46
|
|
|
46
|
-
readonly checkmarkRef = viewChild<ElementRef>('checkmark');
|
|
47
|
+
readonly checkmarkRef = viewChild.required<ElementRef>('checkmark');
|
|
47
48
|
#checkAnim?: AnimationHandle;
|
|
48
49
|
#previousChecked?: boolean;
|
|
49
50
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
}
|
|
66
|
-
if (isChecked === this.#previousChecked) return;
|
|
51
|
+
/**
|
|
52
|
+
* Pops the checkmark in when checked and out when unchecked. A field rather
|
|
53
|
+
* than a constructor: Angular's test build keeps a function for a
|
|
54
|
+
* constructor that nothing calls, which an app's coverage report would show
|
|
55
|
+
* as uncovered.
|
|
56
|
+
*
|
|
57
|
+
* `#previousChecked === undefined` guards the first run: we don't want to
|
|
58
|
+
* pop in the checkmark when the component mounts already checked. The
|
|
59
|
+
* equality guard skips a run that isn't a change: `checked` set and set
|
|
60
|
+
* back before the effect runs (two taps in one frame) still re-runs it.
|
|
61
|
+
*/
|
|
62
|
+
protected readonly checkmarkAnimation = effect(() => {
|
|
63
|
+
const isChecked = this.checked();
|
|
64
|
+
const el = this.checkmarkRef().nativeElement;
|
|
65
|
+
if (this.#previousChecked === undefined) {
|
|
67
66
|
this.#previousChecked = isChecked;
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
if (isChecked === this.#previousChecked) return;
|
|
70
|
+
this.#previousChecked = isChecked;
|
|
68
71
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
});
|
|
73
|
-
}
|
|
72
|
+
this.#checkAnim?.cancel();
|
|
73
|
+
this.#checkAnim = isChecked ? popIn(el) : popOut(el);
|
|
74
|
+
});
|
|
74
75
|
|
|
75
76
|
protected readonly boxClass = computed(() =>
|
|
76
77
|
cn(
|