@baron1996/klinecharts-adapter 0.9.4 → 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 +2 -1
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +461 -56
- package/dist/drawing/engine-port.d.ts +15 -0
- package/dist/drawing/engine-port.d.ts.map +1 -1
- package/dist/engine.d.ts +10 -0
- package/dist/engine.d.ts.map +1 -1
- package/dist/engine.js +24 -0
- 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/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 = () => {
|
|
@@ -196,7 +224,7 @@ export class KLineChartsSceneAdapter {
|
|
|
196
224
|
registerProjectOverlays(handle.module.registerOverlay);
|
|
197
225
|
const idMap = createEngineIdMap(scene, handle.chart);
|
|
198
226
|
applyPanes(scene, handle.chart, idMap);
|
|
199
|
-
adapter = new KLineChartsSceneAdapter(container, scene, handle, idMap, originalBackground);
|
|
227
|
+
adapter = new KLineChartsSceneAdapter(container, scene, handle, idMap, originalBackground, options?.drawingInteraction);
|
|
200
228
|
applyViewport(handle.chart, scene.viewport);
|
|
201
229
|
container.style.backgroundColor = scene.chart.layout.backgroundColor;
|
|
202
230
|
adapter.#workspaceMode = true;
|
|
@@ -359,6 +387,12 @@ export class KLineChartsSceneAdapter {
|
|
|
359
387
|
if (previousId === id) {
|
|
360
388
|
return;
|
|
361
389
|
}
|
|
390
|
+
if (previousId === null && id !== null) {
|
|
391
|
+
this.#enterExclusiveSelection();
|
|
392
|
+
}
|
|
393
|
+
else if (previousId !== null && id === null) {
|
|
394
|
+
this.#leaveExclusiveSelection();
|
|
395
|
+
}
|
|
362
396
|
this.#selectedOverlayId = id;
|
|
363
397
|
if (this.#workspaceMode) {
|
|
364
398
|
this.#emitPort({
|
|
@@ -372,7 +406,63 @@ export class KLineChartsSceneAdapter {
|
|
|
372
406
|
this.#emit({ type: 'overlay-selected', id });
|
|
373
407
|
}
|
|
374
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
|
+
}
|
|
375
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
|
+
}
|
|
376
466
|
if (hit.target !== 'anchor') {
|
|
377
467
|
return { horizontal: true, vertical: true };
|
|
378
468
|
}
|
|
@@ -430,6 +520,7 @@ export class KLineChartsSceneAdapter {
|
|
|
430
520
|
preventDefault?.();
|
|
431
521
|
},
|
|
432
522
|
onDrawEnd: ({ overlay }) => {
|
|
523
|
+
this.#finishTouchPrecisionDrawing(overlay.id);
|
|
433
524
|
if (drawing &&
|
|
434
525
|
this.#interactivePriceMeasurement?.source.id === source.id) {
|
|
435
526
|
this.#interactivePriceMeasurement = undefined;
|
|
@@ -463,6 +554,7 @@ export class KLineChartsSceneAdapter {
|
|
|
463
554
|
}
|
|
464
555
|
},
|
|
465
556
|
onRemoved: ({ overlay }) => {
|
|
557
|
+
this.#finishTouchPrecisionDrawing(overlay.id);
|
|
466
558
|
if (this.#interactivePriceMeasurement?.source.id === overlay.id) {
|
|
467
559
|
this.#interactivePriceMeasurement = undefined;
|
|
468
560
|
}
|
|
@@ -496,32 +588,191 @@ export class KLineChartsSceneAdapter {
|
|
|
496
588
|
throw error;
|
|
497
589
|
}
|
|
498
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
|
+
}
|
|
499
702
|
#installInteractionListeners() {
|
|
500
703
|
this.#container.addEventListener('mousedown', this.#handleRightMouseDown, true);
|
|
704
|
+
this.#container.addEventListener('click', this.#handleCompatibilityClick, true);
|
|
501
705
|
this.#container.addEventListener('contextmenu', this.#handleContextMenu, true);
|
|
502
706
|
this.#container.addEventListener('pointerdown', this.#handlePointerDown, true);
|
|
503
707
|
this.#container.addEventListener('pointermove', this.#handlePointerMove, true);
|
|
504
708
|
this.#container.addEventListener('pointerup', this.#handlePointerUp, true);
|
|
505
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
|
+
});
|
|
506
726
|
window.addEventListener('keydown', this.#handleKeyDown);
|
|
507
727
|
window.addEventListener('blur', this.#handleWindowBlur);
|
|
508
728
|
}
|
|
509
729
|
#removeInteractionListeners() {
|
|
510
730
|
this.#container.removeEventListener('mousedown', this.#handleRightMouseDown, true);
|
|
731
|
+
this.#container.removeEventListener('click', this.#handleCompatibilityClick, true);
|
|
511
732
|
this.#container.removeEventListener('contextmenu', this.#handleContextMenu, true);
|
|
512
733
|
this.#container.removeEventListener('pointerdown', this.#handlePointerDown, true);
|
|
513
734
|
this.#container.removeEventListener('pointermove', this.#handlePointerMove, true);
|
|
514
735
|
this.#container.removeEventListener('pointerup', this.#handlePointerUp, true);
|
|
515
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);
|
|
516
741
|
window.removeEventListener('keydown', this.#handleKeyDown);
|
|
517
742
|
window.removeEventListener('blur', this.#handleWindowBlur);
|
|
518
743
|
}
|
|
519
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
|
+
}
|
|
520
753
|
if (event.button === 2) {
|
|
521
754
|
// 阻止事件到达 KLineCharts;不 preventDefault,保留浏览器原生右键菜单。
|
|
522
755
|
event.stopPropagation();
|
|
523
756
|
}
|
|
524
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
|
+
};
|
|
525
776
|
#handleContextMenu = (event) => {
|
|
526
777
|
// KLineCharts 会 preventDefault。捕获阶段截断传播,但 Baron 本身不拦截菜单。
|
|
527
778
|
event.stopPropagation();
|
|
@@ -598,60 +849,40 @@ export class KLineChartsSceneAdapter {
|
|
|
598
849
|
#overlayGeometries() {
|
|
599
850
|
const geometries = [];
|
|
600
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');
|
|
601
855
|
for (let sceneIndex = 0; sceneIndex < active.length; sceneIndex++) {
|
|
602
856
|
const overlay = active[sceneIndex];
|
|
603
857
|
if (overlay === undefined || !overlay.visible) {
|
|
604
858
|
continue;
|
|
605
859
|
}
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
geometries.push(
|
|
632
|
-
overlayId: overlay.id,
|
|
633
|
-
sceneIndex,
|
|
634
|
-
zLevel: overlay.zLevel,
|
|
635
|
-
locked: overlay.locked,
|
|
636
|
-
anchors: [start, end],
|
|
637
|
-
bodySegments: [[start, end]],
|
|
638
|
-
});
|
|
639
|
-
continue;
|
|
640
|
-
}
|
|
641
|
-
if (overlay.points !== undefined && overlay.points.length >= 2) {
|
|
642
|
-
const anchors = overlay.points.map((point) => this.#toPixel(point, overlay.paneId));
|
|
643
|
-
const bodySegments = [];
|
|
644
|
-
for (let pointIndex = 1; pointIndex < anchors.length; pointIndex++) {
|
|
645
|
-
bodySegments.push([anchors[pointIndex - 1], anchors[pointIndex]]);
|
|
646
|
-
}
|
|
647
|
-
geometries.push({
|
|
648
|
-
overlayId: overlay.id,
|
|
649
|
-
sceneIndex,
|
|
650
|
-
zLevel: overlay.zLevel,
|
|
651
|
-
locked: overlay.locked,
|
|
652
|
-
anchors,
|
|
653
|
-
bodySegments,
|
|
654
|
-
});
|
|
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);
|
|
655
886
|
}
|
|
656
887
|
}
|
|
657
888
|
return geometries;
|
|
@@ -695,13 +926,102 @@ export class KLineChartsSceneAdapter {
|
|
|
695
926
|
this.#emit({ type: 'scene-error', issues: structuredClone(error.issues) });
|
|
696
927
|
}
|
|
697
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
|
+
}
|
|
698
1014
|
#handlePointerDown = (event) => {
|
|
699
1015
|
if (this.#disposed ||
|
|
700
1016
|
event.button !== 0 ||
|
|
701
1017
|
this.#pointerInteraction !== undefined ||
|
|
1018
|
+
this.#deselectingPointerId !== undefined ||
|
|
702
1019
|
!this.#mutationsEnabled) {
|
|
703
1020
|
return;
|
|
704
1021
|
}
|
|
1022
|
+
if (this.#routeTouchPrecisionPointerDown(event)) {
|
|
1023
|
+
return;
|
|
1024
|
+
}
|
|
705
1025
|
const coordinate = this.#pointerCoordinate(event);
|
|
706
1026
|
const measurement = this.#interactivePriceMeasurement;
|
|
707
1027
|
if (measurement !== undefined) {
|
|
@@ -732,9 +1052,22 @@ export class KLineChartsSceneAdapter {
|
|
|
732
1052
|
if (this.#drawingInProgressId !== null) {
|
|
733
1053
|
return;
|
|
734
1054
|
}
|
|
735
|
-
const hit = hitTestOverlayGeometries(coordinate, this.#overlayGeometries());
|
|
1055
|
+
const hit = hitTestOverlayGeometries(coordinate, this.#overlayGeometries(), this.#hitTolerance(event.pointerType));
|
|
736
1056
|
if (hit === null) {
|
|
737
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
|
+
}
|
|
738
1071
|
if (selected !== undefined && isControlledInteractionOverlay(selected)) {
|
|
739
1072
|
this.#selectOverlay(null);
|
|
740
1073
|
}
|
|
@@ -745,7 +1078,8 @@ export class KLineChartsSceneAdapter {
|
|
|
745
1078
|
return;
|
|
746
1079
|
}
|
|
747
1080
|
this.#selectOverlay(before.id);
|
|
748
|
-
if (!isControlledInteractionOverlay(before)
|
|
1081
|
+
if (!isControlledInteractionOverlay(before) &&
|
|
1082
|
+
this.#drawingInteraction.exclusiveSelection !== true) {
|
|
749
1083
|
return;
|
|
750
1084
|
}
|
|
751
1085
|
event.preventDefault();
|
|
@@ -753,7 +1087,12 @@ export class KLineChartsSceneAdapter {
|
|
|
753
1087
|
if (hit.locked) {
|
|
754
1088
|
return;
|
|
755
1089
|
}
|
|
756
|
-
|
|
1090
|
+
try {
|
|
1091
|
+
this.#container.setPointerCapture(event.pointerId);
|
|
1092
|
+
}
|
|
1093
|
+
catch {
|
|
1094
|
+
// Synthetic PointerEvents do not own browser pointer capture.
|
|
1095
|
+
}
|
|
757
1096
|
this.#interactionDimensions = this.#dimensionsForHit(before, hit);
|
|
758
1097
|
this.#pointerInteraction = {
|
|
759
1098
|
pointerId: event.pointerId,
|
|
@@ -766,6 +1105,14 @@ export class KLineChartsSceneAdapter {
|
|
|
766
1105
|
};
|
|
767
1106
|
};
|
|
768
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
|
+
}
|
|
769
1116
|
const interaction = this.#pointerInteraction;
|
|
770
1117
|
if (interaction === undefined || interaction.pointerId !== event.pointerId) {
|
|
771
1118
|
return;
|
|
@@ -797,6 +1144,7 @@ export class KLineChartsSceneAdapter {
|
|
|
797
1144
|
throw new SceneError('RUNTIME_INIT_FAILED', `/overlays/${index}`, `KLineCharts failed to preview Overlay ${normalized.id}.`);
|
|
798
1145
|
}
|
|
799
1146
|
if (this.#workspaceMode) {
|
|
1147
|
+
interaction.candidate = candidate;
|
|
800
1148
|
this.#setActiveOverlays(overlays);
|
|
801
1149
|
return;
|
|
802
1150
|
}
|
|
@@ -816,6 +1164,19 @@ export class KLineChartsSceneAdapter {
|
|
|
816
1164
|
}
|
|
817
1165
|
};
|
|
818
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
|
+
}
|
|
819
1180
|
const interaction = this.#pointerInteraction;
|
|
820
1181
|
if (interaction === undefined || interaction.pointerId !== event.pointerId) {
|
|
821
1182
|
return;
|
|
@@ -823,6 +1184,7 @@ export class KLineChartsSceneAdapter {
|
|
|
823
1184
|
event.preventDefault();
|
|
824
1185
|
event.stopImmediatePropagation();
|
|
825
1186
|
this.#pointerInteraction = undefined;
|
|
1187
|
+
const interactionDimensions = this.#interactionDimensions;
|
|
826
1188
|
this.#interactionDimensions = { horizontal: false, vertical: false };
|
|
827
1189
|
this.#stopPointerCapture(interaction);
|
|
828
1190
|
if (!interaction.started) {
|
|
@@ -841,7 +1203,7 @@ export class KLineChartsSceneAdapter {
|
|
|
841
1203
|
type: 'updated',
|
|
842
1204
|
id: overlay.id,
|
|
843
1205
|
drawing: snapshotOfDrawing(sceneOverlayToDrawing(overlay, drawing)),
|
|
844
|
-
editDimensions: structuredClone(
|
|
1206
|
+
editDimensions: structuredClone(interactionDimensions),
|
|
845
1207
|
});
|
|
846
1208
|
}
|
|
847
1209
|
return;
|
|
@@ -856,21 +1218,56 @@ export class KLineChartsSceneAdapter {
|
|
|
856
1218
|
this.#emit({ type: 'overlay-updated', overlay: committed });
|
|
857
1219
|
};
|
|
858
1220
|
#handlePointerCancel = (event) => {
|
|
1221
|
+
if (this.#touchPrecisionPointer?.pointerId === event.pointerId) {
|
|
1222
|
+
event.preventDefault();
|
|
1223
|
+
event.stopImmediatePropagation();
|
|
1224
|
+
this.#cancelTouchPrecisionDrawing(true);
|
|
1225
|
+
return;
|
|
1226
|
+
}
|
|
859
1227
|
if (this.#pointerInteraction?.pointerId === event.pointerId) {
|
|
860
1228
|
event.preventDefault();
|
|
861
1229
|
event.stopImmediatePropagation();
|
|
862
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);
|
|
863
1241
|
}
|
|
864
1242
|
};
|
|
865
1243
|
#handleKeyDown = (event) => {
|
|
1244
|
+
if (event.key === 'Escape' && this.#touchPrecisionDrawing !== undefined) {
|
|
1245
|
+
this.#cancelTouchPrecisionDrawing(true);
|
|
1246
|
+
return;
|
|
1247
|
+
}
|
|
866
1248
|
if (event.key === 'Escape' && this.#pointerInteraction !== undefined) {
|
|
867
1249
|
this.#cancelInteraction('escape');
|
|
868
1250
|
}
|
|
1251
|
+
if (event.key === 'Escape' &&
|
|
1252
|
+
this.#drawingInteraction.exclusiveSelection === true &&
|
|
1253
|
+
this.#selectedOverlayId !== null) {
|
|
1254
|
+
this.#selectOverlay(null);
|
|
1255
|
+
}
|
|
869
1256
|
};
|
|
870
1257
|
#handleWindowBlur = () => {
|
|
1258
|
+
if (this.#touchPrecisionDrawing !== undefined) {
|
|
1259
|
+
this.#cancelTouchPrecisionDrawing(true);
|
|
1260
|
+
}
|
|
871
1261
|
if (this.#pointerInteraction !== undefined) {
|
|
872
1262
|
this.#cancelInteraction('window-blur');
|
|
873
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
|
+
}
|
|
874
1271
|
};
|
|
875
1272
|
subscribe(listener) {
|
|
876
1273
|
this.#assertActive();
|
|
@@ -1080,6 +1477,9 @@ export class KLineChartsSceneAdapter {
|
|
|
1080
1477
|
throw new SceneError('RUNTIME_INIT_FAILED', '/drawings', `KLineCharts failed to start Drawing ${request.id}.`);
|
|
1081
1478
|
}
|
|
1082
1479
|
this.#drawingInProgressId = request.id;
|
|
1480
|
+
if (request.type === 'segment') {
|
|
1481
|
+
this.#prepareTouchPrecisionDrawing(request.id, overlay.paneId);
|
|
1482
|
+
}
|
|
1083
1483
|
return request.id;
|
|
1084
1484
|
}
|
|
1085
1485
|
listDrawings() {
|
|
@@ -1542,6 +1942,11 @@ export class KLineChartsSceneAdapter {
|
|
|
1542
1942
|
return;
|
|
1543
1943
|
}
|
|
1544
1944
|
this.#cancelInteraction('destroy');
|
|
1945
|
+
this.#deselectingPointerId = undefined;
|
|
1946
|
+
this.#leaveExclusiveSelection();
|
|
1947
|
+
this.#cancelTouchPrecisionDrawing(false);
|
|
1948
|
+
this.#touchPrecisionGuide?.destroy();
|
|
1949
|
+
this.#touchPrecisionGuide = undefined;
|
|
1545
1950
|
this.#interactivePriceMeasurement = undefined;
|
|
1546
1951
|
this.#drawingInProgressId = null;
|
|
1547
1952
|
this.#disposed = true;
|