@baron1996/klinecharts-adapter 0.1.1 → 0.2.2
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/adapter.d.ts +40 -1
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +488 -21
- package/dist/conversion/overlays.d.ts +3 -0
- package/dist/conversion/overlays.d.ts.map +1 -1
- package/dist/conversion/overlays.js +2 -0
- package/dist/conversion/panes.d.ts +3 -1
- package/dist/conversion/panes.d.ts.map +1 -1
- package/dist/conversion/panes.js +7 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +3 -3
- package/dist/extensions/price-measurement.d.ts +13 -0
- package/dist/extensions/price-measurement.d.ts.map +1 -0
- package/dist/extensions/price-measurement.js +64 -0
- package/dist/extensions/register.d.ts.map +1 -1
- package/dist/extensions/register.js +2 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/interaction/dragging.d.ts +12 -0
- package/dist/interaction/dragging.d.ts.map +1 -0
- package/dist/interaction/dragging.js +71 -0
- package/dist/interaction/hit-testing.d.ts +26 -0
- package/dist/interaction/hit-testing.d.ts.map +1 -0
- package/dist/interaction/hit-testing.js +86 -0
- package/dist/interaction/selection-arbitration.d.ts +6 -0
- package/dist/interaction/selection-arbitration.d.ts.map +1 -0
- package/dist/interaction/selection-arbitration.js +10 -0
- package/dist/registry/overlays.d.ts +2 -2
- package/dist/registry/overlays.d.ts.map +1 -1
- package/dist/registry/overlays.js +1 -0
- package/dist/version.d.ts +3 -2
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +3 -2
- package/package.json +2 -2
package/dist/adapter.js
CHANGED
|
@@ -2,9 +2,29 @@ import { parseChartScene, SceneError, } from '@baron1996/kline-scene-schema';
|
|
|
2
2
|
import { createEngine } from './engine.js';
|
|
3
3
|
import { registerProjectOverlays } from './extensions/register.js';
|
|
4
4
|
import { createEngineIdMap, } from './conversion/id-map.js';
|
|
5
|
-
import { applyPanes } from './conversion/panes.js';
|
|
5
|
+
import { applyPanes, overrideSceneYAxis } from './conversion/panes.js';
|
|
6
6
|
import { createSceneOverlays, fromEngineOverlay, toEngineOverlay, toEngineOverlayDrawing, } from './conversion/overlays.js';
|
|
7
|
+
import { normalizePriceValue } from './conversion/price.js';
|
|
7
8
|
import { applyViewport } from './conversion/viewport.js';
|
|
9
|
+
import { createDragCandidate, } from './interaction/dragging.js';
|
|
10
|
+
import { hitTestOverlayGeometries, } from './interaction/hit-testing.js';
|
|
11
|
+
import { shouldIgnoreStaleOverlayDeselection } from './interaction/selection-arbitration.js';
|
|
12
|
+
function promoteSceneToM2(scene, scale) {
|
|
13
|
+
const candidate = structuredClone(scene);
|
|
14
|
+
candidate.runtime.runtimeVersion = '0.2.0';
|
|
15
|
+
for (const pane of candidate.panes) {
|
|
16
|
+
for (const axis of pane.yAxes) {
|
|
17
|
+
axis.scale =
|
|
18
|
+
pane.kind === 'candle' && axis.role === 'primary' && scale !== undefined
|
|
19
|
+
? scale
|
|
20
|
+
: axis.scale ?? 'linear';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return candidate;
|
|
24
|
+
}
|
|
25
|
+
function isControlledInteractionOverlay(overlay) {
|
|
26
|
+
return overlay.type === 'horizontalStraightLine' || overlay.type === 'priceMeasurement';
|
|
27
|
+
}
|
|
8
28
|
/**
|
|
9
29
|
* ChartScene 与 KLineCharts 之间的唯一边界。
|
|
10
30
|
* 引擎对象和内部 ID 永不从该类的公共接口泄露。
|
|
@@ -16,7 +36,7 @@ export class KLineChartsSceneAdapter {
|
|
|
16
36
|
#engine;
|
|
17
37
|
/** 场景 ID 与引擎内部 ID 的双向映射。 */
|
|
18
38
|
#idMap;
|
|
19
|
-
/**
|
|
39
|
+
/** 当前最后一次成功提交、可导出的规范化场景。 */
|
|
20
40
|
#scene;
|
|
21
41
|
/** 当前引擎容器。 */
|
|
22
42
|
#container;
|
|
@@ -26,6 +46,14 @@ export class KLineChartsSceneAdapter {
|
|
|
26
46
|
#disposed = false;
|
|
27
47
|
/** 仅传递纯场景数据的事件订阅者。 */
|
|
28
48
|
#listeners = new Set();
|
|
49
|
+
/** 当前选择状态,null 表示明确未选择。 */
|
|
50
|
+
#selectedOverlayId = null;
|
|
51
|
+
/** 当前受控拖动事务;progress 永不写入 #scene。 */
|
|
52
|
+
#pointerInteraction;
|
|
53
|
+
/** 当前交互式量度的首锚点;只用于补偿引擎丢弃快速第二击,不进入 Scene。 */
|
|
54
|
+
#interactivePriceMeasurement;
|
|
55
|
+
/** 确定性 opaque 交互 ID 序号。 */
|
|
56
|
+
#interactionSequence = 0;
|
|
29
57
|
constructor(container, scene, handle, idMap, originalBackground) {
|
|
30
58
|
this.#container = container;
|
|
31
59
|
this.#scene = scene;
|
|
@@ -33,6 +61,7 @@ export class KLineChartsSceneAdapter {
|
|
|
33
61
|
this.#engine = handle.module;
|
|
34
62
|
this.#idMap = idMap;
|
|
35
63
|
this.#originalBackground = originalBackground;
|
|
64
|
+
this.#installInteractionListeners();
|
|
36
65
|
}
|
|
37
66
|
static async create(container, value) {
|
|
38
67
|
const scene = parseChartScene(value);
|
|
@@ -75,6 +104,17 @@ export class KLineChartsSceneAdapter {
|
|
|
75
104
|
listener(structuredClone(event));
|
|
76
105
|
}
|
|
77
106
|
}
|
|
107
|
+
#selectOverlay(id) {
|
|
108
|
+
const previousId = this.#selectedOverlayId;
|
|
109
|
+
if (previousId === id) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
this.#selectedOverlayId = id;
|
|
113
|
+
this.#emit({ type: 'overlay-selection-changed', previousId, id });
|
|
114
|
+
if (id !== null) {
|
|
115
|
+
this.#emit({ type: 'overlay-selected', id });
|
|
116
|
+
}
|
|
117
|
+
}
|
|
78
118
|
#commitEngineOverlay(engineOverlay, source, kind) {
|
|
79
119
|
const existingIndex = this.#scene.overlays.findIndex((overlay) => overlay.id === source.id);
|
|
80
120
|
const path = existingIndex < 0 ? `/overlays/${this.#scene.overlays.length}` : `/overlays/${existingIndex}`;
|
|
@@ -99,20 +139,47 @@ export class KLineChartsSceneAdapter {
|
|
|
99
139
|
#overlayCallbacks(source, drawing = false) {
|
|
100
140
|
return {
|
|
101
141
|
onDrawEnd: ({ overlay }) => {
|
|
142
|
+
if (drawing &&
|
|
143
|
+
this.#interactivePriceMeasurement?.source.id === source.id) {
|
|
144
|
+
this.#interactivePriceMeasurement = undefined;
|
|
145
|
+
}
|
|
102
146
|
this.#safelyCommitEngineOverlay(overlay, source, drawing ? 'created' : 'updated');
|
|
103
147
|
},
|
|
148
|
+
onPressedMoveStart: ({ overlay }) => {
|
|
149
|
+
this.#selectOverlay(overlay.id);
|
|
150
|
+
},
|
|
104
151
|
onPressedMoveEnd: ({ overlay }) => {
|
|
105
|
-
|
|
152
|
+
if (!isControlledInteractionOverlay(source)) {
|
|
153
|
+
this.#safelyCommitEngineOverlay(overlay, source, 'updated');
|
|
154
|
+
}
|
|
106
155
|
},
|
|
107
156
|
onSelected: ({ overlay }) => {
|
|
108
|
-
this.#
|
|
157
|
+
this.#selectOverlay(overlay.id);
|
|
158
|
+
},
|
|
159
|
+
onDeselected: (event) => {
|
|
160
|
+
const eventX = event.x;
|
|
161
|
+
const eventY = event.y;
|
|
162
|
+
const coordinate = typeof eventX === 'number' && Number.isFinite(eventX) &&
|
|
163
|
+
typeof eventY === 'number' && Number.isFinite(eventY)
|
|
164
|
+
? { x: eventX, y: eventY }
|
|
165
|
+
: undefined;
|
|
166
|
+
if (this.#selectedOverlayId === event.overlay.id &&
|
|
167
|
+
!shouldIgnoreStaleOverlayDeselection(this.#selectedOverlayId, event.overlay.id, coordinate, this.#overlayGeometries())) {
|
|
168
|
+
this.#selectOverlay(null);
|
|
169
|
+
}
|
|
109
170
|
},
|
|
110
171
|
onRemoved: ({ overlay }) => {
|
|
172
|
+
if (this.#interactivePriceMeasurement?.source.id === overlay.id) {
|
|
173
|
+
this.#interactivePriceMeasurement = undefined;
|
|
174
|
+
}
|
|
111
175
|
if (this.#scene.overlays.some((candidate) => candidate.id === overlay.id)) {
|
|
112
176
|
this.#scene = parseChartScene({
|
|
113
177
|
...structuredClone(this.#scene),
|
|
114
178
|
overlays: this.#scene.overlays.filter((candidate) => candidate.id !== overlay.id),
|
|
115
179
|
});
|
|
180
|
+
if (this.#selectedOverlayId === overlay.id) {
|
|
181
|
+
this.#selectOverlay(null);
|
|
182
|
+
}
|
|
116
183
|
this.#emit({ type: 'overlay-removed', id: overlay.id });
|
|
117
184
|
}
|
|
118
185
|
},
|
|
@@ -130,6 +197,334 @@ export class KLineChartsSceneAdapter {
|
|
|
130
197
|
throw error;
|
|
131
198
|
}
|
|
132
199
|
}
|
|
200
|
+
#installInteractionListeners() {
|
|
201
|
+
this.#container.addEventListener('pointerdown', this.#handlePointerDown, true);
|
|
202
|
+
this.#container.addEventListener('pointermove', this.#handlePointerMove, true);
|
|
203
|
+
this.#container.addEventListener('pointerup', this.#handlePointerUp, true);
|
|
204
|
+
this.#container.addEventListener('pointercancel', this.#handlePointerCancel, true);
|
|
205
|
+
window.addEventListener('keydown', this.#handleKeyDown);
|
|
206
|
+
window.addEventListener('blur', this.#handleWindowBlur);
|
|
207
|
+
}
|
|
208
|
+
#removeInteractionListeners() {
|
|
209
|
+
this.#container.removeEventListener('pointerdown', this.#handlePointerDown, true);
|
|
210
|
+
this.#container.removeEventListener('pointermove', this.#handlePointerMove, true);
|
|
211
|
+
this.#container.removeEventListener('pointerup', this.#handlePointerUp, true);
|
|
212
|
+
this.#container.removeEventListener('pointercancel', this.#handlePointerCancel, true);
|
|
213
|
+
window.removeEventListener('keydown', this.#handleKeyDown);
|
|
214
|
+
window.removeEventListener('blur', this.#handleWindowBlur);
|
|
215
|
+
}
|
|
216
|
+
#pointerCoordinate(event) {
|
|
217
|
+
const rect = this.#container.getBoundingClientRect();
|
|
218
|
+
return { x: event.clientX - rect.left, y: event.clientY - rect.top };
|
|
219
|
+
}
|
|
220
|
+
#primaryAxisFilter(paneId) {
|
|
221
|
+
const pane = this.#scene.panes.find((candidate) => candidate.id === paneId);
|
|
222
|
+
const axis = pane?.yAxes.find((candidate) => candidate.role === 'primary');
|
|
223
|
+
const enginePaneId = this.#idMap.paneToEngine.get(paneId);
|
|
224
|
+
const engineAxisId = axis === undefined ? undefined : this.#idMap.yAxisToEngine.get(axis.id);
|
|
225
|
+
if (enginePaneId === undefined || engineAxisId === undefined) {
|
|
226
|
+
throw new SceneError('INVALID_REFERENCE', '/panes', 'Overlay Pane or primary Y-axis is unmapped.');
|
|
227
|
+
}
|
|
228
|
+
return { paneId: enginePaneId, yAxisId: engineAxisId, absolute: true };
|
|
229
|
+
}
|
|
230
|
+
#toPixel(point, paneId) {
|
|
231
|
+
const converted = this.#chart.convertToPixel(point, this.#primaryAxisFilter(paneId));
|
|
232
|
+
if (!Number.isFinite(converted.x) || !Number.isFinite(converted.y)) {
|
|
233
|
+
throw new SceneError('EXPORT_INVALID', '/overlays', 'KLineCharts returned a non-finite pixel coordinate.');
|
|
234
|
+
}
|
|
235
|
+
return { x: converted.x, y: converted.y };
|
|
236
|
+
}
|
|
237
|
+
#fromPixel(point, paneId) {
|
|
238
|
+
const converted = this.#chart.convertFromPixel([point], this.#primaryAxisFilter(paneId));
|
|
239
|
+
const value = converted[0];
|
|
240
|
+
if (!Number.isFinite(value?.dataIndex) || !Number.isFinite(value?.value)) {
|
|
241
|
+
throw new SceneError('INVALID_REFERENCE', '/overlays', 'Pointer does not map to finite chart data.');
|
|
242
|
+
}
|
|
243
|
+
return { dataIndex: value.dataIndex, value: value.value };
|
|
244
|
+
}
|
|
245
|
+
#measurementAnchor(point, paneId, path) {
|
|
246
|
+
const converted = this.#chart.convertFromPixel([point], this.#primaryAxisFilter(paneId));
|
|
247
|
+
const value = converted[0];
|
|
248
|
+
if (!Number.isSafeInteger(value?.timestamp) ||
|
|
249
|
+
!Number.isFinite(value?.value) ||
|
|
250
|
+
!this.#scene.data.some((bar) => bar.timestamp === value?.timestamp)) {
|
|
251
|
+
throw new SceneError('INVALID_REFERENCE', path, 'Pointer does not map to a finite price and an embedded market-data timestamp.');
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
timestamp: value.timestamp,
|
|
255
|
+
value: normalizePriceValue(value.value, this.#scene.symbol.pricePrecision, `${path}/value`),
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
#completeInteractivePriceMeasurement(drawing, end) {
|
|
259
|
+
const { text: _text, ...source } = drawing.source;
|
|
260
|
+
const candidate = parseChartScene({
|
|
261
|
+
...structuredClone(this.#scene),
|
|
262
|
+
overlays: [
|
|
263
|
+
...structuredClone(this.#scene.overlays),
|
|
264
|
+
{
|
|
265
|
+
...structuredClone(source),
|
|
266
|
+
start: structuredClone(drawing.start),
|
|
267
|
+
end: structuredClone(end),
|
|
268
|
+
},
|
|
269
|
+
],
|
|
270
|
+
});
|
|
271
|
+
const index = candidate.overlays.length - 1;
|
|
272
|
+
const overlay = candidate.overlays[index];
|
|
273
|
+
this.#interactivePriceMeasurement = undefined;
|
|
274
|
+
if (!this.#chart.removeOverlay({ id: overlay.id })) {
|
|
275
|
+
throw new SceneError('RUNTIME_INIT_FAILED', `/overlays/${index}`, `KLineCharts failed to replace in-progress Overlay ${overlay.id}.`);
|
|
276
|
+
}
|
|
277
|
+
const result = this.#chart.createOverlay(toEngineOverlay(overlay, this.#idMap, `/overlays/${index}`, this.#overlayCallbacks(overlay)));
|
|
278
|
+
if (result !== overlay.id) {
|
|
279
|
+
throw new SceneError('RUNTIME_INIT_FAILED', `/overlays/${index}`, `KLineCharts failed to commit interactive Overlay ${overlay.id}.`);
|
|
280
|
+
}
|
|
281
|
+
this.#scene = candidate;
|
|
282
|
+
this.#emit({ type: 'overlay-created', overlay });
|
|
283
|
+
}
|
|
284
|
+
#overlayGeometries() {
|
|
285
|
+
const geometries = [];
|
|
286
|
+
for (let sceneIndex = 0; sceneIndex < this.#scene.overlays.length; sceneIndex++) {
|
|
287
|
+
const overlay = this.#scene.overlays[sceneIndex];
|
|
288
|
+
if (overlay === undefined || !overlay.visible) {
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
if (overlay.type === 'horizontalStraightLine') {
|
|
292
|
+
const anchor = overlay.anchor;
|
|
293
|
+
if (anchor === undefined || !('value' in anchor)) {
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
const paneFilter = this.#primaryAxisFilter(overlay.paneId);
|
|
297
|
+
const paneMain = this.#chart.getDom(paneFilter.paneId, 'main');
|
|
298
|
+
const containerRect = this.#container.getBoundingClientRect();
|
|
299
|
+
const mainRect = paneMain?.getBoundingClientRect() ?? containerRect;
|
|
300
|
+
const projected = this.#toPixel({ timestamp: this.#scene.data[0].timestamp, value: anchor.value }, overlay.paneId);
|
|
301
|
+
const start = { x: mainRect.left - containerRect.left, y: projected.y };
|
|
302
|
+
const end = { x: mainRect.right - containerRect.left, y: projected.y };
|
|
303
|
+
geometries.push({
|
|
304
|
+
overlayId: overlay.id,
|
|
305
|
+
sceneIndex,
|
|
306
|
+
zLevel: overlay.zLevel,
|
|
307
|
+
locked: overlay.locked,
|
|
308
|
+
anchors: [{ x: (start.x + end.x) / 2, y: projected.y }],
|
|
309
|
+
bodySegments: [[start, end]],
|
|
310
|
+
});
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
if (overlay.type === 'priceMeasurement' && overlay.start !== undefined && overlay.end !== undefined) {
|
|
314
|
+
const start = this.#toPixel(overlay.start, overlay.paneId);
|
|
315
|
+
const end = this.#toPixel(overlay.end, overlay.paneId);
|
|
316
|
+
geometries.push({
|
|
317
|
+
overlayId: overlay.id,
|
|
318
|
+
sceneIndex,
|
|
319
|
+
zLevel: overlay.zLevel,
|
|
320
|
+
locked: overlay.locked,
|
|
321
|
+
anchors: [start, end],
|
|
322
|
+
bodySegments: [[start, end]],
|
|
323
|
+
});
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
if (overlay.points !== undefined && overlay.points.length >= 2) {
|
|
327
|
+
const anchors = overlay.points.map((point) => this.#toPixel(point, overlay.paneId));
|
|
328
|
+
const bodySegments = [];
|
|
329
|
+
for (let pointIndex = 1; pointIndex < anchors.length; pointIndex++) {
|
|
330
|
+
bodySegments.push([anchors[pointIndex - 1], anchors[pointIndex]]);
|
|
331
|
+
}
|
|
332
|
+
geometries.push({
|
|
333
|
+
overlayId: overlay.id,
|
|
334
|
+
sceneIndex,
|
|
335
|
+
zLevel: overlay.zLevel,
|
|
336
|
+
locked: overlay.locked,
|
|
337
|
+
anchors,
|
|
338
|
+
bodySegments,
|
|
339
|
+
});
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
return geometries;
|
|
343
|
+
}
|
|
344
|
+
#interactionIdentity(interaction) {
|
|
345
|
+
return {
|
|
346
|
+
interactionId: interaction.interactionId,
|
|
347
|
+
overlayId: interaction.hit.overlayId,
|
|
348
|
+
target: interaction.hit.target,
|
|
349
|
+
anchorIndex: interaction.hit.anchorIndex,
|
|
350
|
+
before: structuredClone(interaction.before),
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
#stopPointerCapture(interaction) {
|
|
354
|
+
if (this.#container.hasPointerCapture(interaction.pointerId)) {
|
|
355
|
+
this.#container.releasePointerCapture(interaction.pointerId);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
#cancelInteraction(reason, error) {
|
|
359
|
+
const interaction = this.#pointerInteraction;
|
|
360
|
+
if (interaction === undefined) {
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
this.#pointerInteraction = undefined;
|
|
364
|
+
this.#stopPointerCapture(interaction);
|
|
365
|
+
if (!interaction.started) {
|
|
366
|
+
return;
|
|
367
|
+
}
|
|
368
|
+
const index = this.#scene.overlays.findIndex((overlay) => overlay.id === interaction.before.id);
|
|
369
|
+
if (index < 0 ||
|
|
370
|
+
!this.#chart.overrideOverlay(toEngineOverlay(interaction.before, this.#idMap, `/overlays/${index}`, this.#overlayCallbacks(interaction.before)))) {
|
|
371
|
+
throw new SceneError('RUNTIME_INIT_FAILED', '/overlays', `KLineCharts failed to restore Overlay ${interaction.before.id}.`);
|
|
372
|
+
}
|
|
373
|
+
this.#emit({
|
|
374
|
+
type: 'overlay-drag-cancelled',
|
|
375
|
+
...this.#interactionIdentity(interaction),
|
|
376
|
+
reason,
|
|
377
|
+
});
|
|
378
|
+
if (reason === 'validation-error' && error !== undefined) {
|
|
379
|
+
this.#emit({ type: 'scene-error', issues: structuredClone(error.issues) });
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
#handlePointerDown = (event) => {
|
|
383
|
+
if (this.#disposed || event.button !== 0 || this.#pointerInteraction !== undefined) {
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
386
|
+
const coordinate = this.#pointerCoordinate(event);
|
|
387
|
+
const measurement = this.#interactivePriceMeasurement;
|
|
388
|
+
if (measurement !== undefined) {
|
|
389
|
+
try {
|
|
390
|
+
const point = this.#measurementAnchor(coordinate, measurement.source.paneId, measurement.start === undefined ? '/overlays/start' : '/overlays/end');
|
|
391
|
+
if (measurement.start === undefined) {
|
|
392
|
+
measurement.start = point;
|
|
393
|
+
return;
|
|
394
|
+
}
|
|
395
|
+
event.preventDefault();
|
|
396
|
+
event.stopImmediatePropagation();
|
|
397
|
+
this.#completeInteractivePriceMeasurement(measurement, point);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
catch (error) {
|
|
401
|
+
if (error instanceof SceneError) {
|
|
402
|
+
event.preventDefault();
|
|
403
|
+
event.stopImmediatePropagation();
|
|
404
|
+
this.#emit({ type: 'scene-error', issues: structuredClone(error.issues) });
|
|
405
|
+
return;
|
|
406
|
+
}
|
|
407
|
+
throw error;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
const hit = hitTestOverlayGeometries(coordinate, this.#overlayGeometries());
|
|
411
|
+
if (hit === null) {
|
|
412
|
+
const selected = this.#scene.overlays.find((overlay) => overlay.id === this.#selectedOverlayId);
|
|
413
|
+
if (selected !== undefined && isControlledInteractionOverlay(selected)) {
|
|
414
|
+
this.#selectOverlay(null);
|
|
415
|
+
}
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const before = this.#scene.overlays.find((overlay) => overlay.id === hit.overlayId);
|
|
419
|
+
if (before === undefined) {
|
|
420
|
+
return;
|
|
421
|
+
}
|
|
422
|
+
this.#selectOverlay(before.id);
|
|
423
|
+
if (!isControlledInteractionOverlay(before)) {
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
event.preventDefault();
|
|
427
|
+
event.stopImmediatePropagation();
|
|
428
|
+
if (hit.locked) {
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
this.#container.setPointerCapture(event.pointerId);
|
|
432
|
+
this.#pointerInteraction = {
|
|
433
|
+
pointerId: event.pointerId,
|
|
434
|
+
originClient: coordinate,
|
|
435
|
+
originData: this.#fromPixel(coordinate, before.paneId),
|
|
436
|
+
hit,
|
|
437
|
+
before: structuredClone(before),
|
|
438
|
+
interactionId: `interaction-${this.#interactionSequence++}`,
|
|
439
|
+
started: false,
|
|
440
|
+
};
|
|
441
|
+
};
|
|
442
|
+
#handlePointerMove = (event) => {
|
|
443
|
+
const interaction = this.#pointerInteraction;
|
|
444
|
+
if (interaction === undefined || interaction.pointerId !== event.pointerId) {
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
event.preventDefault();
|
|
448
|
+
event.stopImmediatePropagation();
|
|
449
|
+
const coordinate = this.#pointerCoordinate(event);
|
|
450
|
+
if (!interaction.started &&
|
|
451
|
+
Math.hypot(coordinate.x - interaction.originClient.x, coordinate.y - interaction.originClient.y) < 0.5) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
if (!interaction.started) {
|
|
455
|
+
interaction.started = true;
|
|
456
|
+
this.#emit({
|
|
457
|
+
type: 'overlay-drag-started',
|
|
458
|
+
...this.#interactionIdentity(interaction),
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
try {
|
|
462
|
+
const candidate = createDragCandidate(interaction.before, interaction.hit, interaction.originData, this.#fromPixel(coordinate, interaction.before.paneId), this.#scene.data.map((bar) => bar.timestamp), this.#scene.symbol.pricePrecision);
|
|
463
|
+
const index = this.#scene.overlays.findIndex((overlay) => overlay.id === candidate.id);
|
|
464
|
+
const overlays = structuredClone(this.#scene.overlays);
|
|
465
|
+
overlays[index] = candidate;
|
|
466
|
+
const parsed = parseChartScene({ ...structuredClone(this.#scene), overlays });
|
|
467
|
+
const normalized = parsed.overlays[index];
|
|
468
|
+
if (!this.#chart.overrideOverlay(toEngineOverlay(normalized, this.#idMap, `/overlays/${index}`, this.#overlayCallbacks(normalized)))) {
|
|
469
|
+
throw new SceneError('RUNTIME_INIT_FAILED', `/overlays/${index}`, `KLineCharts failed to preview Overlay ${normalized.id}.`);
|
|
470
|
+
}
|
|
471
|
+
interaction.candidate = normalized;
|
|
472
|
+
this.#emit({
|
|
473
|
+
type: 'overlay-dragging',
|
|
474
|
+
...this.#interactionIdentity(interaction),
|
|
475
|
+
candidate: normalized,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
catch (error) {
|
|
479
|
+
if (error instanceof SceneError) {
|
|
480
|
+
this.#cancelInteraction('validation-error', error);
|
|
481
|
+
return;
|
|
482
|
+
}
|
|
483
|
+
throw error;
|
|
484
|
+
}
|
|
485
|
+
};
|
|
486
|
+
#handlePointerUp = (event) => {
|
|
487
|
+
const interaction = this.#pointerInteraction;
|
|
488
|
+
if (interaction === undefined || interaction.pointerId !== event.pointerId) {
|
|
489
|
+
return;
|
|
490
|
+
}
|
|
491
|
+
event.preventDefault();
|
|
492
|
+
event.stopImmediatePropagation();
|
|
493
|
+
this.#pointerInteraction = undefined;
|
|
494
|
+
this.#stopPointerCapture(interaction);
|
|
495
|
+
if (!interaction.started) {
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
const overlay = interaction.candidate ?? interaction.before;
|
|
499
|
+
const index = this.#scene.overlays.findIndex((candidate) => candidate.id === overlay.id);
|
|
500
|
+
const overlays = structuredClone(this.#scene.overlays);
|
|
501
|
+
overlays[index] = overlay;
|
|
502
|
+
this.#scene = parseChartScene({ ...structuredClone(this.#scene), overlays });
|
|
503
|
+
const committed = this.#scene.overlays[index];
|
|
504
|
+
this.#emit({
|
|
505
|
+
type: 'overlay-drag-committed',
|
|
506
|
+
...this.#interactionIdentity(interaction),
|
|
507
|
+
overlay: committed,
|
|
508
|
+
});
|
|
509
|
+
this.#emit({ type: 'overlay-updated', overlay: committed });
|
|
510
|
+
};
|
|
511
|
+
#handlePointerCancel = (event) => {
|
|
512
|
+
if (this.#pointerInteraction?.pointerId === event.pointerId) {
|
|
513
|
+
event.preventDefault();
|
|
514
|
+
event.stopImmediatePropagation();
|
|
515
|
+
this.#cancelInteraction('pointer-cancel');
|
|
516
|
+
}
|
|
517
|
+
};
|
|
518
|
+
#handleKeyDown = (event) => {
|
|
519
|
+
if (event.key === 'Escape' && this.#pointerInteraction !== undefined) {
|
|
520
|
+
this.#cancelInteraction('escape');
|
|
521
|
+
}
|
|
522
|
+
};
|
|
523
|
+
#handleWindowBlur = () => {
|
|
524
|
+
if (this.#pointerInteraction !== undefined) {
|
|
525
|
+
this.#cancelInteraction('window-blur');
|
|
526
|
+
}
|
|
527
|
+
};
|
|
133
528
|
subscribe(listener) {
|
|
134
529
|
this.#assertActive();
|
|
135
530
|
this.#listeners.add(listener);
|
|
@@ -139,24 +534,60 @@ export class KLineChartsSceneAdapter {
|
|
|
139
534
|
}
|
|
140
535
|
exportScene() {
|
|
141
536
|
this.#assertActive();
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
throw new SceneError('EXPORT_INVALID', `/overlays/${index}`, `KLineCharts lost Overlay ${source.id}.`);
|
|
537
|
+
for (let index = 0; index < this.#scene.overlays.length; index++) {
|
|
538
|
+
const overlay = this.#scene.overlays[index];
|
|
539
|
+
if (!this.#engineOverlays().some((engine) => engine.id === overlay.id)) {
|
|
540
|
+
throw new SceneError('EXPORT_INVALID', `/overlays/${index}`, `KLineCharts lost Overlay ${overlay.id}.`);
|
|
147
541
|
}
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
542
|
+
}
|
|
543
|
+
return parseChartScene(structuredClone(this.#scene));
|
|
544
|
+
}
|
|
545
|
+
async setPriceScale(scale) {
|
|
546
|
+
this.#assertActive();
|
|
547
|
+
const candidate = parseChartScene(promoteSceneToM2(this.#scene, scale));
|
|
548
|
+
const paneIndex = candidate.panes.findIndex((pane) => pane.kind === 'candle');
|
|
549
|
+
const pane = candidate.panes[paneIndex];
|
|
550
|
+
const axisIndex = pane.yAxes.findIndex((axis) => axis.role === 'primary');
|
|
551
|
+
const axis = pane.yAxes[axisIndex];
|
|
552
|
+
const previousPane = this.#scene.panes[paneIndex];
|
|
553
|
+
const previousAxis = previousPane.yAxes[axisIndex];
|
|
554
|
+
const path = `/panes/${paneIndex}/yAxes/${axisIndex}`;
|
|
555
|
+
try {
|
|
556
|
+
overrideSceneYAxis(this.#chart, this.#idMap, axis, pane.id, path);
|
|
557
|
+
// KLineCharts batches Y-axis recreation in a microtask; await that formal
|
|
558
|
+
// layout boundary before making the upgraded Scene externally visible.
|
|
559
|
+
await Promise.resolve();
|
|
560
|
+
const reference = candidate.data[0];
|
|
561
|
+
this.#toPixel({ timestamp: reference.timestamp, value: reference.close }, pane.id);
|
|
562
|
+
}
|
|
563
|
+
catch (error) {
|
|
564
|
+
overrideSceneYAxis(this.#chart, this.#idMap, previousAxis, previousPane.id, path);
|
|
565
|
+
await Promise.resolve();
|
|
566
|
+
if (error instanceof SceneError) {
|
|
567
|
+
throw error;
|
|
568
|
+
}
|
|
569
|
+
throw new SceneError('RUNTIME_INIT_FAILED', `${path}/scale`, 'KLineCharts failed to apply the requested price scale atomically.');
|
|
570
|
+
}
|
|
571
|
+
this.#scene = candidate;
|
|
572
|
+
return structuredClone(candidate);
|
|
573
|
+
}
|
|
574
|
+
projectPoint(point, paneId) {
|
|
575
|
+
this.#assertActive();
|
|
576
|
+
const targetPane = paneId ?? this.#scene.panes.find((pane) => pane.kind === 'candle').id;
|
|
577
|
+
return this.#toPixel(point, targetPane);
|
|
578
|
+
}
|
|
579
|
+
hitTestOverlay(point) {
|
|
580
|
+
this.#assertActive();
|
|
581
|
+
return hitTestOverlayGeometries(point, this.#overlayGeometries());
|
|
154
582
|
}
|
|
155
583
|
addOverlay(value) {
|
|
156
584
|
this.#assertActive();
|
|
585
|
+
const baseScene = value.type === 'priceMeasurement'
|
|
586
|
+
? promoteSceneToM2(this.#scene)
|
|
587
|
+
: structuredClone(this.#scene);
|
|
157
588
|
const candidate = parseChartScene({
|
|
158
|
-
...
|
|
159
|
-
overlays: [...
|
|
589
|
+
...baseScene,
|
|
590
|
+
overlays: [...baseScene.overlays, structuredClone(value)],
|
|
160
591
|
});
|
|
161
592
|
const index = candidate.overlays.length - 1;
|
|
162
593
|
const overlay = candidate.overlays[index];
|
|
@@ -168,12 +599,13 @@ export class KLineChartsSceneAdapter {
|
|
|
168
599
|
this.#emit({ type: 'overlay-created', overlay });
|
|
169
600
|
return structuredClone(overlay);
|
|
170
601
|
}
|
|
171
|
-
updateOverlay(value) {
|
|
602
|
+
#updateOverlay(value, styleChange) {
|
|
172
603
|
this.#assertActive();
|
|
173
604
|
const index = this.#scene.overlays.findIndex((overlay) => overlay.id === value.id);
|
|
174
605
|
if (index < 0) {
|
|
175
606
|
throw new SceneError('INVALID_REFERENCE', '/overlays', `Overlay ${value.id} does not exist.`);
|
|
176
607
|
}
|
|
608
|
+
const before = structuredClone(this.#scene.overlays[index]);
|
|
177
609
|
const overlays = structuredClone(this.#scene.overlays);
|
|
178
610
|
overlays[index] = structuredClone(value);
|
|
179
611
|
const candidate = parseChartScene({
|
|
@@ -181,13 +613,26 @@ export class KLineChartsSceneAdapter {
|
|
|
181
613
|
overlays,
|
|
182
614
|
});
|
|
183
615
|
const overlay = candidate.overlays[index];
|
|
184
|
-
if (!this.#chart.overrideOverlay(toEngineOverlay(overlay, this.#idMap, `/overlays/${index}
|
|
616
|
+
if (!this.#chart.overrideOverlay(toEngineOverlay(overlay, this.#idMap, `/overlays/${index}`, this.#overlayCallbacks(overlay)))) {
|
|
185
617
|
throw new SceneError('RUNTIME_INIT_FAILED', `/overlays/${index}`, `KLineCharts failed to update Overlay ${overlay.id}.`);
|
|
186
618
|
}
|
|
187
619
|
this.#scene = candidate;
|
|
620
|
+
if (styleChange) {
|
|
621
|
+
this.#emit({ type: 'overlay-style-changed', before, overlay });
|
|
622
|
+
}
|
|
188
623
|
this.#emit({ type: 'overlay-updated', overlay });
|
|
189
624
|
return structuredClone(overlay);
|
|
190
625
|
}
|
|
626
|
+
updateOverlay(value) {
|
|
627
|
+
return this.#updateOverlay(value, false);
|
|
628
|
+
}
|
|
629
|
+
updateOverlayStyles(id, styles) {
|
|
630
|
+
const overlay = this.getOverlay(id);
|
|
631
|
+
if (overlay === undefined) {
|
|
632
|
+
throw new SceneError('INVALID_REFERENCE', '/overlays', `Overlay ${id} does not exist.`);
|
|
633
|
+
}
|
|
634
|
+
return this.#updateOverlay({ ...overlay, styles: structuredClone(styles) }, true);
|
|
635
|
+
}
|
|
191
636
|
removeOverlay(id) {
|
|
192
637
|
this.#assertActive();
|
|
193
638
|
const index = this.#scene.overlays.findIndex((overlay) => overlay.id === id);
|
|
@@ -202,6 +647,9 @@ export class KLineChartsSceneAdapter {
|
|
|
202
647
|
...structuredClone(this.#scene),
|
|
203
648
|
overlays: this.#scene.overlays.filter((overlay) => overlay.id !== id),
|
|
204
649
|
});
|
|
650
|
+
if (this.#selectedOverlayId === id) {
|
|
651
|
+
this.#selectOverlay(null);
|
|
652
|
+
}
|
|
205
653
|
this.#emit({ type: 'overlay-removed', id });
|
|
206
654
|
}
|
|
207
655
|
return true;
|
|
@@ -212,17 +660,32 @@ export class KLineChartsSceneAdapter {
|
|
|
212
660
|
this.#engineOverlays().some((overlay) => overlay.id === request.id)) {
|
|
213
661
|
throw new SceneError('DUPLICATE_ID', '/overlays/id', `Overlay ${request.id} already exists.`);
|
|
214
662
|
}
|
|
663
|
+
const candidate = request.type === 'priceMeasurement'
|
|
664
|
+
? parseChartScene(promoteSceneToM2(this.#scene))
|
|
665
|
+
: this.#scene;
|
|
215
666
|
const result = this.#chart.createOverlay(toEngineOverlayDrawing(structuredClone(request), this.#idMap, this.#overlayCallbacks(structuredClone(request), true)));
|
|
216
667
|
if (result !== request.id) {
|
|
217
668
|
throw new SceneError('RUNTIME_INIT_FAILED', '/overlays', `KLineCharts failed to start drawing Overlay ${request.id}.`);
|
|
218
669
|
}
|
|
670
|
+
this.#scene = candidate;
|
|
671
|
+
if (request.type === 'priceMeasurement') {
|
|
672
|
+
this.#interactivePriceMeasurement = {
|
|
673
|
+
source: {
|
|
674
|
+
...structuredClone(request),
|
|
675
|
+
type: 'priceMeasurement',
|
|
676
|
+
},
|
|
677
|
+
};
|
|
678
|
+
}
|
|
219
679
|
return request.id;
|
|
220
680
|
}
|
|
221
681
|
getOverlay(id) {
|
|
222
|
-
|
|
682
|
+
this.#assertActive();
|
|
683
|
+
const overlay = this.#scene.overlays.find((candidate) => candidate.id === id);
|
|
684
|
+
return overlay === undefined ? undefined : structuredClone(overlay);
|
|
223
685
|
}
|
|
224
686
|
listOverlays() {
|
|
225
|
-
|
|
687
|
+
this.#assertActive();
|
|
688
|
+
return structuredClone(this.#scene.overlays);
|
|
226
689
|
}
|
|
227
690
|
inspect() {
|
|
228
691
|
this.#assertActive();
|
|
@@ -241,6 +704,7 @@ export class KLineChartsSceneAdapter {
|
|
|
241
704
|
});
|
|
242
705
|
return {
|
|
243
706
|
engineVersion: this.#engine.version(),
|
|
707
|
+
runtimeVersion: this.#scene.runtime.runtimeVersion,
|
|
244
708
|
dataCount: this.#chart.getDataList().length,
|
|
245
709
|
paneIds: this.#scene.panes.map((pane) => pane.id),
|
|
246
710
|
indicators,
|
|
@@ -253,7 +717,10 @@ export class KLineChartsSceneAdapter {
|
|
|
253
717
|
if (this.#disposed) {
|
|
254
718
|
return;
|
|
255
719
|
}
|
|
720
|
+
this.#cancelInteraction('destroy');
|
|
721
|
+
this.#interactivePriceMeasurement = undefined;
|
|
256
722
|
this.#disposed = true;
|
|
723
|
+
this.#removeInteractionListeners();
|
|
257
724
|
this.#listeners.clear();
|
|
258
725
|
this.#engine.dispose(this.#container);
|
|
259
726
|
this.#container.replaceChildren();
|
|
@@ -18,9 +18,12 @@ export interface OverlayDrawingSource extends OverlaySourceSnapshot {
|
|
|
18
18
|
}
|
|
19
19
|
export interface EngineOverlayCallbacks {
|
|
20
20
|
readonly onDrawEnd?: NonNullable<OverlayCreate['onDrawEnd']>;
|
|
21
|
+
readonly onPressedMoveStart?: NonNullable<OverlayCreate['onPressedMoveStart']>;
|
|
22
|
+
readonly onPressedMoving?: NonNullable<OverlayCreate['onPressedMoving']>;
|
|
21
23
|
readonly onPressedMoveEnd?: NonNullable<OverlayCreate['onPressedMoveEnd']>;
|
|
22
24
|
readonly onRemoved?: NonNullable<OverlayCreate['onRemoved']>;
|
|
23
25
|
readonly onSelected?: NonNullable<OverlayCreate['onSelected']>;
|
|
26
|
+
readonly onDeselected?: NonNullable<OverlayCreate['onDeselected']>;
|
|
24
27
|
}
|
|
25
28
|
export declare function toEngineOverlay(overlay: SceneOverlay, idMap: EngineIdMap, path: string, callbacks?: EngineOverlayCallbacks): OverlayCreate;
|
|
26
29
|
/** 创建尚无几何点的交互式 Overlay,不把临时状态写入 Scene。 */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"overlays.d.ts","sourceRoot":"","sources":["../../src/conversion/overlays.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,UAAU,EACV,YAAY,EACZ,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAEX,OAAO,EACP,aAAa,EAGb,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO/C,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;IACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC,UAAU,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,oBAAqB,SAAQ,qBAAqB;IAClE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,SAAS,CAAC,EAAE,WAAW,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC;IAC7D,QAAQ,CAAC,gBAAgB,CAAC,EAAE,WAAW,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,WAAW,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC;IAC7D,QAAQ,CAAC,UAAU,CAAC,EAAE,WAAW,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"overlays.d.ts","sourceRoot":"","sources":["../../src/conversion/overlays.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,UAAU,EACV,YAAY,EACZ,MAAM,+BAA+B,CAAC;AAEvC,OAAO,KAAK,EAEX,OAAO,EACP,aAAa,EAGb,MAAM,aAAa,CAAC;AAErB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAO/C,MAAM,WAAW,qBAAqB;IACrC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,YAAY,CAAC,QAAQ,CAAC,CAAC;IACxC,QAAQ,CAAC,QAAQ,CAAC,EAAE,YAAY,CAAC,UAAU,CAAC,CAAC;CAC7C;AAED,MAAM,WAAW,oBAAqB,SAAQ,qBAAqB;IAClE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,sBAAsB;IACtC,QAAQ,CAAC,SAAS,CAAC,EAAE,WAAW,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC;IAC7D,QAAQ,CAAC,kBAAkB,CAAC,EAAE,WAAW,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC,CAAC;IAC/E,QAAQ,CAAC,eAAe,CAAC,EAAE,WAAW,CAAC,aAAa,CAAC,iBAAiB,CAAC,CAAC,CAAC;IACzE,QAAQ,CAAC,gBAAgB,CAAC,EAAE,WAAW,CAAC,aAAa,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAC3E,QAAQ,CAAC,SAAS,CAAC,EAAE,WAAW,CAAC,aAAa,CAAC,WAAW,CAAC,CAAC,CAAC;IAC7D,QAAQ,CAAC,UAAU,CAAC,EAAE,WAAW,CAAC,aAAa,CAAC,YAAY,CAAC,CAAC,CAAC;IAC/D,QAAQ,CAAC,YAAY,CAAC,EAAE,WAAW,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC;CACnE;AAoGD,wBAAgB,eAAe,CAC9B,OAAO,EAAE,YAAY,EACrB,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,EACZ,SAAS,GAAE,sBAA2B,GACpC,aAAa,CAwBf;AAED,0CAA0C;AAC1C,wBAAgB,sBAAsB,CACrC,MAAM,EAAE,oBAAoB,EAC5B,KAAK,EAAE,WAAW,EAClB,SAAS,EAAE,sBAAsB,GAC/B,aAAa,CAsBf;AAiED,wBAAgB,iBAAiB,CAChC,MAAM,EAAE,OAAO,EACf,MAAM,EAAE,qBAAqB,EAC7B,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,MAAM,EACZ,cAAc,EAAE,MAAM,GACpB,YAAY,CAkHd;AAED,qBAAqB;AACrB,wBAAgB,mBAAmB,CAClC,KAAK,EAAE,UAAU,EACjB,KAAK,EAAE;IAAE,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,GAAG,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,CAAA;CAAE,EACpF,KAAK,EAAE,WAAW,EAClB,SAAS,CAAC,EAAE,CAAC,OAAO,EAAE,YAAY,KAAK,sBAAsB,GAC3D,IAAI,CAiBN"}
|