gloomberb 0.10.3 → 0.10.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.
Files changed (152) hide show
  1. package/README.md +3 -3
  2. package/README.zh-CN.md +2 -2
  3. package/package.json +2 -1
  4. package/src/api-client/auth.ts +8 -0
  5. package/src/api-client/index.ts +70 -0
  6. package/src/api-client/types.ts +49 -0
  7. package/src/app/runtime/desktop-deeplink.test.ts +8 -0
  8. package/src/app/runtime/desktop-deeplink.ts +14 -1
  9. package/src/components/chart/composite/composite-chart.test.tsx +340 -17
  10. package/src/components/chart/composite/composite-chart.tsx +859 -68
  11. package/src/components/chart/composite/interactions.test.ts +78 -22
  12. package/src/components/chart/composite/interactions.ts +45 -17
  13. package/src/components/chart/composite/rasterizer.ts +8 -2
  14. package/src/components/chart/composite/renderers.test.ts +61 -1
  15. package/src/components/chart/composite/scene.ts +14 -4
  16. package/src/components/chart/composite/tools.test.ts +371 -0
  17. package/src/components/chart/composite/tools.ts +508 -0
  18. package/src/components/chart/composite/types.ts +4 -0
  19. package/src/components/chart/core/pointer.ts +2 -0
  20. package/src/components/chart/native/raster/primitives.ts +18 -0
  21. package/src/components/chart/price-axis-labels.tsx +42 -2
  22. package/src/components/chart/static/chart/axis-overlays.test.tsx +2 -0
  23. package/src/components/chart/static/chart/axis-overlays.tsx +46 -0
  24. package/src/components/command-bar/action-runtime.ts +3 -0
  25. package/src/components/command-bar/assist/inventory.test.ts +93 -0
  26. package/src/components/command-bar/assist/inventory.ts +92 -0
  27. package/src/components/command-bar/assist/model.test.ts +104 -0
  28. package/src/components/command-bar/assist/model.ts +169 -0
  29. package/src/components/command-bar/assist/runtime.ts +185 -0
  30. package/src/components/command-bar/commands/direct/index.ts +1 -1
  31. package/src/components/command-bar/keyboard-shortcuts.ts +5 -0
  32. package/src/components/command-bar/list/model.ts +15 -2
  33. package/src/components/command-bar/list/navigation.ts +6 -0
  34. package/src/components/command-bar/list/view.tsx +16 -3
  35. package/src/components/command-bar/pane-templates/workflow.ts +1 -0
  36. package/src/components/command-bar/panel/body.tsx +2 -0
  37. package/src/components/command-bar/panel/index.tsx +3 -0
  38. package/src/components/command-bar/panel/runtime.ts +6 -0
  39. package/src/components/command-bar/panel/types.ts +1 -0
  40. package/src/components/command-bar/routes/root/results.ts +28 -2
  41. package/src/components/command-bar/routes/root/runtime.ts +66 -36
  42. package/src/components/command-bar/routes/root/shortcuts.ts +8 -4
  43. package/src/components/command-bar/routing/navigation-state.ts +17 -0
  44. package/src/components/command-bar/selection-runtime.ts +26 -0
  45. package/src/components/command-bar/surface/assist.test.tsx +276 -0
  46. package/src/components/command-bar/surface/index.tsx +93 -2
  47. package/src/components/command-bar/surface/portfolio.test.tsx +2 -1
  48. package/src/components/command-bar/view-model.ts +7 -1
  49. package/src/components/command-bar/workflow/broker.test.ts +22 -0
  50. package/src/components/command-bar/workflow/broker.ts +1 -0
  51. package/src/components/layout/detached-pane-shell.tsx +40 -2
  52. package/src/components/layout/floating-pane.tsx +4 -1
  53. package/src/components/layout/pane/header.tsx +64 -6
  54. package/src/components/layout/pane/index.tsx +4 -1
  55. package/src/components/layout/shell/index.tsx +23 -0
  56. package/src/components/layout/shell/pane/layers.tsx +5 -0
  57. package/src/components/onboarding/account-step/chooser-panel.tsx +74 -0
  58. package/src/components/onboarding/account-step/form-panel.tsx +157 -0
  59. package/src/components/onboarding/account-step/index.tsx +58 -0
  60. package/src/components/onboarding/account-step/model.test.ts +67 -0
  61. package/src/components/onboarding/account-step/model.ts +126 -0
  62. package/src/components/onboarding/account-step/signed-in-panel.tsx +31 -0
  63. package/src/components/onboarding/onboarding-steps.tsx +91 -55
  64. package/src/components/onboarding/onboarding-wizard.test.tsx +73 -6
  65. package/src/components/onboarding/onboarding-wizard.tsx +93 -9
  66. package/src/components/onboarding/portfolio-step/broker-fields-panel.tsx +3 -3
  67. package/src/components/onboarding/wizard-account.ts +235 -0
  68. package/src/components/onboarding/wizard-keyboard.ts +67 -1
  69. package/src/components/onboarding/wizard-model.ts +2 -2
  70. package/src/i18n/es.ts +1093 -0
  71. package/src/i18n/index.test.ts +5 -2
  72. package/src/i18n/index.ts +3 -0
  73. package/src/i18n/ja.ts +64 -3
  74. package/src/i18n/ko.ts +64 -3
  75. package/src/i18n/languages.test.ts +4 -1
  76. package/src/i18n/languages.ts +12 -1
  77. package/src/i18n/zh-cn.ts +64 -3
  78. package/src/i18n/zh-tw.ts +64 -3
  79. package/src/market-data/coordinator/quotes.ts +63 -1
  80. package/src/market-data/coordinator/subscriptions.test.ts +33 -0
  81. package/src/market-data/quotes/cadence.ts +2 -0
  82. package/src/plugins/builtin/account-management/footer.ts +9 -3
  83. package/src/plugins/builtin/account-management/model.ts +53 -1
  84. package/src/plugins/builtin/account-management/pane.tsx +107 -24
  85. package/src/plugins/builtin/ai/index.tsx +7 -2
  86. package/src/plugins/builtin/ai/screener/market-runtime.ts +4 -2
  87. package/src/plugins/builtin/ai/screener/pane.tsx +3 -0
  88. package/src/plugins/builtin/chart-composer/editor.tsx +0 -1
  89. package/src/plugins/builtin/chart-composer/index.tsx +10 -2
  90. package/src/plugins/builtin/chart-composer/pane.tsx +111 -44
  91. package/src/plugins/builtin/chart-composer/quick-add.tsx +27 -3
  92. package/src/plugins/builtin/chart-composer/semantic.ts +21 -0
  93. package/src/plugins/builtin/chat/controller/persistence.ts +5 -0
  94. package/src/plugins/builtin/cloud/plugin.tsx +9 -1
  95. package/src/plugins/builtin/cloud/upgrade-command.ts +28 -0
  96. package/src/plugins/builtin/cloud/upgrade-status-widget.tsx +54 -0
  97. package/src/plugins/builtin/help/index.tsx +38 -0
  98. package/src/plugins/builtin/news/wire/news/footer.ts +18 -38
  99. package/src/plugins/builtin/options/footer.test.ts +20 -124
  100. package/src/plugins/builtin/options/footer.ts +32 -90
  101. package/src/plugins/builtin/options/index.tsx +6 -0
  102. package/src/plugins/builtin/options/view.tsx +3 -0
  103. package/src/plugins/builtin/portfolio-list/index.tsx +12 -4
  104. package/src/plugins/builtin/portfolio-list/pane/index.tsx +22 -1
  105. package/src/plugins/builtin/portfolio-list/pane/streaming.ts +17 -13
  106. package/src/plugins/builtin/portfolio-list/use-throttled-ticker-order.test.tsx +76 -0
  107. package/src/plugins/builtin/portfolio-list/use-throttled-ticker-order.ts +78 -0
  108. package/src/plugins/builtin/shared/cloud-upgrade.ts +131 -0
  109. package/src/plugins/builtin/shared/live-streaming.ts +60 -0
  110. package/src/plugins/builtin/shared/plan-access.test.ts +69 -0
  111. package/src/plugins/builtin/shared/plan-access.ts +109 -0
  112. package/src/plugins/builtin/ticker-detail/chart-tab-switch.test.tsx +1 -1
  113. package/src/plugins/builtin/ticker-detail/index.tsx +11 -2
  114. package/src/plugins/builtin/ticker-detail/pane.tsx +21 -3
  115. package/src/plugins/builtin/ticker-detail/quote-monitor/index.tsx +4 -2
  116. package/src/plugins/registry/index.test.ts +51 -0
  117. package/src/plugins/registry/index.ts +12 -0
  118. package/src/plugins/registry/pane-settings.ts +29 -0
  119. package/src/renderers/electrobun/view/host/box.test.tsx +97 -0
  120. package/src/renderers/electrobun/view/host/box.tsx +3 -0
  121. package/src/renderers/electrobun/view/host/chart-surface.tsx +85 -1
  122. package/src/renderers/electrobun/view/host/focus-scope.test.ts +37 -0
  123. package/src/renderers/electrobun/view/host/focus-scope.ts +56 -0
  124. package/src/renderers/electrobun/view/host/input.tsx +16 -1
  125. package/src/renderers/electrobun/view/host/mouse.ts +1 -0
  126. package/src/renderers/electrobun/view/host/style.ts +1 -1
  127. package/src/renderers/electrobun/view/host/tabs.tsx +4 -2
  128. package/src/renderers/electrobun/view/main.tsx +2 -0
  129. package/src/renderers/opentui/chart-surface.tsx +2 -0
  130. package/src/renderers/opentui/host.tsx +3 -0
  131. package/src/sources/gloomberb-cloud/index.test.ts +8 -0
  132. package/src/sources/gloomberb-cloud/index.ts +4 -4
  133. package/src/sources/provider-router/history.test.ts +3 -3
  134. package/src/sources/provider-router/history.ts +15 -4
  135. package/src/sources/yahoo-finance/history.test.ts +47 -0
  136. package/src/sources/yahoo-finance/history.ts +5 -1
  137. package/src/state/hooks/quote-streaming.test.tsx +124 -1
  138. package/src/state/hooks/quote-streaming.ts +90 -7
  139. package/src/theme/colors.ts +20 -1
  140. package/src/theme/themes.test.ts +3 -0
  141. package/src/{sources/gloomberb-cloud → time-series}/history-quality.ts +27 -3
  142. package/src/time-series/resolution.test.ts +51 -0
  143. package/src/time-series/resolution.ts +41 -0
  144. package/src/time-series/resolve.test.ts +207 -1
  145. package/src/time-series/resolve.ts +196 -29
  146. package/src/time-series/spec-studies.test.ts +4 -0
  147. package/src/time-series/spec.ts +15 -1
  148. package/src/time-series/types.ts +7 -0
  149. package/src/time-series/use-chart-resolution.test.tsx +37 -0
  150. package/src/time-series/use-chart-resolution.ts +17 -1
  151. package/src/types/plugin.ts +9 -0
  152. package/src/ui/host.tsx +17 -0
