@baron1996/klinecharts-adapter 0.9.3 → 0.9.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapter.d.ts +3 -1
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +466 -57
- package/dist/conversion/chart-options.d.ts +2 -2
- package/dist/conversion/chart-options.d.ts.map +1 -1
- package/dist/conversion/chart-options.js +15 -15
- package/dist/drawing/engine-port.d.ts +15 -0
- package/dist/drawing/engine-port.d.ts.map +1 -1
- package/dist/engine.d.ts +13 -1
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +26 -2
- package/dist/interaction/dragging.d.ts.map +1 -1
- package/dist/interaction/dragging.js +149 -44
- package/dist/interaction/hit-testing.d.ts +16 -1
- package/dist/interaction/hit-testing.d.ts.map +1 -1
- package/dist/interaction/hit-testing.js +25 -4
- package/dist/interaction/overlay-geometry.d.ts +15 -0
- package/dist/interaction/overlay-geometry.d.ts.map +1 -0
- package/dist/interaction/overlay-geometry.js +228 -0
- package/dist/interaction/touch-precision-drawing.d.ts +29 -0
- package/dist/interaction/touch-precision-drawing.d.ts.map +1 -0
- package/dist/interaction/touch-precision-drawing.js +273 -0
- package/dist/time-series/adapter.d.ts +3 -1
- package/dist/time-series/adapter.d.ts.map +1 -1
- package/dist/time-series/adapter.js +2 -2
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/adapter.js
CHANGED
|
@@ -12,8 +12,10 @@ import { toOverlayStyles } from './conversion/overlays.js';
|
|
|
12
12
|
import { drawingToSceneOverlay, sceneOverlayToDrawing, } from './drawing/overlay-conversion.js';
|
|
13
13
|
import { MainSeriesPresentationError, presentationToSceneCandle, STANDARD_CLOSE_LINE_PRESENTATION, } from './main-series-presentation.js';
|
|
14
14
|
import { createDragCandidate, } from './interaction/dragging.js';
|
|
15
|
-
import { hitTestOverlayGeometries, } from './interaction/hit-testing.js';
|
|
15
|
+
import { DEFAULT_OVERLAY_MOUSE_HIT_TOLERANCE, DEFAULT_OVERLAY_TOUCH_HIT_TOLERANCE, hitTestOverlayGeometries, } from './interaction/hit-testing.js';
|
|
16
|
+
import { projectOverlayGeometry } from './interaction/overlay-geometry.js';
|
|
16
17
|
import { shouldIgnoreStaleOverlayDeselection } from './interaction/selection-arbitration.js';
|
|
18
|
+
import { isTouchPrecisionTap, resolveTouchPrecisionCursor, TouchPrecisionDrawingGuide, } from './interaction/touch-precision-drawing.js';
|
|
17
19
|
function promoteSceneToM2(scene, scale) {
|
|
18
20
|
const candidate = structuredClone(scene);
|
|
19
21
|
candidate.runtime.runtimeVersion = '0.2.0';
|
|
@@ -41,12 +43,19 @@ export class KLineChartsSceneAdapter {
|
|
|
41
43
|
#engine;
|
|
42
44
|
/** 引擎点击仲裁显式复位;每次绘制开始前调用,保证新绘制首击独立成单击。 */
|
|
43
45
|
#resetClickArbitration;
|
|
46
|
+
/** 向引擎派发鼠标移动语义,供触摸虚拟光标复用原生绘制预览。 */
|
|
47
|
+
#dispatchEngineMouseMove;
|
|
48
|
+
/** 向引擎派发鼠标单击语义,供触摸轻点复用原生绘制落点。 */
|
|
49
|
+
#dispatchEngineMouseClick;
|
|
44
50
|
/** 引擎十字线动作监听器集合。 */
|
|
45
51
|
#crosshairListeners = new Set();
|
|
46
52
|
/** 引擎十字线动作退订函数。 */
|
|
47
53
|
#unsubscribeCrosshair = null;
|
|
48
54
|
/** 引擎十字线动作的绑定处理函数。 */
|
|
49
55
|
#handleCrosshairChange = (payload) => {
|
|
56
|
+
if (this.#chartInteractionSnapshot !== undefined) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
50
59
|
const event = (typeof payload === 'object' && payload !== null
|
|
51
60
|
? payload
|
|
52
61
|
: {});
|
|
@@ -102,12 +111,26 @@ export class KLineChartsSceneAdapter {
|
|
|
102
111
|
#listeners = new Set();
|
|
103
112
|
/** 当前选择状态,null 表示明确未选择。 */
|
|
104
113
|
#selectedOverlayId = null;
|
|
114
|
+
/** 独占选择前的图表交互状态,用于无损恢复宿主原配置。 */
|
|
115
|
+
#chartInteractionSnapshot;
|
|
116
|
+
/** 点击空白退出编辑时被完整消费的 Pointer ID。 */
|
|
117
|
+
#deselectingPointerId;
|
|
105
118
|
/** 当前受控拖动事务;progress 永不写入 #scene。 */
|
|
106
119
|
#pointerInteraction;
|
|
107
120
|
/** 当前交互式量度的首锚点;只用于补偿引擎丢弃快速第二击,不进入 Scene。 */
|
|
108
121
|
#interactivePriceMeasurement;
|
|
109
122
|
/** 当前引擎进行中的绘制 Overlay ID;非 null 时 pointerdown 只路由给新绘制,不做命中测试。 */
|
|
110
123
|
#drawingInProgressId = null;
|
|
124
|
+
/** 宿主显式开启的 Drawing 输入策略;未配置时保持引擎原生行为。 */
|
|
125
|
+
#drawingInteraction;
|
|
126
|
+
/** 触摸精确绘制的通用提示与虚拟光标层。 */
|
|
127
|
+
#touchPrecisionGuide;
|
|
128
|
+
/** 当前允许切换到触摸精确交互的线段绘制。 */
|
|
129
|
+
#touchPrecisionDrawing;
|
|
130
|
+
/** 当前单指定位手势;拖动只更新光标,轻点才确认。 */
|
|
131
|
+
#touchPrecisionPointer;
|
|
132
|
+
/** 阻断触摸结束后浏览器补发的兼容鼠标事件。 */
|
|
133
|
+
#suppressCompatibilityMouseUntil = 0;
|
|
111
134
|
/** 确定性 opaque 交互 ID 序号。 */
|
|
112
135
|
#interactionSequence = 0;
|
|
113
136
|
/** 显式 Workspace 模式;Legacy 与 Workspace 状态严格隔离。 */
|
|
@@ -135,14 +158,19 @@ export class KLineChartsSceneAdapter {
|
|
|
135
158
|
horizontal: false,
|
|
136
159
|
vertical: false,
|
|
137
160
|
};
|
|
138
|
-
constructor(container, scene, handle, idMap, originalBackground) {
|
|
161
|
+
constructor(container, scene, handle, idMap, originalBackground, drawingInteraction = {}) {
|
|
139
162
|
this.#container = container;
|
|
140
163
|
this.#scene = scene;
|
|
141
164
|
this.#chart = handle.chart;
|
|
142
165
|
this.#engine = handle.module;
|
|
143
166
|
this.#resetClickArbitration = handle.resetClickArbitration;
|
|
167
|
+
this.#dispatchEngineMouseMove = handle.dispatchMouseMove;
|
|
168
|
+
this.#dispatchEngineMouseClick = handle.dispatchMouseClick;
|
|
144
169
|
this.#idMap = idMap;
|
|
145
170
|
this.#originalBackground = originalBackground;
|
|
171
|
+
this.#drawingInteraction = structuredClone(drawingInteraction);
|
|
172
|
+
this.#hitTolerance('mouse');
|
|
173
|
+
this.#hitTolerance('touch');
|
|
146
174
|
this.#installInteractionListeners();
|
|
147
175
|
this.#chart.subscribeAction('onCrosshairChange', this.#handleCrosshairChange);
|
|
148
176
|
this.#unsubscribeCrosshair = () => {
|
|
@@ -188,11 +216,15 @@ export class KLineChartsSceneAdapter {
|
|
|
188
216
|
let handle;
|
|
189
217
|
let adapter;
|
|
190
218
|
try {
|
|
191
|
-
handle = await createEngine(container, scene
|
|
219
|
+
handle = await createEngine(container, scene, {
|
|
220
|
+
...(options?.displayTimezone === undefined
|
|
221
|
+
? {}
|
|
222
|
+
: { displayTimezone: options.displayTimezone }),
|
|
223
|
+
});
|
|
192
224
|
registerProjectOverlays(handle.module.registerOverlay);
|
|
193
225
|
const idMap = createEngineIdMap(scene, handle.chart);
|
|
194
226
|
applyPanes(scene, handle.chart, idMap);
|
|
195
|
-
adapter = new KLineChartsSceneAdapter(container, scene, handle, idMap, originalBackground);
|
|
227
|
+
adapter = new KLineChartsSceneAdapter(container, scene, handle, idMap, originalBackground, options?.drawingInteraction);
|
|
196
228
|
applyViewport(handle.chart, scene.viewport);
|
|
197
229
|
container.style.backgroundColor = scene.chart.layout.backgroundColor;
|
|
198
230
|
adapter.#workspaceMode = true;
|
|
@@ -355,6 +387,12 @@ export class KLineChartsSceneAdapter {
|
|
|
355
387
|
if (previousId === id) {
|
|
356
388
|
return;
|
|
357
389
|
}
|
|
390
|
+
if (previousId === null && id !== null) {
|
|
391
|
+
this.#enterExclusiveSelection();
|
|
392
|
+
}
|
|
393
|
+
else if (previousId !== null && id === null) {
|
|
394
|
+
this.#leaveExclusiveSelection();
|
|
395
|
+
}
|
|
358
396
|
this.#selectedOverlayId = id;
|
|
359
397
|
if (this.#workspaceMode) {
|
|
360
398
|
this.#emitPort({
|
|
@@ -368,7 +406,63 @@ export class KLineChartsSceneAdapter {
|
|
|
368
406
|
this.#emit({ type: 'overlay-selected', id });
|
|
369
407
|
}
|
|
370
408
|
}
|
|
409
|
+
#enterExclusiveSelection() {
|
|
410
|
+
if (this.#drawingInteraction.exclusiveSelection !== true ||
|
|
411
|
+
this.#chartInteractionSnapshot !== undefined) {
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
this.#chartInteractionSnapshot = {
|
|
415
|
+
scrollEnabled: this.#chart.isScrollEnabled(),
|
|
416
|
+
zoomEnabled: this.#chart.isZoomEnabled(),
|
|
417
|
+
crosshairVisible: this.#chart.getStyles().crosshair.show,
|
|
418
|
+
};
|
|
419
|
+
this.#chart.setScrollEnabled(false);
|
|
420
|
+
this.#chart.setZoomEnabled(false);
|
|
421
|
+
this.#chart.setStyles({ crosshair: { show: false } });
|
|
422
|
+
}
|
|
423
|
+
#leaveExclusiveSelection() {
|
|
424
|
+
const snapshot = this.#chartInteractionSnapshot;
|
|
425
|
+
if (snapshot === undefined) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
this.#chartInteractionSnapshot = undefined;
|
|
429
|
+
this.#chart.setScrollEnabled(snapshot.scrollEnabled);
|
|
430
|
+
this.#chart.setZoomEnabled(snapshot.zoomEnabled);
|
|
431
|
+
this.#chart.setStyles({ crosshair: { show: snapshot.crosshairVisible } });
|
|
432
|
+
}
|
|
433
|
+
#hitTolerance(pointerType) {
|
|
434
|
+
const touch = pointerType === 'touch';
|
|
435
|
+
const defaults = touch
|
|
436
|
+
? DEFAULT_OVERLAY_TOUCH_HIT_TOLERANCE
|
|
437
|
+
: DEFAULT_OVERLAY_MOUSE_HIT_TOLERANCE;
|
|
438
|
+
const configured = touch
|
|
439
|
+
? this.#drawingInteraction.hitTolerance?.touch
|
|
440
|
+
: this.#drawingInteraction.hitTolerance?.mouse;
|
|
441
|
+
const body = configured?.body ?? defaults.body;
|
|
442
|
+
const anchor = configured?.anchor ?? defaults.anchor;
|
|
443
|
+
if (!Number.isFinite(body) || body < 0 || !Number.isFinite(anchor) || anchor < 0) {
|
|
444
|
+
throw new SceneError('RUNTIME_INIT_FAILED', '/drawingInteraction/hitTolerance', 'Drawing hit tolerance must contain finite non-negative CSS pixel values.');
|
|
445
|
+
}
|
|
446
|
+
return { body, anchor };
|
|
447
|
+
}
|
|
371
448
|
#dimensionsForHit(overlay, hit) {
|
|
449
|
+
const constrainedType = overlay.type === 'horizontalStraightLine'
|
|
450
|
+
|| overlay.type === 'priceLine'
|
|
451
|
+
|| overlay.type === 'simpleTag'
|
|
452
|
+
|| overlay.type === 'horizontalRayLine'
|
|
453
|
+
|| overlay.type === 'horizontalSegment'
|
|
454
|
+
? 'vertical'
|
|
455
|
+
: overlay.type === 'verticalStraightLine'
|
|
456
|
+
|| overlay.type === 'verticalRayLine'
|
|
457
|
+
|| overlay.type === 'verticalSegment'
|
|
458
|
+
? 'horizontal'
|
|
459
|
+
: null;
|
|
460
|
+
if (constrainedType === 'vertical') {
|
|
461
|
+
return { horizontal: false, vertical: true };
|
|
462
|
+
}
|
|
463
|
+
if (constrainedType === 'horizontal') {
|
|
464
|
+
return { horizontal: true, vertical: false };
|
|
465
|
+
}
|
|
372
466
|
if (hit.target !== 'anchor') {
|
|
373
467
|
return { horizontal: true, vertical: true };
|
|
374
468
|
}
|
|
@@ -426,6 +520,7 @@ export class KLineChartsSceneAdapter {
|
|
|
426
520
|
preventDefault?.();
|
|
427
521
|
},
|
|
428
522
|
onDrawEnd: ({ overlay }) => {
|
|
523
|
+
this.#finishTouchPrecisionDrawing(overlay.id);
|
|
429
524
|
if (drawing &&
|
|
430
525
|
this.#interactivePriceMeasurement?.source.id === source.id) {
|
|
431
526
|
this.#interactivePriceMeasurement = undefined;
|
|
@@ -459,6 +554,7 @@ export class KLineChartsSceneAdapter {
|
|
|
459
554
|
}
|
|
460
555
|
},
|
|
461
556
|
onRemoved: ({ overlay }) => {
|
|
557
|
+
this.#finishTouchPrecisionDrawing(overlay.id);
|
|
462
558
|
if (this.#interactivePriceMeasurement?.source.id === overlay.id) {
|
|
463
559
|
this.#interactivePriceMeasurement = undefined;
|
|
464
560
|
}
|
|
@@ -492,32 +588,191 @@ export class KLineChartsSceneAdapter {
|
|
|
492
588
|
throw error;
|
|
493
589
|
}
|
|
494
590
|
}
|
|
591
|
+
#prepareTouchPrecisionDrawing(id, paneId) {
|
|
592
|
+
if (this.#drawingInteraction.touch !== 'precision-cursor') {
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
this.#finishTouchPrecisionDrawing();
|
|
596
|
+
this.#touchPrecisionDrawing = {
|
|
597
|
+
id,
|
|
598
|
+
paneId,
|
|
599
|
+
phase: 'move-start',
|
|
600
|
+
};
|
|
601
|
+
const view = this.#container.ownerDocument.defaultView;
|
|
602
|
+
if ((view?.navigator.maxTouchPoints ?? 0) > 0 ||
|
|
603
|
+
view?.matchMedia('(any-pointer: coarse)').matches === true) {
|
|
604
|
+
const guide = this.#ensureTouchPrecisionGuide();
|
|
605
|
+
guide.setPhase('move-start');
|
|
606
|
+
guide.show();
|
|
607
|
+
}
|
|
608
|
+
}
|
|
609
|
+
#ensureTouchPrecisionGuide() {
|
|
610
|
+
this.#touchPrecisionGuide ??= new TouchPrecisionDrawingGuide(this.#container, () => this.#cancelTouchPrecisionDrawing(true));
|
|
611
|
+
return this.#touchPrecisionGuide;
|
|
612
|
+
}
|
|
613
|
+
#finishTouchPrecisionDrawing(id) {
|
|
614
|
+
const drawing = this.#touchPrecisionDrawing;
|
|
615
|
+
if (drawing === undefined || (id !== undefined && drawing.id !== id)) {
|
|
616
|
+
return;
|
|
617
|
+
}
|
|
618
|
+
this.#stopTouchPrecisionPointerCapture();
|
|
619
|
+
this.#touchPrecisionPointer = undefined;
|
|
620
|
+
this.#touchPrecisionDrawing = undefined;
|
|
621
|
+
this.#touchPrecisionGuide?.hide();
|
|
622
|
+
}
|
|
623
|
+
#cancelTouchPrecisionDrawing(emitRemoval) {
|
|
624
|
+
const drawing = this.#touchPrecisionDrawing;
|
|
625
|
+
if (drawing === undefined) {
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
const id = drawing.id;
|
|
629
|
+
this.#finishTouchPrecisionDrawing(id);
|
|
630
|
+
this.#suppressCompatibilityMouseUntil = performance.now() + 800;
|
|
631
|
+
this.#chart.removeOverlay({ id });
|
|
632
|
+
if (this.#drawingInProgressId === id) {
|
|
633
|
+
this.#drawingInProgressId = null;
|
|
634
|
+
}
|
|
635
|
+
if (this.#workspaceMode) {
|
|
636
|
+
const removed = this.#workspaceSources.delete(id);
|
|
637
|
+
if (emitRemoval && removed) {
|
|
638
|
+
this.#emitPort({ type: 'removed', id });
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
#stopTouchPrecisionPointerCapture() {
|
|
643
|
+
const pointer = this.#touchPrecisionPointer;
|
|
644
|
+
if (pointer === undefined) {
|
|
645
|
+
return;
|
|
646
|
+
}
|
|
647
|
+
try {
|
|
648
|
+
if (this.#container.hasPointerCapture(pointer.pointerId)) {
|
|
649
|
+
this.#container.releasePointerCapture(pointer.pointerId);
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
catch {
|
|
653
|
+
// Synthetic browser tests and interrupted system gestures may not own capture.
|
|
654
|
+
}
|
|
655
|
+
}
|
|
656
|
+
#touchPrecisionMainElement(paneId) {
|
|
657
|
+
const filter = this.#primaryAxisFilter(paneId);
|
|
658
|
+
const main = this.#chart.getDom(filter.paneId, 'main');
|
|
659
|
+
if (!(main instanceof HTMLElement)) {
|
|
660
|
+
throw new SceneError('INVALID_REFERENCE', '/drawings/target', 'Touch Drawing target Pane has no interactive main element.');
|
|
661
|
+
}
|
|
662
|
+
return main;
|
|
663
|
+
}
|
|
664
|
+
#touchPrecisionBounds(paneId) {
|
|
665
|
+
const containerRect = this.#container.getBoundingClientRect();
|
|
666
|
+
const mainRect = this.#touchPrecisionMainElement(paneId).getBoundingClientRect();
|
|
667
|
+
return {
|
|
668
|
+
left: Math.max(0, mainRect.left - containerRect.left),
|
|
669
|
+
top: Math.max(0, mainRect.top - containerRect.top),
|
|
670
|
+
right: Math.min(containerRect.width, mainRect.right - containerRect.left),
|
|
671
|
+
bottom: Math.min(containerRect.height, mainRect.bottom - containerRect.top),
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
#dispatchTouchPrecisionMouseMove(point) {
|
|
675
|
+
const rect = this.#container.getBoundingClientRect();
|
|
676
|
+
this.#dispatchEngineMouseMove({
|
|
677
|
+
x: point.x,
|
|
678
|
+
y: point.y,
|
|
679
|
+
pageX: rect.left + point.x + window.scrollX,
|
|
680
|
+
pageY: rect.top + point.y + window.scrollY,
|
|
681
|
+
});
|
|
682
|
+
}
|
|
683
|
+
#dispatchTouchPrecisionClick(point) {
|
|
684
|
+
const rect = this.#container.getBoundingClientRect();
|
|
685
|
+
this.#resetClickArbitration();
|
|
686
|
+
this.#dispatchEngineMouseClick({
|
|
687
|
+
x: point.x,
|
|
688
|
+
y: point.y,
|
|
689
|
+
pageX: rect.left + point.x + window.scrollX,
|
|
690
|
+
pageY: rect.top + point.y + window.scrollY,
|
|
691
|
+
});
|
|
692
|
+
}
|
|
693
|
+
#updateTouchPrecisionCursor(drawing, pointer) {
|
|
694
|
+
const cursor = resolveTouchPrecisionCursor(pointer, this.#touchPrecisionBounds(drawing.paneId));
|
|
695
|
+
this.#touchPrecisionGuide?.updateCursor(cursor);
|
|
696
|
+
this.#dispatchTouchPrecisionMouseMove(cursor);
|
|
697
|
+
return cursor;
|
|
698
|
+
}
|
|
699
|
+
#isTouchPrecisionCancelTarget(target) {
|
|
700
|
+
return this.#touchPrecisionGuide?.ownsCancelTarget(target) ?? false;
|
|
701
|
+
}
|
|
495
702
|
#installInteractionListeners() {
|
|
496
703
|
this.#container.addEventListener('mousedown', this.#handleRightMouseDown, true);
|
|
704
|
+
this.#container.addEventListener('click', this.#handleCompatibilityClick, true);
|
|
497
705
|
this.#container.addEventListener('contextmenu', this.#handleContextMenu, true);
|
|
498
706
|
this.#container.addEventListener('pointerdown', this.#handlePointerDown, true);
|
|
499
707
|
this.#container.addEventListener('pointermove', this.#handlePointerMove, true);
|
|
500
708
|
this.#container.addEventListener('pointerup', this.#handlePointerUp, true);
|
|
501
709
|
this.#container.addEventListener('pointercancel', this.#handlePointerCancel, true);
|
|
710
|
+
this.#container.addEventListener('touchstart', this.#handleCompatibilityTouch, {
|
|
711
|
+
capture: true,
|
|
712
|
+
passive: false,
|
|
713
|
+
});
|
|
714
|
+
this.#container.addEventListener('touchmove', this.#handleCompatibilityTouch, {
|
|
715
|
+
capture: true,
|
|
716
|
+
passive: false,
|
|
717
|
+
});
|
|
718
|
+
this.#container.addEventListener('touchend', this.#handleCompatibilityTouch, {
|
|
719
|
+
capture: true,
|
|
720
|
+
passive: false,
|
|
721
|
+
});
|
|
722
|
+
this.#container.addEventListener('touchcancel', this.#handleCompatibilityTouch, {
|
|
723
|
+
capture: true,
|
|
724
|
+
passive: false,
|
|
725
|
+
});
|
|
502
726
|
window.addEventListener('keydown', this.#handleKeyDown);
|
|
503
727
|
window.addEventListener('blur', this.#handleWindowBlur);
|
|
504
728
|
}
|
|
505
729
|
#removeInteractionListeners() {
|
|
506
730
|
this.#container.removeEventListener('mousedown', this.#handleRightMouseDown, true);
|
|
731
|
+
this.#container.removeEventListener('click', this.#handleCompatibilityClick, true);
|
|
507
732
|
this.#container.removeEventListener('contextmenu', this.#handleContextMenu, true);
|
|
508
733
|
this.#container.removeEventListener('pointerdown', this.#handlePointerDown, true);
|
|
509
734
|
this.#container.removeEventListener('pointermove', this.#handlePointerMove, true);
|
|
510
735
|
this.#container.removeEventListener('pointerup', this.#handlePointerUp, true);
|
|
511
736
|
this.#container.removeEventListener('pointercancel', this.#handlePointerCancel, true);
|
|
737
|
+
this.#container.removeEventListener('touchstart', this.#handleCompatibilityTouch, true);
|
|
738
|
+
this.#container.removeEventListener('touchmove', this.#handleCompatibilityTouch, true);
|
|
739
|
+
this.#container.removeEventListener('touchend', this.#handleCompatibilityTouch, true);
|
|
740
|
+
this.#container.removeEventListener('touchcancel', this.#handleCompatibilityTouch, true);
|
|
512
741
|
window.removeEventListener('keydown', this.#handleKeyDown);
|
|
513
742
|
window.removeEventListener('blur', this.#handleWindowBlur);
|
|
514
743
|
}
|
|
515
744
|
#handleRightMouseDown = (event) => {
|
|
745
|
+
if (event.button === 0 &&
|
|
746
|
+
event.isTrusted &&
|
|
747
|
+
performance.now() < this.#suppressCompatibilityMouseUntil &&
|
|
748
|
+
!this.#isTouchPrecisionCancelTarget(event.target)) {
|
|
749
|
+
event.preventDefault();
|
|
750
|
+
event.stopImmediatePropagation();
|
|
751
|
+
return;
|
|
752
|
+
}
|
|
516
753
|
if (event.button === 2) {
|
|
517
754
|
// 阻止事件到达 KLineCharts;不 preventDefault,保留浏览器原生右键菜单。
|
|
518
755
|
event.stopPropagation();
|
|
519
756
|
}
|
|
520
757
|
};
|
|
758
|
+
#handleCompatibilityClick = (event) => {
|
|
759
|
+
if (event.isTrusted &&
|
|
760
|
+
performance.now() < this.#suppressCompatibilityMouseUntil &&
|
|
761
|
+
!this.#isTouchPrecisionCancelTarget(event.target)) {
|
|
762
|
+
event.preventDefault();
|
|
763
|
+
event.stopImmediatePropagation();
|
|
764
|
+
}
|
|
765
|
+
};
|
|
766
|
+
#handleCompatibilityTouch = (event) => {
|
|
767
|
+
if ((this.#touchPrecisionDrawing !== undefined ||
|
|
768
|
+
performance.now() < this.#suppressCompatibilityMouseUntil) &&
|
|
769
|
+
!this.#isTouchPrecisionCancelTarget(event.target)) {
|
|
770
|
+
if (event.cancelable) {
|
|
771
|
+
event.preventDefault();
|
|
772
|
+
}
|
|
773
|
+
event.stopImmediatePropagation();
|
|
774
|
+
}
|
|
775
|
+
};
|
|
521
776
|
#handleContextMenu = (event) => {
|
|
522
777
|
// KLineCharts 会 preventDefault。捕获阶段截断传播,但 Baron 本身不拦截菜单。
|
|
523
778
|
event.stopPropagation();
|
|
@@ -594,60 +849,40 @@ export class KLineChartsSceneAdapter {
|
|
|
594
849
|
#overlayGeometries() {
|
|
595
850
|
const geometries = [];
|
|
596
851
|
const active = this.#activeOverlays();
|
|
852
|
+
const containerRect = this.#container.getBoundingClientRect();
|
|
853
|
+
const canvas = this.#container.ownerDocument.createElement('canvas');
|
|
854
|
+
const textContext = canvas.getContext('2d');
|
|
597
855
|
for (let sceneIndex = 0; sceneIndex < active.length; sceneIndex++) {
|
|
598
856
|
const overlay = active[sceneIndex];
|
|
599
857
|
if (overlay === undefined || !overlay.visible) {
|
|
600
858
|
continue;
|
|
601
859
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
geometries.push(
|
|
628
|
-
overlayId: overlay.id,
|
|
629
|
-
sceneIndex,
|
|
630
|
-
zLevel: overlay.zLevel,
|
|
631
|
-
locked: overlay.locked,
|
|
632
|
-
anchors: [start, end],
|
|
633
|
-
bodySegments: [[start, end]],
|
|
634
|
-
});
|
|
635
|
-
continue;
|
|
636
|
-
}
|
|
637
|
-
if (overlay.points !== undefined && overlay.points.length >= 2) {
|
|
638
|
-
const anchors = overlay.points.map((point) => this.#toPixel(point, overlay.paneId));
|
|
639
|
-
const bodySegments = [];
|
|
640
|
-
for (let pointIndex = 1; pointIndex < anchors.length; pointIndex++) {
|
|
641
|
-
bodySegments.push([anchors[pointIndex - 1], anchors[pointIndex]]);
|
|
642
|
-
}
|
|
643
|
-
geometries.push({
|
|
644
|
-
overlayId: overlay.id,
|
|
645
|
-
sceneIndex,
|
|
646
|
-
zLevel: overlay.zLevel,
|
|
647
|
-
locked: overlay.locked,
|
|
648
|
-
anchors,
|
|
649
|
-
bodySegments,
|
|
650
|
-
});
|
|
860
|
+
const paneFilter = this.#primaryAxisFilter(overlay.paneId);
|
|
861
|
+
const paneMain = this.#chart.getDom(paneFilter.paneId, 'main');
|
|
862
|
+
const mainRect = paneMain?.getBoundingClientRect() ?? containerRect;
|
|
863
|
+
const geometry = projectOverlayGeometry(overlay, sceneIndex, {
|
|
864
|
+
bounds: {
|
|
865
|
+
left: mainRect.left - containerRect.left,
|
|
866
|
+
top: mainRect.top - containerRect.top,
|
|
867
|
+
right: mainRect.right - containerRect.left,
|
|
868
|
+
bottom: mainRect.bottom - containerRect.top,
|
|
869
|
+
},
|
|
870
|
+
referenceTimestamp: this.#scene.data[0].timestamp,
|
|
871
|
+
referenceValue: this.#scene.data[0].close,
|
|
872
|
+
project: (point) => this.#toPixel(point, overlay.paneId),
|
|
873
|
+
measureText: (text, source) => {
|
|
874
|
+
const textStyle = source.styles.text;
|
|
875
|
+
if (textContext !== null) {
|
|
876
|
+
textContext.font = `${textStyle.weight} ${textStyle.size}px ${textStyle.family}`;
|
|
877
|
+
}
|
|
878
|
+
return {
|
|
879
|
+
width: Math.max(textStyle.size, textContext?.measureText(text).width ?? [...text].length * textStyle.size),
|
|
880
|
+
height: textStyle.size * 1.4,
|
|
881
|
+
};
|
|
882
|
+
},
|
|
883
|
+
});
|
|
884
|
+
if (geometry !== null) {
|
|
885
|
+
geometries.push(geometry);
|
|
651
886
|
}
|
|
652
887
|
}
|
|
653
888
|
return geometries;
|
|
@@ -691,13 +926,102 @@ export class KLineChartsSceneAdapter {
|
|
|
691
926
|
this.#emit({ type: 'scene-error', issues: structuredClone(error.issues) });
|
|
692
927
|
}
|
|
693
928
|
}
|
|
929
|
+
#routeTouchPrecisionPointerDown(event) {
|
|
930
|
+
const drawing = this.#touchPrecisionDrawing;
|
|
931
|
+
if (drawing === undefined || this.#isTouchPrecisionCancelTarget(event.target)) {
|
|
932
|
+
return false;
|
|
933
|
+
}
|
|
934
|
+
if (event.pointerType !== 'touch') {
|
|
935
|
+
this.#finishTouchPrecisionDrawing(drawing.id);
|
|
936
|
+
return false;
|
|
937
|
+
}
|
|
938
|
+
event.preventDefault();
|
|
939
|
+
event.stopImmediatePropagation();
|
|
940
|
+
if (this.#touchPrecisionPointer !== undefined) {
|
|
941
|
+
return true;
|
|
942
|
+
}
|
|
943
|
+
const guide = this.#ensureTouchPrecisionGuide();
|
|
944
|
+
guide.setPhase(drawing.phase);
|
|
945
|
+
guide.show();
|
|
946
|
+
const point = this.#pointerCoordinate(event);
|
|
947
|
+
this.#touchPrecisionPointer = {
|
|
948
|
+
pointerId: event.pointerId,
|
|
949
|
+
origin: point,
|
|
950
|
+
current: point,
|
|
951
|
+
};
|
|
952
|
+
try {
|
|
953
|
+
this.#container.setPointerCapture(event.pointerId);
|
|
954
|
+
}
|
|
955
|
+
catch {
|
|
956
|
+
// Synthetic PointerEvents do not own browser pointer capture.
|
|
957
|
+
}
|
|
958
|
+
this.#suppressCompatibilityMouseUntil = performance.now() + 800;
|
|
959
|
+
this.#updateTouchPrecisionCursor(drawing, point);
|
|
960
|
+
return true;
|
|
961
|
+
}
|
|
962
|
+
#routeTouchPrecisionPointerMove(event) {
|
|
963
|
+
const drawing = this.#touchPrecisionDrawing;
|
|
964
|
+
const pointer = this.#touchPrecisionPointer;
|
|
965
|
+
if (drawing === undefined ||
|
|
966
|
+
pointer === undefined ||
|
|
967
|
+
pointer.pointerId !== event.pointerId) {
|
|
968
|
+
return false;
|
|
969
|
+
}
|
|
970
|
+
event.preventDefault();
|
|
971
|
+
event.stopImmediatePropagation();
|
|
972
|
+
const point = this.#pointerCoordinate(event);
|
|
973
|
+
pointer.current = point;
|
|
974
|
+
this.#suppressCompatibilityMouseUntil = performance.now() + 800;
|
|
975
|
+
this.#updateTouchPrecisionCursor(drawing, point);
|
|
976
|
+
return true;
|
|
977
|
+
}
|
|
978
|
+
#routeTouchPrecisionPointerUp(event) {
|
|
979
|
+
const drawing = this.#touchPrecisionDrawing;
|
|
980
|
+
const pointer = this.#touchPrecisionPointer;
|
|
981
|
+
if (drawing === undefined ||
|
|
982
|
+
pointer === undefined ||
|
|
983
|
+
pointer.pointerId !== event.pointerId) {
|
|
984
|
+
return false;
|
|
985
|
+
}
|
|
986
|
+
event.preventDefault();
|
|
987
|
+
event.stopImmediatePropagation();
|
|
988
|
+
const point = this.#pointerCoordinate(event);
|
|
989
|
+
pointer.current = point;
|
|
990
|
+
const cursor = this.#updateTouchPrecisionCursor(drawing, point);
|
|
991
|
+
const tap = isTouchPrecisionTap(pointer.origin, pointer.current);
|
|
992
|
+
this.#stopTouchPrecisionPointerCapture();
|
|
993
|
+
this.#touchPrecisionPointer = undefined;
|
|
994
|
+
this.#suppressCompatibilityMouseUntil = performance.now() + 800;
|
|
995
|
+
if (drawing.phase === 'move-start') {
|
|
996
|
+
drawing.phase = 'confirm-start';
|
|
997
|
+
this.#touchPrecisionGuide?.setPhase('confirm-start');
|
|
998
|
+
return true;
|
|
999
|
+
}
|
|
1000
|
+
if (!tap) {
|
|
1001
|
+
return true;
|
|
1002
|
+
}
|
|
1003
|
+
const phase = drawing.phase;
|
|
1004
|
+
this.#dispatchTouchPrecisionClick(cursor);
|
|
1005
|
+
const active = this.#touchPrecisionDrawing;
|
|
1006
|
+
if (phase === 'confirm-start' &&
|
|
1007
|
+
active !== undefined &&
|
|
1008
|
+
active.id === drawing.id) {
|
|
1009
|
+
active.phase = 'confirm-end';
|
|
1010
|
+
this.#touchPrecisionGuide?.setPhase('confirm-end');
|
|
1011
|
+
}
|
|
1012
|
+
return true;
|
|
1013
|
+
}
|
|
694
1014
|
#handlePointerDown = (event) => {
|
|
695
1015
|
if (this.#disposed ||
|
|
696
1016
|
event.button !== 0 ||
|
|
697
1017
|
this.#pointerInteraction !== undefined ||
|
|
1018
|
+
this.#deselectingPointerId !== undefined ||
|
|
698
1019
|
!this.#mutationsEnabled) {
|
|
699
1020
|
return;
|
|
700
1021
|
}
|
|
1022
|
+
if (this.#routeTouchPrecisionPointerDown(event)) {
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
701
1025
|
const coordinate = this.#pointerCoordinate(event);
|
|
702
1026
|
const measurement = this.#interactivePriceMeasurement;
|
|
703
1027
|
if (measurement !== undefined) {
|
|
@@ -728,9 +1052,22 @@ export class KLineChartsSceneAdapter {
|
|
|
728
1052
|
if (this.#drawingInProgressId !== null) {
|
|
729
1053
|
return;
|
|
730
1054
|
}
|
|
731
|
-
const hit = hitTestOverlayGeometries(coordinate, this.#overlayGeometries());
|
|
1055
|
+
const hit = hitTestOverlayGeometries(coordinate, this.#overlayGeometries(), this.#hitTolerance(event.pointerType));
|
|
732
1056
|
if (hit === null) {
|
|
733
1057
|
const selected = this.#activeOverlays().find((overlay) => overlay.id === this.#selectedOverlayId);
|
|
1058
|
+
if (selected !== undefined &&
|
|
1059
|
+
this.#drawingInteraction.exclusiveSelection === true) {
|
|
1060
|
+
event.preventDefault();
|
|
1061
|
+
event.stopImmediatePropagation();
|
|
1062
|
+
this.#deselectingPointerId = event.pointerId;
|
|
1063
|
+
try {
|
|
1064
|
+
this.#container.setPointerCapture(event.pointerId);
|
|
1065
|
+
}
|
|
1066
|
+
catch {
|
|
1067
|
+
// Synthetic PointerEvents do not own browser pointer capture.
|
|
1068
|
+
}
|
|
1069
|
+
return;
|
|
1070
|
+
}
|
|
734
1071
|
if (selected !== undefined && isControlledInteractionOverlay(selected)) {
|
|
735
1072
|
this.#selectOverlay(null);
|
|
736
1073
|
}
|
|
@@ -741,7 +1078,8 @@ export class KLineChartsSceneAdapter {
|
|
|
741
1078
|
return;
|
|
742
1079
|
}
|
|
743
1080
|
this.#selectOverlay(before.id);
|
|
744
|
-
if (!isControlledInteractionOverlay(before)
|
|
1081
|
+
if (!isControlledInteractionOverlay(before) &&
|
|
1082
|
+
this.#drawingInteraction.exclusiveSelection !== true) {
|
|
745
1083
|
return;
|
|
746
1084
|
}
|
|
747
1085
|
event.preventDefault();
|
|
@@ -749,7 +1087,12 @@ export class KLineChartsSceneAdapter {
|
|
|
749
1087
|
if (hit.locked) {
|
|
750
1088
|
return;
|
|
751
1089
|
}
|
|
752
|
-
|
|
1090
|
+
try {
|
|
1091
|
+
this.#container.setPointerCapture(event.pointerId);
|
|
1092
|
+
}
|
|
1093
|
+
catch {
|
|
1094
|
+
// Synthetic PointerEvents do not own browser pointer capture.
|
|
1095
|
+
}
|
|
753
1096
|
this.#interactionDimensions = this.#dimensionsForHit(before, hit);
|
|
754
1097
|
this.#pointerInteraction = {
|
|
755
1098
|
pointerId: event.pointerId,
|
|
@@ -762,6 +1105,14 @@ export class KLineChartsSceneAdapter {
|
|
|
762
1105
|
};
|
|
763
1106
|
};
|
|
764
1107
|
#handlePointerMove = (event) => {
|
|
1108
|
+
if (this.#routeTouchPrecisionPointerMove(event)) {
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
if (this.#deselectingPointerId === event.pointerId) {
|
|
1112
|
+
event.preventDefault();
|
|
1113
|
+
event.stopImmediatePropagation();
|
|
1114
|
+
return;
|
|
1115
|
+
}
|
|
765
1116
|
const interaction = this.#pointerInteraction;
|
|
766
1117
|
if (interaction === undefined || interaction.pointerId !== event.pointerId) {
|
|
767
1118
|
return;
|
|
@@ -793,6 +1144,7 @@ export class KLineChartsSceneAdapter {
|
|
|
793
1144
|
throw new SceneError('RUNTIME_INIT_FAILED', `/overlays/${index}`, `KLineCharts failed to preview Overlay ${normalized.id}.`);
|
|
794
1145
|
}
|
|
795
1146
|
if (this.#workspaceMode) {
|
|
1147
|
+
interaction.candidate = candidate;
|
|
796
1148
|
this.#setActiveOverlays(overlays);
|
|
797
1149
|
return;
|
|
798
1150
|
}
|
|
@@ -812,6 +1164,19 @@ export class KLineChartsSceneAdapter {
|
|
|
812
1164
|
}
|
|
813
1165
|
};
|
|
814
1166
|
#handlePointerUp = (event) => {
|
|
1167
|
+
if (this.#routeTouchPrecisionPointerUp(event)) {
|
|
1168
|
+
return;
|
|
1169
|
+
}
|
|
1170
|
+
if (this.#deselectingPointerId === event.pointerId) {
|
|
1171
|
+
event.preventDefault();
|
|
1172
|
+
event.stopImmediatePropagation();
|
|
1173
|
+
if (this.#container.hasPointerCapture(event.pointerId)) {
|
|
1174
|
+
this.#container.releasePointerCapture(event.pointerId);
|
|
1175
|
+
}
|
|
1176
|
+
this.#deselectingPointerId = undefined;
|
|
1177
|
+
this.#selectOverlay(null);
|
|
1178
|
+
return;
|
|
1179
|
+
}
|
|
815
1180
|
const interaction = this.#pointerInteraction;
|
|
816
1181
|
if (interaction === undefined || interaction.pointerId !== event.pointerId) {
|
|
817
1182
|
return;
|
|
@@ -819,6 +1184,7 @@ export class KLineChartsSceneAdapter {
|
|
|
819
1184
|
event.preventDefault();
|
|
820
1185
|
event.stopImmediatePropagation();
|
|
821
1186
|
this.#pointerInteraction = undefined;
|
|
1187
|
+
const interactionDimensions = this.#interactionDimensions;
|
|
822
1188
|
this.#interactionDimensions = { horizontal: false, vertical: false };
|
|
823
1189
|
this.#stopPointerCapture(interaction);
|
|
824
1190
|
if (!interaction.started) {
|
|
@@ -837,7 +1203,7 @@ export class KLineChartsSceneAdapter {
|
|
|
837
1203
|
type: 'updated',
|
|
838
1204
|
id: overlay.id,
|
|
839
1205
|
drawing: snapshotOfDrawing(sceneOverlayToDrawing(overlay, drawing)),
|
|
840
|
-
editDimensions: structuredClone(
|
|
1206
|
+
editDimensions: structuredClone(interactionDimensions),
|
|
841
1207
|
});
|
|
842
1208
|
}
|
|
843
1209
|
return;
|
|
@@ -852,21 +1218,56 @@ export class KLineChartsSceneAdapter {
|
|
|
852
1218
|
this.#emit({ type: 'overlay-updated', overlay: committed });
|
|
853
1219
|
};
|
|
854
1220
|
#handlePointerCancel = (event) => {
|
|
1221
|
+
if (this.#touchPrecisionPointer?.pointerId === event.pointerId) {
|
|
1222
|
+
event.preventDefault();
|
|
1223
|
+
event.stopImmediatePropagation();
|
|
1224
|
+
this.#cancelTouchPrecisionDrawing(true);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
855
1227
|
if (this.#pointerInteraction?.pointerId === event.pointerId) {
|
|
856
1228
|
event.preventDefault();
|
|
857
1229
|
event.stopImmediatePropagation();
|
|
858
1230
|
this.#cancelInteraction('pointer-cancel');
|
|
1231
|
+
return;
|
|
1232
|
+
}
|
|
1233
|
+
if (this.#deselectingPointerId === event.pointerId) {
|
|
1234
|
+
event.preventDefault();
|
|
1235
|
+
event.stopImmediatePropagation();
|
|
1236
|
+
if (this.#container.hasPointerCapture(event.pointerId)) {
|
|
1237
|
+
this.#container.releasePointerCapture(event.pointerId);
|
|
1238
|
+
}
|
|
1239
|
+
this.#deselectingPointerId = undefined;
|
|
1240
|
+
this.#selectOverlay(null);
|
|
859
1241
|
}
|
|
860
1242
|
};
|
|
861
1243
|
#handleKeyDown = (event) => {
|
|
1244
|
+
if (event.key === 'Escape' && this.#touchPrecisionDrawing !== undefined) {
|
|
1245
|
+
this.#cancelTouchPrecisionDrawing(true);
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
862
1248
|
if (event.key === 'Escape' && this.#pointerInteraction !== undefined) {
|
|
863
1249
|
this.#cancelInteraction('escape');
|
|
864
1250
|
}
|
|
1251
|
+
if (event.key === 'Escape' &&
|
|
1252
|
+
this.#drawingInteraction.exclusiveSelection === true &&
|
|
1253
|
+
this.#selectedOverlayId !== null) {
|
|
1254
|
+
this.#selectOverlay(null);
|
|
1255
|
+
}
|
|
865
1256
|
};
|
|
866
1257
|
#handleWindowBlur = () => {
|
|
1258
|
+
if (this.#touchPrecisionDrawing !== undefined) {
|
|
1259
|
+
this.#cancelTouchPrecisionDrawing(true);
|
|
1260
|
+
}
|
|
867
1261
|
if (this.#pointerInteraction !== undefined) {
|
|
868
1262
|
this.#cancelInteraction('window-blur');
|
|
869
1263
|
}
|
|
1264
|
+
if (this.#deselectingPointerId !== undefined) {
|
|
1265
|
+
if (this.#container.hasPointerCapture(this.#deselectingPointerId)) {
|
|
1266
|
+
this.#container.releasePointerCapture(this.#deselectingPointerId);
|
|
1267
|
+
}
|
|
1268
|
+
this.#deselectingPointerId = undefined;
|
|
1269
|
+
this.#selectOverlay(null);
|
|
1270
|
+
}
|
|
870
1271
|
};
|
|
871
1272
|
subscribe(listener) {
|
|
872
1273
|
this.#assertActive();
|
|
@@ -1076,6 +1477,9 @@ export class KLineChartsSceneAdapter {
|
|
|
1076
1477
|
throw new SceneError('RUNTIME_INIT_FAILED', '/drawings', `KLineCharts failed to start Drawing ${request.id}.`);
|
|
1077
1478
|
}
|
|
1078
1479
|
this.#drawingInProgressId = request.id;
|
|
1480
|
+
if (request.type === 'segment') {
|
|
1481
|
+
this.#prepareTouchPrecisionDrawing(request.id, overlay.paneId);
|
|
1482
|
+
}
|
|
1079
1483
|
return request.id;
|
|
1080
1484
|
}
|
|
1081
1485
|
listDrawings() {
|
|
@@ -1538,6 +1942,11 @@ export class KLineChartsSceneAdapter {
|
|
|
1538
1942
|
return;
|
|
1539
1943
|
}
|
|
1540
1944
|
this.#cancelInteraction('destroy');
|
|
1945
|
+
this.#deselectingPointerId = undefined;
|
|
1946
|
+
this.#leaveExclusiveSelection();
|
|
1947
|
+
this.#cancelTouchPrecisionDrawing(false);
|
|
1948
|
+
this.#touchPrecisionGuide?.destroy();
|
|
1949
|
+
this.#touchPrecisionGuide = undefined;
|
|
1541
1950
|
this.#interactivePriceMeasurement = undefined;
|
|
1542
1951
|
this.#drawingInProgressId = null;
|
|
1543
1952
|
this.#disposed = true;
|