@blotch/dolan 22.2.2 → 22.2.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/add.d.ts +6 -2
- package/dist/commands/add.js +41 -11
- package/dist/commands/add.js.map +1 -1
- package/dist/commands/add.test.js +162 -9
- package/dist/commands/add.test.js.map +1 -1
- package/dist/commands/diff.d.ts +2 -1
- package/dist/commands/diff.js +38 -25
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/diff.test.js +72 -11
- package/dist/commands/diff.test.js.map +1 -1
- package/dist/commands/doctor.js +228 -34
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +367 -10
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/eject.js +7 -0
- package/dist/commands/eject.js.map +1 -1
- package/dist/commands/eject.test.js +39 -0
- package/dist/commands/eject.test.js.map +1 -1
- package/dist/commands/info.js +3 -2
- package/dist/commands/info.js.map +1 -1
- package/dist/commands/init.d.ts +37 -1
- package/dist/commands/init.js +169 -40
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/init.test.js +392 -27
- package/dist/commands/init.test.js.map +1 -1
- package/dist/commands/install-packages.d.ts +6 -0
- package/dist/commands/install-packages.js +20 -0
- package/dist/commands/install-packages.js.map +1 -0
- package/dist/commands/install-packages.test.d.ts +1 -0
- package/dist/commands/install-packages.test.js +55 -0
- package/dist/commands/install-packages.test.js.map +1 -0
- package/dist/commands/list.js +21 -14
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/list.test.js +62 -5
- package/dist/commands/list.test.js.map +1 -1
- package/dist/commands/outdated.js +11 -10
- package/dist/commands/outdated.js.map +1 -1
- package/dist/commands/outdated.test.js +20 -5
- package/dist/commands/outdated.test.js.map +1 -1
- package/dist/commands/remove.d.ts +4 -0
- package/dist/commands/remove.js +17 -4
- package/dist/commands/remove.js.map +1 -1
- package/dist/commands/remove.test.js +53 -2
- package/dist/commands/remove.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 +11 -0
- package/dist/commands/update.js +174 -58
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/update.test.js +587 -33
- package/dist/commands/update.test.js.map +1 -1
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/lockfile.d.ts +7 -0
- package/dist/lockfile.js +36 -0
- package/dist/lockfile.js.map +1 -1
- package/dist/lockfile.test.d.ts +1 -0
- package/dist/lockfile.test.js +25 -0
- package/dist/lockfile.test.js.map +1 -0
- package/dist/registry.d.ts +14 -0
- package/dist/registry.js +78 -24
- package/dist/registry.js.map +1 -1
- package/dist/registry.test.js +119 -38
- package/dist/registry.test.js.map +1 -1
- package/dist/test-utils.d.ts +5 -0
- package/dist/test-utils.js +6 -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 +14 -0
- package/dist/testing/set-input-signal.js +21 -0
- package/dist/testing/set-input-signal.js.map +1 -0
- package/dist/testing/set-input-signal.test.d.ts +1 -0
- package/dist/testing/set-input-signal.test.js +20 -0
- package/dist/testing/set-input-signal.test.js.map +1 -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 +283 -151
- package/dist/ui/components/action-sheet/action-sheet.ts +77 -43
- 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 +208 -94
- package/dist/ui/components/alert-dialog/alert-dialog.ts +75 -38
- package/dist/ui/components/area-chart/area-chart.test.ts +134 -119
- package/dist/ui/components/area-chart/area-chart.ts +100 -315
- package/dist/ui/components/area-chart/index.ts +2 -3
- 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 +12 -14
- package/dist/ui/components/bar-chart/bar-chart.test.ts +172 -45
- package/dist/ui/components/bar-chart/bar-chart.ts +140 -294
- package/dist/ui/components/bar-chart/index.ts +3 -3
- package/dist/ui/components/bottom-sheet/bottom-sheet.test.ts +386 -128
- package/dist/ui/components/bottom-sheet/bottom-sheet.ts +170 -53
- package/dist/ui/components/button/button.test.ts +611 -2
- package/dist/ui/components/button/button.ts +434 -49
- package/dist/ui/components/button/index.ts +2 -0
- package/dist/ui/components/button-group/button-group.test.ts +484 -2
- package/dist/ui/components/button-group/button-group.ts +380 -68
- package/dist/ui/components/button-group/index.ts +3 -0
- package/dist/ui/components/candlestick-chart/candlestick-chart.test.ts +183 -58
- package/dist/ui/components/candlestick-chart/candlestick-chart.ts +150 -323
- package/dist/ui/components/candlestick-chart/index.ts +3 -4
- package/dist/ui/components/card/card.test.ts +82 -62
- package/dist/ui/components/chart/chart-focus.test.ts +132 -0
- package/dist/ui/components/chart/chart-focus.ts +115 -0
- package/dist/ui/components/chart/chart-presets.test.ts +335 -0
- package/dist/ui/components/chart/chart-presets.ts +278 -0
- package/dist/ui/components/chart/chart-scene.test.ts +589 -0
- package/dist/ui/components/chart/chart-scene.ts +483 -0
- package/dist/ui/components/chart/chart-theme.test.ts +85 -0
- package/dist/ui/components/chart/chart-theme.ts +210 -0
- package/dist/ui/components/chart/chart-zoom.test.ts +159 -0
- package/dist/ui/components/chart/chart-zoom.ts +162 -0
- package/dist/ui/components/chart/chart.test.ts +1017 -0
- package/dist/ui/components/chart/chart.ts +934 -0
- package/dist/ui/components/chart/index.ts +24 -0
- package/dist/ui/components/checkbox/checkbox.test.ts +124 -42
- package/dist/ui/components/checkbox/checkbox.ts +23 -23
- package/dist/ui/components/collapsible/collapsible.test.ts +158 -81
- package/dist/ui/components/collapsible/collapsible.ts +19 -14
- 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 +213 -108
- package/dist/ui/components/dialog/dialog.ts +72 -35
- package/dist/ui/components/dock/dock-scroll.test.ts +130 -0
- package/dist/ui/components/dock/dock-scroll.ts +133 -0
- package/dist/ui/components/dock/dock.test.ts +749 -0
- package/dist/ui/components/dock/dock.ts +960 -0
- package/dist/ui/components/dock/index.ts +2 -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 +338 -0
- package/dist/ui/components/glass/glass.ts +360 -0
- package/dist/ui/components/glass/index.ts +18 -0
- package/dist/ui/components/icon/icon-theme.test.ts +169 -0
- package/dist/ui/components/icon/icon-theme.ts +227 -0
- package/dist/ui/components/icon/icon.test.ts +85 -25
- package/dist/ui/components/icon/icon.ts +38 -21
- package/dist/ui/components/input/input.test.ts +178 -101
- package/dist/ui/components/input/input.ts +15 -12
- package/dist/ui/components/label/label.test.ts +11 -24
- package/dist/ui/components/line-chart/index.ts +2 -3
- package/dist/ui/components/line-chart/line-chart.test.ts +149 -56
- package/dist/ui/components/line-chart/line-chart.ts +83 -241
- package/dist/ui/components/list/list.test.ts +119 -21
- package/dist/ui/components/list/list.ts +21 -15
- 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 +632 -0
- package/dist/ui/components/menu/menu-popup.ts +544 -0
- package/dist/ui/components/menu/menu.test.ts +429 -0
- package/dist/ui/components/menu/menu.ts +415 -0
- package/dist/ui/components/nav-drawer/nav-drawer.test.ts +435 -191
- package/dist/ui/components/nav-drawer/nav-drawer.ts +127 -56
- package/dist/ui/components/pagination/pagination.test.ts +177 -65
- package/dist/ui/components/pagination/pagination.ts +55 -8
- package/dist/ui/components/progress/progress.test.ts +102 -46
- package/dist/ui/components/progress/progress.ts +52 -54
- package/dist/ui/components/radio-group/radio-group.test.ts +128 -83
- package/dist/ui/components/radio-group/radio-group.ts +24 -24
- package/dist/ui/components/scatter-chart/index.ts +2 -3
- package/dist/ui/components/scatter-chart/scatter-chart.test.ts +131 -26
- package/dist/ui/components/scatter-chart/scatter-chart.ts +104 -250
- package/dist/ui/components/scroll-area/scroll-area.test.ts +95 -24
- package/dist/ui/components/scroll-area/scroll-area.ts +8 -8
- package/dist/ui/components/select/select.test.ts +138 -78
- package/dist/ui/components/select/select.ts +17 -11
- package/dist/ui/components/separator/separator.test.ts +18 -17
- package/dist/ui/components/sheet/sheet.test.ts +64 -11
- package/dist/ui/components/sheet/sheet.ts +9 -1
- package/dist/ui/components/skeleton/skeleton.test.ts +24 -7
- package/dist/ui/components/skeleton/skeleton.ts +10 -16
- package/dist/ui/components/spinner/spinner.test.ts +67 -22
- package/dist/ui/components/spinner/spinner.ts +48 -41
- package/dist/ui/components/switch/switch.test.ts +116 -46
- package/dist/ui/components/switch/switch.ts +8 -13
- 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/tabs.test.ts +215 -157
- package/dist/ui/components/tabs/tabs.ts +110 -118
- package/dist/ui/components/textarea/textarea.test.ts +206 -100
- package/dist/ui/components/textarea/textarea.ts +14 -12
- package/dist/ui/components/toast/toast.test.ts +436 -315
- package/dist/ui/components/toast/toast.ts +62 -55
- package/dist/ui/components/toggle/toggle.test.ts +247 -140
- package/dist/ui/theme/chart-colors.test.ts +35 -0
- package/dist/ui/theme/dark.css +46 -2
- package/dist/ui/theme/default.css +63 -7
- package/dist/ui/theme/icon-colors.test.ts +35 -0
- 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 +98 -25
- package/dist/ui/theme/tailwind.css +113 -0
- package/dist/ui/utils/animate.test.ts +13 -1
- package/dist/utils/analyze.d.ts +14 -0
- package/dist/utils/analyze.js +15 -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 +29 -0
- package/dist/utils/installed.js +43 -0
- package/dist/utils/installed.js.map +1 -0
- package/dist/utils/installed.test.d.ts +1 -0
- package/dist/utils/installed.test.js +57 -0
- package/dist/utils/installed.test.js.map +1 -0
- package/dist/utils/interactive.d.ts +9 -0
- package/dist/utils/interactive.js +10 -0
- package/dist/utils/interactive.js.map +1 -0
- package/dist/utils/interactive.test.d.ts +1 -0
- package/dist/utils/interactive.test.js +24 -0
- package/dist/utils/interactive.test.js.map +1 -0
- package/dist/utils/packages.d.ts +36 -0
- package/dist/utils/packages.js +70 -0
- package/dist/utils/packages.js.map +1 -0
- package/dist/utils/packages.test.d.ts +1 -0
- package/dist/utils/packages.test.js +89 -0
- package/dist/utils/packages.test.js.map +1 -0
- package/dist/utils/resolve-paths.d.ts +17 -1
- package/dist/utils/resolve-paths.js +21 -3
- package/dist/utils/resolve-paths.js.map +1 -1
- package/dist/utils/resolve-paths.test.js +38 -2
- package/dist/utils/resolve-paths.test.js.map +1 -1
- package/dist/utils/tailwind.d.ts +25 -0
- package/dist/utils/tailwind.js +51 -0
- package/dist/utils/tailwind.js.map +1 -0
- package/dist/utils/tailwind.test.d.ts +1 -0
- package/dist/utils/tailwind.test.js +71 -0
- package/dist/utils/tailwind.test.js.map +1 -0
- package/package.json +13 -1
- package/dist/ui/components/area-chart/area-chart-render.test.ts +0 -121
- package/dist/ui/components/bar-chart/bar-chart-render.test.ts +0 -174
- package/dist/ui/components/button/button-render.test.ts +0 -113
- 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 -174
- package/dist/ui/components/cartesian-chart/cartesian-chart-render.test.ts +0 -188
- package/dist/ui/components/cartesian-chart/cartesian-chart.test.ts +0 -525
- package/dist/ui/components/cartesian-chart/cartesian-chart.ts +0 -1140
- package/dist/ui/components/cartesian-chart/index.ts +0 -18
- package/dist/ui/components/line-chart/line-chart-render.test.ts +0 -121
- package/dist/ui/components/scatter-chart/scatter-chart-render.test.ts +0 -157
|
@@ -2,272 +2,114 @@ import {
|
|
|
2
2
|
Component,
|
|
3
3
|
ViewEncapsulation,
|
|
4
4
|
computed,
|
|
5
|
-
inject,
|
|
6
5
|
input,
|
|
7
6
|
output,
|
|
8
7
|
} from '@angular/core';
|
|
9
|
-
import {
|
|
8
|
+
import { d3Curve, defineChart, dot, lineY } from '@tanstack/charts/universal';
|
|
9
|
+
import type { ChartMark, ChartPoint } from '@tanstack/charts/types';
|
|
10
|
+
import { curveMonotoneX } from 'd3-shape';
|
|
10
11
|
|
|
12
|
+
import { UiChart, tooltip } from '../chart/chart';
|
|
11
13
|
import {
|
|
12
|
-
type
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
} from '../
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
angle: number;
|
|
30
|
-
};
|
|
14
|
+
type CartesianPresetOptions,
|
|
15
|
+
PRESET_CHART_BINDINGS,
|
|
16
|
+
UiCartesianPreset,
|
|
17
|
+
type XYDatum,
|
|
18
|
+
presetAxes,
|
|
19
|
+
} from '../chart/chart-presets';
|
|
20
|
+
|
|
21
|
+
export type LineChartOptions = {
|
|
22
|
+
readonly data: readonly XYDatum[];
|
|
23
|
+
/** Any CSS color; theme tokens like `var(--primary)` follow dark mode. */
|
|
24
|
+
readonly color?: string;
|
|
25
|
+
readonly strokeWidth?: number;
|
|
26
|
+
readonly dotRadius?: number;
|
|
27
|
+
readonly showDots?: boolean;
|
|
28
|
+
/** Draws a monotone curve through the points instead of straight segments. */
|
|
29
|
+
readonly smooth?: boolean;
|
|
30
|
+
} & CartesianPresetOptions;
|
|
31
31
|
|
|
32
32
|
/**
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* as a thin `<view>` rotated about its start point (see `UiLineSeries`).
|
|
33
|
+
* The definition `<ui-line-chart>` renders, for when you want to start from
|
|
34
|
+
* the preset and add marks of your own:
|
|
36
35
|
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
* never emit a degenerate 0-width rotated view.
|
|
36
|
+
* ```ts
|
|
37
|
+
* const base = lineChartDefinition({ data });
|
|
38
|
+
* const withTarget = defineChart({ ...base, marks: [...base.marks, ruleY([80])] });
|
|
39
|
+
* ```
|
|
42
40
|
*/
|
|
43
|
-
export const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
41
|
+
export const lineChartDefinition = (options: LineChartOptions) => {
|
|
42
|
+
const {
|
|
43
|
+
data,
|
|
44
|
+
color = 'var(--primary)',
|
|
45
|
+
strokeWidth = 2,
|
|
46
|
+
dotRadius = 3,
|
|
47
|
+
showDots = true,
|
|
48
|
+
smooth = false,
|
|
49
|
+
} = options;
|
|
50
|
+
const curve = smooth ? d3Curve(curveMonotoneX) : undefined;
|
|
51
|
+
const marks: ChartMark<XYDatum, any, any, any, any, any, any>[] = [
|
|
52
|
+
lineY(data, { x: 'x', y: 'y', stroke: color, strokeWidth, curve }),
|
|
53
|
+
];
|
|
54
|
+
if (showDots && dotRadius > 0) {
|
|
55
|
+
marks.push(dot(data, { x: 'x', y: 'y', r: dotRadius, fill: color }));
|
|
56
56
|
}
|
|
57
|
-
return
|
|
57
|
+
return defineChart({
|
|
58
|
+
marks,
|
|
59
|
+
scales: presetAxes(options, {
|
|
60
|
+
x: data.map((d) => d.x),
|
|
61
|
+
y: data.map((d) => d.y),
|
|
62
|
+
}),
|
|
63
|
+
tooltip: options.tooltip === false ? false : tooltip,
|
|
64
|
+
});
|
|
58
65
|
};
|
|
59
66
|
|
|
60
|
-
// ---------------------------------------------------------------------------
|
|
61
|
-
// Rendering helpers (mirror the equivalents in cartesian-chart.ts — kept local
|
|
62
|
-
// so this module stays self-contained when scaffolded by `dolan add`)
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* Formats a pixel value for an inline style, avoiding scientific notation.
|
|
67
|
-
*/
|
|
68
|
-
const px = (n: number): string => `${n.toFixed(2)}px`;
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Default axis label formatter — plain JS only (no `Intl`, absent in Lynx).
|
|
72
|
-
*/
|
|
73
|
-
const defaultTickFormat = (value: number): string =>
|
|
74
|
-
String(Math.round(value * 100) / 100);
|
|
75
|
-
|
|
76
67
|
/**
|
|
77
|
-
* A single line
|
|
78
|
-
*
|
|
79
|
-
*
|
|
68
|
+
* A single-series line chart. For several series, a reference line or any
|
|
69
|
+
* other mark, build a definition and render it with `<ui-chart>`, starting
|
|
70
|
+
* from {@link lineChartDefinition} if you like.
|
|
80
71
|
*
|
|
81
|
-
*
|
|
82
|
-
*
|
|
83
|
-
*
|
|
84
|
-
* itself must be the plot-sized containing block — it fills the plot area (its
|
|
85
|
-
* direct parent, a definite-size box). Sizing an *inner* view instead would fail
|
|
86
|
-
* because a content-sized host of absolute-only children collapses to 0×0.
|
|
87
|
-
*/
|
|
88
|
-
@Component({
|
|
89
|
-
selector: 'ui-line-series',
|
|
90
|
-
standalone: true,
|
|
91
|
-
imports: [LYNX_ELEMENTS],
|
|
92
|
-
encapsulation: ViewEncapsulation.None,
|
|
93
|
-
host: {
|
|
94
|
-
style:
|
|
95
|
-
'position: absolute; left: 0px; top: 0px; width: 100%; height: 100%;',
|
|
96
|
-
'[class]': 'userClass()',
|
|
97
|
-
},
|
|
98
|
-
template: `
|
|
99
|
-
<!-- Segments render before dots so each dot paints on top of the line
|
|
100
|
-
(Lynx paint order follows source order; z-index is unnecessary). -->
|
|
101
|
-
@for (segment of segments(); track $index) {
|
|
102
|
-
<view [style]="segment.style"></view>
|
|
103
|
-
}
|
|
104
|
-
@for (dot of dots(); track $index) {
|
|
105
|
-
<view [style]="dot.style" (bindtap)="onPointTap(dot.point)"></view>
|
|
106
|
-
}
|
|
107
|
-
`,
|
|
108
|
-
})
|
|
109
|
-
export class UiLineSeries {
|
|
110
|
-
readonly #chart = inject(UiCartesianChart);
|
|
111
|
-
|
|
112
|
-
readonly data = input.required<ChartPoint[]>();
|
|
113
|
-
readonly color = input('var(--primary)');
|
|
114
|
-
readonly strokeWidth = input(2);
|
|
115
|
-
readonly dotRadius = input(3);
|
|
116
|
-
readonly showDots = input(true);
|
|
117
|
-
/** Draw a smooth (monotone-cubic) curve through the points instead of straight
|
|
118
|
-
* segments. Dots still sit on the raw data points. */
|
|
119
|
-
readonly smooth = input(false);
|
|
120
|
-
readonly userClass = input<string>('', { alias: 'class' });
|
|
121
|
-
|
|
122
|
-
readonly pointTap = output<ChartPoint>();
|
|
123
|
-
|
|
124
|
-
// Project each data point into plot-local pixel space via the shared scales.
|
|
125
|
-
// Reading the chart's scale signals here makes this recompute whenever the
|
|
126
|
-
// domain or plot size changes.
|
|
127
|
-
readonly #pixelPoints = computed(() => {
|
|
128
|
-
const scaleX = this.#chart.xScale();
|
|
129
|
-
const scaleY = this.#chart.yScale();
|
|
130
|
-
return this.data().map((point) => ({
|
|
131
|
-
x: scaleX(point.x),
|
|
132
|
-
y: scaleY(point.y),
|
|
133
|
-
point,
|
|
134
|
-
}));
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
protected readonly segments = computed(() => {
|
|
138
|
-
const stroke = this.strokeWidth();
|
|
139
|
-
const fill = this.color();
|
|
140
|
-
// Smooth mode replaces the raw polyline with a dense monotone-cubic
|
|
141
|
-
// resampling; `computeLineSegments` then rasterizes it identically — the
|
|
142
|
-
// curve is just made of many more, shorter rotated views.
|
|
143
|
-
const pixels = this.#pixelPoints();
|
|
144
|
-
const line = this.smooth() ? sampleSmoothLine(pixels) : pixels;
|
|
145
|
-
return computeLineSegments(line).map((s) => ({
|
|
146
|
-
style:
|
|
147
|
-
`position: absolute; left: ${px(s.x)}; top: ${px(s.y - stroke / 2)}; ` +
|
|
148
|
-
`width: ${px(s.length)}; height: ${px(stroke)}; ` +
|
|
149
|
-
`background-color: ${fill}; border-radius: ${px(stroke / 2)}; ` +
|
|
150
|
-
`transform-origin: 0 50%; transform: rotate(${s.angle.toFixed(2)}deg);`,
|
|
151
|
-
}));
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
protected readonly dots = computed(() => {
|
|
155
|
-
if (!this.showDots()) return [];
|
|
156
|
-
const radius = this.dotRadius();
|
|
157
|
-
const fill = this.color();
|
|
158
|
-
return this.#pixelPoints().map((p) => ({
|
|
159
|
-
point: p.point,
|
|
160
|
-
style:
|
|
161
|
-
`position: absolute; left: ${px(p.x - radius)}; top: ${px(p.y - radius)}; ` +
|
|
162
|
-
`width: ${px(radius * 2)}; height: ${px(radius * 2)}; ` +
|
|
163
|
-
`background-color: ${fill}; border-radius: ${px(radius)};`,
|
|
164
|
-
}));
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
protected onPointTap(point: ChartPoint): void {
|
|
168
|
-
this.pointTap.emit(point);
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* A ready-to-use line chart for the common single-series case. Composes a
|
|
174
|
-
* `<ui-cartesian-chart>` with one `<ui-line-series>` and auto-derives rounded
|
|
175
|
-
* axis domains + ticks from the data. For multiple series or full control over
|
|
176
|
-
* the axes, use `<ui-cartesian-chart>` with `<ui-line-series>` children directly.
|
|
72
|
+
* ```html
|
|
73
|
+
* <ui-line-chart [data]="revenue" [smooth]="true" yAxisLabel="Revenue" />
|
|
74
|
+
* ```
|
|
177
75
|
*/
|
|
178
76
|
@Component({
|
|
179
77
|
selector: 'ui-line-chart',
|
|
180
|
-
|
|
181
|
-
imports: [LYNX_ELEMENTS, UiCartesianChart, UiLineSeries],
|
|
78
|
+
imports: [UiChart],
|
|
182
79
|
encapsulation: ViewEncapsulation.None,
|
|
183
80
|
template: `
|
|
184
|
-
<ui-
|
|
185
|
-
[
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
[width]="width()"
|
|
190
|
-
[height]="height()"
|
|
191
|
-
[tickCount]="tickCount()"
|
|
192
|
-
[showXGrid]="showXGrid()"
|
|
193
|
-
[xAxisLabel]="xAxisLabel()"
|
|
194
|
-
[yAxisLabel]="yAxisLabel()"
|
|
195
|
-
[padding]="padding()"
|
|
196
|
-
[xTickFormat]="xTickFormat()"
|
|
197
|
-
[yTickFormat]="yTickFormat()"
|
|
198
|
-
[zoomable]="zoomable()"
|
|
199
|
-
[zoomAxes]="zoomAxes()"
|
|
200
|
-
[showZoomControls]="showZoomControls()"
|
|
201
|
-
[minZoom]="minZoom()"
|
|
202
|
-
[maxZoom]="maxZoom()"
|
|
203
|
-
[zoomStep]="zoomStep()"
|
|
204
|
-
[class]="userClass()"
|
|
205
|
-
>
|
|
206
|
-
<ui-line-series
|
|
207
|
-
[data]="data()"
|
|
208
|
-
[color]="color()"
|
|
209
|
-
[strokeWidth]="strokeWidth()"
|
|
210
|
-
[dotRadius]="dotRadius()"
|
|
211
|
-
[showDots]="showDots()"
|
|
212
|
-
[smooth]="smooth()"
|
|
213
|
-
(pointTap)="pointTap.emit($event)"
|
|
214
|
-
/>
|
|
215
|
-
</ui-cartesian-chart>
|
|
81
|
+
<ui-chart
|
|
82
|
+
[definition]="definition()"
|
|
83
|
+
${PRESET_CHART_BINDINGS}
|
|
84
|
+
(pointSelect)="onSelect($event)"
|
|
85
|
+
/>
|
|
216
86
|
`,
|
|
217
87
|
})
|
|
218
|
-
export class UiLineChart {
|
|
219
|
-
readonly data = input.required<
|
|
88
|
+
export class UiLineChart extends UiCartesianPreset<XYDatum> {
|
|
89
|
+
readonly data = input.required<readonly XYDatum[]>();
|
|
220
90
|
readonly color = input('var(--primary)');
|
|
221
|
-
readonly width = input(320);
|
|
222
|
-
readonly height = input(200);
|
|
223
|
-
readonly tickCount = input(5);
|
|
224
91
|
readonly strokeWidth = input(2);
|
|
225
92
|
readonly dotRadius = input(3);
|
|
226
93
|
readonly showDots = input(true);
|
|
227
|
-
/** Draw a smooth (monotone-cubic) curve through the points. */
|
|
228
94
|
readonly smooth = input(false);
|
|
229
|
-
/** Draw vertical gridlines at each x tick. */
|
|
230
|
-
readonly showXGrid = input(false);
|
|
231
|
-
/** Title for the x-axis (centered below the tick labels). */
|
|
232
|
-
readonly xAxisLabel = input<string>('');
|
|
233
|
-
/** Title for the y-axis (rotated in the left gutter). */
|
|
234
|
-
readonly yAxisLabel = input<string>('');
|
|
235
|
-
/** Inner inset of the plot area, each side a fraction (0–1) — see {@link ChartPadding}. */
|
|
236
|
-
readonly padding = input<ChartPadding>({});
|
|
237
|
-
/** Force the x-axis lower/upper bound instead of deriving it from the data —
|
|
238
|
-
* e.g. `xMin={0}` to anchor the origin. Omitted bounds fall back to the data. */
|
|
239
|
-
readonly xMin = input<number | undefined>(undefined);
|
|
240
|
-
readonly xMax = input<number | undefined>(undefined);
|
|
241
|
-
/** Force the y-axis lower/upper bound (e.g. `yMin={0}` to start at zero). */
|
|
242
|
-
readonly yMin = input<number | undefined>(undefined);
|
|
243
|
-
readonly yMax = input<number | undefined>(undefined);
|
|
244
|
-
readonly xTickFormat = input<(value: number) => string>(defaultTickFormat);
|
|
245
|
-
readonly yTickFormat = input<(value: number) => string>(defaultTickFormat);
|
|
246
|
-
readonly userClass = input<string>('', { alias: 'class' });
|
|
247
95
|
|
|
248
|
-
|
|
249
|
-
readonly
|
|
250
|
-
|
|
251
|
-
readonly
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
const
|
|
265
|
-
|
|
266
|
-
}
|
|
267
|
-
protected readonly yAxis = computed(() => {
|
|
268
|
-
const ys = this.data().map((p) => p.y);
|
|
269
|
-
const lo = this.yMin() ?? Math.min(...ys);
|
|
270
|
-
const hi = this.yMax() ?? Math.max(...ys);
|
|
271
|
-
return niceScale(lo, hi, this.tickCount());
|
|
272
|
-
});
|
|
96
|
+
/** The tapped point's datum. */
|
|
97
|
+
readonly pointTap = output<XYDatum>();
|
|
98
|
+
|
|
99
|
+
protected readonly definition = computed(() =>
|
|
100
|
+
lineChartDefinition({
|
|
101
|
+
...this.commonOptions(),
|
|
102
|
+
data: this.data(),
|
|
103
|
+
color: this.color(),
|
|
104
|
+
strokeWidth: this.strokeWidth(),
|
|
105
|
+
dotRadius: this.dotRadius(),
|
|
106
|
+
showDots: this.showDots(),
|
|
107
|
+
smooth: this.smooth(),
|
|
108
|
+
}),
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
protected onSelect(point: ChartPoint | null): void {
|
|
112
|
+
const datum = this.datumOf(point);
|
|
113
|
+
if (datum) this.pointTap.emit(datum);
|
|
114
|
+
}
|
|
273
115
|
}
|
|
@@ -1,35 +1,133 @@
|
|
|
1
|
-
import { Component } from '@angular/core';
|
|
2
|
-
import {
|
|
1
|
+
import { Component, signal } from '@angular/core';
|
|
2
|
+
import {
|
|
3
|
+
fireEvent,
|
|
4
|
+
render,
|
|
5
|
+
waitForUpdate,
|
|
6
|
+
} from '@blotch/angular-lynx-testing-library';
|
|
3
7
|
import { LYNX_ELEMENTS } from '@blotch/angular-lynx';
|
|
4
|
-
import { describe, expect, it } from 'vitest';
|
|
5
|
-
import {
|
|
8
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
9
|
+
import { lynxMainThread } from '../../../test-utils/lynx-main-thread';
|
|
6
10
|
import { UiList } from './list';
|
|
7
11
|
|
|
8
12
|
@Component({
|
|
9
|
-
standalone: true,
|
|
10
13
|
imports: [UiList, LYNX_ELEMENTS],
|
|
11
|
-
template:
|
|
14
|
+
template: `
|
|
15
|
+
<ui-list
|
|
16
|
+
[height]="height()"
|
|
17
|
+
[mainAxisGap]="mainAxisGap()"
|
|
18
|
+
[crossAxisGap]="crossAxisGap()"
|
|
19
|
+
[bounces]="bounces()"
|
|
20
|
+
[scrollBar]="scrollBar()"
|
|
21
|
+
[nestedScroll]="nestedScroll()"
|
|
22
|
+
[sticky]="sticky()"
|
|
23
|
+
class="bg-card"
|
|
24
|
+
(scrolledToStart)="reached.push('start')"
|
|
25
|
+
(scrolledToEnd)="reached.push('end')"
|
|
26
|
+
>
|
|
27
|
+
<text>Item</text>
|
|
28
|
+
</ui-list>
|
|
29
|
+
`,
|
|
12
30
|
})
|
|
13
|
-
class ListHost {
|
|
31
|
+
class ListHost {
|
|
32
|
+
readonly height = signal('');
|
|
33
|
+
readonly mainAxisGap = signal('');
|
|
34
|
+
readonly crossAxisGap = signal('');
|
|
35
|
+
readonly bounces = signal(true);
|
|
36
|
+
readonly scrollBar = signal(true);
|
|
37
|
+
readonly nestedScroll = signal(false);
|
|
38
|
+
readonly sticky = signal(false);
|
|
39
|
+
readonly reached: string[] = [];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const renderList = async () => {
|
|
43
|
+
const result = await render(ListHost);
|
|
44
|
+
return {
|
|
45
|
+
...result,
|
|
46
|
+
host: result.componentRef.instance as ListHost,
|
|
47
|
+
list: () => result.container.querySelector('list')!,
|
|
48
|
+
};
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* The values each boolean reached Lynx with, through `__SetAttribute`.
|
|
53
|
+
*/
|
|
54
|
+
const watchAttributes = () => {
|
|
55
|
+
const setAttribute = vi.spyOn(lynxMainThread(), '__SetAttribute');
|
|
56
|
+
return {
|
|
57
|
+
last: (name: string): unknown =>
|
|
58
|
+
setAttribute.mock.calls.filter((call) => call[1] === name).at(-1)?.[2],
|
|
59
|
+
restore: () => setAttribute.mockRestore(),
|
|
60
|
+
};
|
|
61
|
+
};
|
|
14
62
|
|
|
15
63
|
describe('UiList', () => {
|
|
16
|
-
it('renders
|
|
17
|
-
// Lynx `<list>` is virtualized
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
expect(
|
|
64
|
+
it('renders a single-column vertical list with the given class', async () => {
|
|
65
|
+
// Lynx `<list>` is virtualized: items join the native tree only through
|
|
66
|
+
// its componentAtIndex callback, which a unit test doesn't drive. So this
|
|
67
|
+
// checks the list itself rather than the projected item.
|
|
68
|
+
const { list } = await renderList();
|
|
69
|
+
expect(list().getAttribute('list-type')).toBe('single');
|
|
70
|
+
expect(list().getAttribute('span-count')).toBe('1');
|
|
71
|
+
expect(list().getAttribute('scroll-orientation')).toBe('vertical');
|
|
72
|
+
expect(list().classList).toContain('w-full');
|
|
73
|
+
expect(list().classList).toContain('bg-card');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('gives the list an explicit height, which it needs to scroll', async () => {
|
|
77
|
+
const { host, list } = await renderList();
|
|
78
|
+
expect(list().getAttribute('style') ?? '').not.toContain('height');
|
|
79
|
+
|
|
80
|
+
host.height.set('400px');
|
|
81
|
+
await waitForUpdate();
|
|
82
|
+
expect(list().getAttribute('style')).toContain('height: 400px');
|
|
23
83
|
});
|
|
24
84
|
|
|
25
|
-
it('
|
|
26
|
-
//
|
|
27
|
-
//
|
|
28
|
-
const
|
|
29
|
-
|
|
85
|
+
it('sets the gaps as list styles, which Lynx reads, not attributes', async () => {
|
|
86
|
+
// jsdom drops style properties it doesn't know, so the gaps never show in
|
|
87
|
+
// the style attribute: watch what reaches Lynx's inline style API instead.
|
|
88
|
+
const addInlineStyle = vi.spyOn(lynxMainThread(), '__AddInlineStyle');
|
|
89
|
+
const { host, list } = await renderList();
|
|
90
|
+
host.mainAxisGap.set('8px');
|
|
91
|
+
host.crossAxisGap.set('12px');
|
|
30
92
|
await waitForUpdate();
|
|
31
93
|
|
|
32
|
-
|
|
33
|
-
|
|
94
|
+
expect(addInlineStyle).toHaveBeenCalledWith(
|
|
95
|
+
list(),
|
|
96
|
+
'list-main-axis-gap',
|
|
97
|
+
'8px',
|
|
98
|
+
);
|
|
99
|
+
expect(addInlineStyle).toHaveBeenCalledWith(
|
|
100
|
+
list(),
|
|
101
|
+
'list-cross-axis-gap',
|
|
102
|
+
'12px',
|
|
103
|
+
);
|
|
104
|
+
expect(list().getAttribute('list-main-axis-gap')).toBeNull();
|
|
105
|
+
addInlineStyle.mockRestore();
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it('hands Lynx real booleans, so the defaults that are on can be turned off', async () => {
|
|
109
|
+
// A false used to remove the attribute, which leaves Lynx's default: on.
|
|
110
|
+
const attributes = watchAttributes();
|
|
111
|
+
const { host } = await renderList();
|
|
112
|
+
expect(attributes.last('bounces')).toBe(true);
|
|
113
|
+
expect(attributes.last('scroll-bar-enable')).toBe(true);
|
|
114
|
+
|
|
115
|
+
host.bounces.set(false);
|
|
116
|
+
host.scrollBar.set(false);
|
|
117
|
+
host.nestedScroll.set(true);
|
|
118
|
+
host.sticky.set(true);
|
|
119
|
+
await waitForUpdate();
|
|
120
|
+
expect(attributes.last('bounces')).toBe(false);
|
|
121
|
+
expect(attributes.last('scroll-bar-enable')).toBe(false);
|
|
122
|
+
expect(attributes.last('enable-nested-scroll')).toBe(true);
|
|
123
|
+
expect(attributes.last('sticky')).toBe(true);
|
|
124
|
+
attributes.restore();
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('emits when scrolled to the start or the end', async () => {
|
|
128
|
+
const { host, list } = await renderList();
|
|
129
|
+
fireEvent.scrolltolower(list());
|
|
130
|
+
fireEvent.scrolltoupper(list());
|
|
131
|
+
expect(host.reached).toEqual(['end', 'start']);
|
|
34
132
|
});
|
|
35
133
|
});
|
|
@@ -11,25 +11,24 @@ import { cn } from '../../utils/cn';
|
|
|
11
11
|
|
|
12
12
|
@Component({
|
|
13
13
|
selector: 'ui-list',
|
|
14
|
-
standalone: true,
|
|
15
14
|
imports: [LYNX_ELEMENTS],
|
|
16
15
|
encapsulation: ViewEncapsulation.None,
|
|
17
|
-
//
|
|
18
|
-
// false
|
|
19
|
-
// attribute
|
|
16
|
+
// The booleans are property bindings to <list>'s inputs, which hand Lynx a
|
|
17
|
+
// real `false`. They used to be `[attr.x]="x() || undefined"`, which removed
|
|
18
|
+
// the attribute when false, and a missing attribute means Lynx's default: on,
|
|
19
|
+
// for `bounces` and `scroll-bar-enable`. So neither could be turned off.
|
|
20
|
+
// (`[attr.x]` would send the string "false" instead.)
|
|
20
21
|
template: `
|
|
21
22
|
<list
|
|
22
23
|
[attr.list-type]="listType()"
|
|
23
24
|
[attr.span-count]="spanCount()"
|
|
24
25
|
[attr.scroll-orientation]="orientation()"
|
|
25
|
-
[
|
|
26
|
-
[
|
|
27
|
-
[
|
|
28
|
-
[
|
|
26
|
+
[bounces]="bounces()"
|
|
27
|
+
[scroll-bar-enable]="scrollBar()"
|
|
28
|
+
[enable-nested-scroll]="nestedScroll()"
|
|
29
|
+
[sticky]="sticky()"
|
|
29
30
|
[attr.sticky-offset]="stickyOffset()"
|
|
30
31
|
[attr.preload-buffer-count]="preloadBufferCount()"
|
|
31
|
-
[attr.list-main-axis-gap]="mainAxisGap()"
|
|
32
|
-
[attr.list-cross-axis-gap]="crossAxisGap()"
|
|
33
32
|
[attr.lower-threshold-item-count]="lowerThresholdItemCount()"
|
|
34
33
|
[attr.upper-threshold-item-count]="upperThresholdItemCount()"
|
|
35
34
|
[class]="listClass()"
|
|
@@ -63,9 +62,16 @@ export class UiList {
|
|
|
63
62
|
|
|
64
63
|
protected readonly listClass = computed(() => cn('w-full', this.userClass()));
|
|
65
64
|
|
|
66
|
-
// Explicit height is required for list to scroll in Lynx
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
65
|
+
// Explicit height is required for list to scroll in Lynx. The gaps go here
|
|
66
|
+
// too: they're CSS properties that Lynx's list reads from its style, and as
|
|
67
|
+
// attributes they did nothing.
|
|
68
|
+
protected readonly listStyle = computed(() =>
|
|
69
|
+
[
|
|
70
|
+
this.height() && `height: ${this.height()};`,
|
|
71
|
+
this.mainAxisGap() && `list-main-axis-gap: ${this.mainAxisGap()};`,
|
|
72
|
+
this.crossAxisGap() && `list-cross-axis-gap: ${this.crossAxisGap()};`,
|
|
73
|
+
]
|
|
74
|
+
.filter(Boolean)
|
|
75
|
+
.join(' '),
|
|
76
|
+
);
|
|
71
77
|
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export {
|
|
2
|
+
UI_MENU,
|
|
3
|
+
UiMenu,
|
|
4
|
+
UiMenuItem,
|
|
5
|
+
UiMenuSection,
|
|
6
|
+
UiMenuSeparator,
|
|
7
|
+
UiMenuTrigger,
|
|
8
|
+
type UiMenuHost,
|
|
9
|
+
} from './menu';
|
|
10
|
+
export {
|
|
11
|
+
UiMenuPopup,
|
|
12
|
+
measureMenuElement,
|
|
13
|
+
type MenuElement,
|
|
14
|
+
type MenuKind,
|
|
15
|
+
type MenuPhase,
|
|
16
|
+
} from './menu-popup';
|
|
17
|
+
export {
|
|
18
|
+
menuStartFrame,
|
|
19
|
+
placeMenu,
|
|
20
|
+
type MenuPlacement,
|
|
21
|
+
type MenuPlacementMode,
|
|
22
|
+
type MenuPlacementOptions,
|
|
23
|
+
type MenuRect,
|
|
24
|
+
type MenuSize,
|
|
25
|
+
} from './menu-placement';
|