@@ -0,0 +1,371 @@
1
+ import { describe, expect, it } from "bun:test";
2
+ import {
3
+ buildChartToolVectors,
4
+ CHART_DRAWING_COLORS,
5
+ countMeasureBars,
6
+ drawChartToolOverlay,
7
+ hitTestDrawings,
8
+ nextDrawingColor,
9
+ shiftDrawing,
10
+ formatMeasureSpan,
11
+ resolveChartToolKind,
12
+ resolveDrawingFromDrag,
13
+ resolveZoomBoxRange,
14
+ summarizeMeasure,
15
+ } from "./tools";
16
+ import type {
17
+ CompositeAxisDomain,
18
+ CompositeChartScene,
19
+ CompositePanelScene,
20
+ } from "./types";
21
+
22
+ const domain: CompositeAxisDomain = {
23
+ side: "right",
24
+ min: 0,
25
+ max: 200,
26
+ scale: "linear",
27
+ unit: "USD",
28
+ unitGroup: "currency",
29
+ seriesIds: ["a"],
30
+ };
31
+
32
+ const DAY = 24 * 60 * 60 * 1000;
33
+ const START = Date.UTC(2026, 0, 1);
34
+
35
+ function calendarScene(spanMs: number): CompositeChartScene {
36
+ return {
37
+ width: 100,
38
+ height: 20,
39
+ startTime: START,
40
+ endTime: START + spanMs,
41
+ timeScale: { kind: "calendar", startTime: START, endTime: START + spanMs },
42
+ dates: [],
43
+ dateRatios: [],
44
+ panels: [],
45
+ cursorDate: null,
46
+ cursorXRatio: null,
47
+ cursorValues: [],
48
+ };
49
+ }
50
+
51
+ describe("resolveChartToolKind", () => {
52
+ it("leaves plain and ctrl drags to pan and wheel zoom", () => {
53
+ expect(resolveChartToolKind({ shift: false, alt: false, ctrl: false })).toBeNull();
54
+ expect(resolveChartToolKind({ shift: true, alt: false, ctrl: true })).toBeNull();
55
+ });
56
+
57
+ it("maps shift to zoom and alt to measure", () => {
58
+ expect(resolveChartToolKind({ shift: true, alt: false, ctrl: false })).toBe("zoom");
59
+ expect(resolveChartToolKind({ shift: false, alt: true, ctrl: false })).toBe("measure");
60
+ expect(resolveChartToolKind({ shift: true, alt: true, ctrl: false })).toBe("zoom");
61
+ });
62
+ });
63
+
64
+ describe("summarizeMeasure", () => {
65
+ it("reports signed change, percent, bars, and span", () => {
66
+ expect(summarizeMeasure({
67
+ startValue: 100,
68
+ endValue: 110,
69
+ startTime: START,
70
+ endTime: START + 3 * DAY,
71
+ bars: 3,
72
+ domain,
73
+ })).toBe("Δ +$10.0 (+10.00%) · 3 bars · 3d");
74
+ });
75
+
76
+ it("keeps the percentage signed when measuring downward", () => {
77
+ expect(summarizeMeasure({
78
+ startValue: 110,
79
+ endValue: 99,
80
+ startTime: START,
81
+ endTime: START + DAY,
82
+ bars: 1,
83
+ domain,
84
+ })).toBe("Δ -$11.0 (-10.00%) · 1 bar · 1d");
85
+ });
86
+
87
+ it("drops the percentage when the baseline is zero or flips sign", () => {
88
+ const fromZero = summarizeMeasure({
89
+ startValue: 0,
90
+ endValue: 5,
91
+ startTime: START,
92
+ endTime: START + DAY,
93
+ bars: 0,
94
+ domain,
95
+ });
96
+ expect(fromZero).toBe("Δ +$5.00 · 1d");
97
+ const acrossZero = summarizeMeasure({
98
+ startValue: -4,
99
+ endValue: 6,
100
+ startTime: START,
101
+ endTime: START + DAY,
102
+ bars: 0,
103
+ domain,
104
+ });
105
+ expect(acrossZero).not.toContain("%");
106
+ });
107
+
108
+ it("reports a percentage relative to the magnitude of a negative baseline", () => {
109
+ const summary = summarizeMeasure({
110
+ startValue: -100,
111
+ endValue: -80,
112
+ startTime: START,
113
+ endTime: START + DAY,
114
+ bars: 0,
115
+ domain,
116
+ });
117
+ expect(summary).toContain("(+20.00%)");
118
+ });
119
+ });
120
+
121
+ describe("formatMeasureSpan", () => {
122
+ it("scales the unit to the span", () => {
123
+ expect(formatMeasureSpan(45_000)).toBe("45s");
124
+ expect(formatMeasureSpan(90 * 60_000)).toBe("1h 30m");
125
+ expect(formatMeasureSpan(2 * DAY + 3 * 60 * 60_000)).toBe("2d 3h");
126
+ expect(formatMeasureSpan(30 * DAY)).toBe("30d");
127
+ });
128
+ });
129
+
130
+ describe("countMeasureBars", () => {
131
+ it("counts observations inside the drag regardless of direction", () => {
132
+ const dates = [0, 1, 2, 3, 4].map((day) => new Date(START + day * DAY));
133
+ expect(countMeasureBars(dates, START, START + 2 * DAY)).toBe(3);
134
+ expect(countMeasureBars(dates, START + 2 * DAY, START)).toBe(3);
135
+ });
136
+ });
137
+
138
+ describe("resolveZoomBoxRange", () => {
139
+ const scene = calendarScene(10 * DAY);
140
+
141
+ it("ignores a modifier-held click", () => {
142
+ expect(resolveZoomBoxRange(scene, {
143
+ kind: "zoom",
144
+ startXRatio: 0.5,
145
+ startYRatio: 0.5,
146
+ endXRatio: 0.501,
147
+ endYRatio: 0.5,
148
+ path: [],
149
+ }, DAY)).toBeNull();
150
+ });
151
+
152
+ it("orders a right-to-left drag", () => {
153
+ const range = resolveZoomBoxRange(scene, {
154
+ kind: "zoom",
155
+ startXRatio: 0.8,
156
+ startYRatio: 0,
157
+ endXRatio: 0.2,
158
+ endYRatio: 1,
159
+ path: [],
160
+ }, DAY);
161
+ expect(range?.start.getTime()).toBe(START + 2 * DAY);
162
+ expect(range?.end.getTime()).toBe(START + 8 * DAY);
163
+ });
164
+
165
+ it("widens a selection below the smallest resolvable span", () => {
166
+ const range = resolveZoomBoxRange(scene, {
167
+ kind: "zoom",
168
+ startXRatio: 0.5,
169
+ startYRatio: 0,
170
+ endXRatio: 0.52,
171
+ endYRatio: 1,
172
+ path: [],
173
+ }, DAY);
174
+ expect(range).not.toBeNull();
175
+ expect(range!.end.getTime() - range!.start.getTime()).toBe(DAY);
176
+ expect((range!.start.getTime() + range!.end.getTime()) / 2).toBe(START + 5.1 * DAY);
177
+ });
178
+ });
179
+
180
+ describe("chart drawings", () => {
181
+ const panel: CompositePanelScene = {
182
+ id: "main",
183
+ height: 10,
184
+ scale: "linear",
185
+ axes: { left: { ...domain, side: "left" } },
186
+ series: [{
187
+ source: { id: "a", axis: "left" } as never,
188
+ points: [],
189
+ }],
190
+ };
191
+
192
+ it("anchors a drawing to the data under the drag, not to the pixels", () => {
193
+ const scene = calendarScene(10 * DAY);
194
+ const drawing = resolveDrawingFromDrag(scene, panel, {
195
+ kind: "line",
196
+ startXRatio: 0.2,
197
+ startYRatio: 0.25,
198
+ endXRatio: 0.8,
199
+ endYRatio: 0.75,
200
+ path: [],
201
+ }, "#ffcc00", "d1");
202
+
203
+ expect(drawing?.points[0]?.time).toBe(START + 2 * DAY);
204
+ expect(drawing?.points[1]?.time).toBe(START + 8 * DAY);
205
+ // yRatio runs top-down, so 0.25 is three quarters up a 0..200 axis.
206
+ expect(drawing?.points[0]?.value).toBeCloseTo(150, 6);
207
+ expect(drawing?.points[1]?.value).toBeCloseTo(50, 6);
208
+ });
209
+
210
+ it("redraws a stored line through the current viewport", () => {
211
+ const scene = calendarScene(10 * DAY);
212
+ const drawing = resolveDrawingFromDrag(scene, panel, {
213
+ kind: "line",
214
+ startXRatio: 0,
215
+ startYRatio: 0.5,
216
+ endXRatio: 1,
217
+ endYRatio: 0.5,
218
+ path: [],
219
+ }, "#ffcc00", "d1")!;
220
+ const base = {
221
+ width: 21,
222
+ height: 11,
223
+ pixels: new Uint8Array(21 * 11 * 4),
224
+ };
225
+ const painted = drawChartToolOverlay(base, null, {
226
+ positive: "#00ff00",
227
+ negative: "#ff0000",
228
+ zoom: "#8888ff",
229
+ draw: "#ffcc00",
230
+ }, "up", { scene, panel, items: [drawing] });
231
+ const alphaAt = (x: number, y: number) => painted.pixels[(y * 21 + x) * 4 + 3]!;
232
+
233
+ // A flat line at the middle value paints the middle row and nothing else.
234
+ expect(alphaAt(10, 5)).toBeGreaterThan(0);
235
+ expect(alphaAt(10, 0)).toBe(0);
236
+
237
+ // Halving the visible span pushes the same anchors off both edges.
238
+ const zoomed: CompositeChartScene = {
239
+ ...scene,
240
+ startTime: START + 2 * DAY,
241
+ endTime: START + 7 * DAY,
242
+ timeScale: { kind: "calendar", startTime: START + 2 * DAY, endTime: START + 7 * DAY },
243
+ };
244
+ const rezoomed = drawChartToolOverlay(base, null, {
245
+ positive: "#00ff00",
246
+ negative: "#ff0000",
247
+ zoom: "#8888ff",
248
+ draw: "#ffcc00",
249
+ }, "up", { scene: zoomed, panel, items: [drawing] });
250
+ expect(rezoomed.pixels[(5 * 21 + 10) * 4 + 3]!).toBeGreaterThan(0);
251
+ });
252
+ });
253
+
254
+ describe("editing drawings", () => {
255
+ const panel: CompositePanelScene = {
256
+ id: "main",
257
+ height: 10,
258
+ scale: "linear",
259
+ axes: { left: { ...domain, side: "left" } },
260
+ series: [{ source: { id: "a", axis: "left" } as never, points: [] }],
261
+ };
262
+ const scene = calendarScene(10 * DAY);
263
+ const line = {
264
+ id: "line-1",
265
+ panelId: "main",
266
+ color: CHART_DRAWING_COLORS[0],
267
+ points: [
268
+ { time: START + 2 * DAY, value: 150 },
269
+ { time: START + 8 * DAY, value: 50 },
270
+ ],
271
+ };
272
+
273
+ it("grabs an endpoint before the body, and misses outside the slack", () => {
274
+ expect(hitTestDrawings([line], scene, panel, { xRatio: 0.2, yRatio: 0.25 }))
275
+ .toMatchObject({ pointIndex: 0 });
276
+ expect(hitTestDrawings([line], scene, panel, { xRatio: 0.5, yRatio: 0.5 }))
277
+ .toMatchObject({ pointIndex: null });
278
+ expect(hitTestDrawings([line], scene, panel, { xRatio: 0.5, yRatio: 0.9 })).toBeNull();
279
+ });
280
+
281
+ it("measures grab slack in pixels, not in ratios", () => {
282
+ // On a 4:1 plot the same ratio is four times fewer pixels sideways, so the
283
+ // sideways slack has to shrink with it or the grab is uneven.
284
+ const near = { xRatio: 0.2 + 0.005, yRatio: 0.25 };
285
+ const far = { xRatio: 0.2 + 0.02, yRatio: 0.25 };
286
+ expect(hitTestDrawings([line], scene, panel, near, 4)).toMatchObject({ pointIndex: 0 });
287
+ expect(hitTestDrawings([line], scene, panel, far, 4)?.pointIndex ?? "none").not.toBe(0);
288
+ // Vertical slack is unchanged by the aspect.
289
+ expect(hitTestDrawings([line], scene, panel, { xRatio: 0.2, yRatio: 0.27 }, 4))
290
+ .toMatchObject({ pointIndex: 0 });
291
+ });
292
+
293
+ it("leaves drawings that belong to another panel alone", () => {
294
+ const other = { ...line, panelId: "volume" };
295
+ expect(hitTestDrawings([other], scene, panel, { xRatio: 0.2, yRatio: 0.25 })).toBeNull();
296
+ });
297
+
298
+ it("moves a whole shape, or just the grabbed end", () => {
299
+ const moved = shiftDrawing(line, scene, panel, 0.1, 0, null);
300
+ expect(moved.points[0]!.time).toBe(START + 3 * DAY);
301
+ expect(moved.points[1]!.time).toBe(START + 9 * DAY);
302
+
303
+ const reshaped = shiftDrawing(line, scene, panel, 0, 0.25, 1);
304
+ expect(reshaped.points[0]!.value).toBeCloseTo(150, 6);
305
+ expect(reshaped.points[1]!.value).toBeCloseTo(0, 6);
306
+ });
307
+
308
+ it("cycles colours and wraps", () => {
309
+ expect(nextDrawingColor(CHART_DRAWING_COLORS[0])).toBe(CHART_DRAWING_COLORS[1]);
310
+ expect(nextDrawingColor(CHART_DRAWING_COLORS.at(-1)!)).toBe(CHART_DRAWING_COLORS[0]);
311
+ expect(nextDrawingColor("#000000")).toBe(CHART_DRAWING_COLORS[0]);
312
+ });
313
+ });
314
+
315
+ describe("vector overlays", () => {
316
+ const panel: CompositePanelScene = {
317
+ id: "main",
318
+ height: 10,
319
+ scale: "linear",
320
+ axes: { left: { ...domain, side: "left" } },
321
+ series: [{ source: { id: "a", axis: "left" } as never, points: [] }],
322
+ };
323
+ const scene = calendarScene(10 * DAY);
324
+ const colors = { positive: "#0f0", negative: "#f00", zoom: "#00f", draw: "#ff0" };
325
+
326
+ it("spans the full height for a zoom selection and boxes a measurement", () => {
327
+ const zoom = buildChartToolVectors({
328
+ scene,
329
+ panel,
330
+ drawings: [],
331
+ selectedId: null,
332
+ colors,
333
+ direction: "up",
334
+ drag: { kind: "zoom", startXRatio: 0.2, startYRatio: 0.4, endXRatio: 0.6, endYRatio: 0.9, path: [] },
335
+ });
336
+ expect(zoom[0]).toMatchObject({ box: true, points: [{ x: 0.2, y: 0 }, { x: 0.6, y: 1 }] });
337
+
338
+ const measure = buildChartToolVectors({
339
+ scene,
340
+ panel,
341
+ drawings: [],
342
+ selectedId: null,
343
+ colors,
344
+ direction: "down",
345
+ drag: { kind: "measure", startXRatio: 0.2, startYRatio: 0.4, endXRatio: 0.6, endYRatio: 0.9, path: [] },
346
+ });
347
+ expect(measure[0]).toMatchObject({ box: true, color: "#f00" });
348
+ expect(measure[1]!.points).toEqual([{ x: 0.2, y: 0.4 }, { x: 0.6, y: 0.9 }]);
349
+ });
350
+
351
+ it("gives handles to the selected line only", () => {
352
+ const line = {
353
+ id: "line-1",
354
+ panelId: "main",
355
+ color: "#ff0",
356
+ points: [
357
+ { time: START + 2 * DAY, value: 150 },
358
+ { time: START + 8 * DAY, value: 50 },
359
+ ],
360
+ };
361
+ const [unselected] = buildChartToolVectors({
362
+ scene, panel, drawings: [line], selectedId: null, colors, direction: "up", drag: null,
363
+ });
364
+ const [selected] = buildChartToolVectors({
365
+ scene, panel, drawings: [line], selectedId: "line-1", colors, direction: "up", drag: null,
366
+ });
367
+ expect(unselected?.handles).toBe(false);
368
+ expect(selected?.handles).toBe(true);
369
+ expect(selected?.points).toEqual([{ x: 0.2, y: 0.25 }, { x: 0.8, y: 0.75 }]);
370
+ });
371
+ });