@baron1996/klinecharts-adapter 0.1.0 → 0.2.1
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/README.md +6 -0
- package/dist/adapter.d.ts +40 -1
- package/dist/adapter.d.ts.map +1 -1
- package/dist/adapter.js +408 -22
- package/dist/conversion/overlays.d.ts +4 -1
- package/dist/conversion/overlays.d.ts.map +1 -1
- package/dist/conversion/overlays.js +26 -20
- 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/conversion/price.d.ts +6 -0
- package/dist/conversion/price.d.ts.map +1 -0
- package/dist/conversion/price.js +48 -0
- 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
|
@@ -18,14 +18,17 @@ 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。 */
|
|
27
30
|
export declare function toEngineOverlayDrawing(source: OverlayDrawingSource, idMap: EngineIdMap, callbacks: EngineOverlayCallbacks): OverlayCreate;
|
|
28
|
-
export declare function fromEngineOverlay(engine: Overlay, source: OverlaySourceSnapshot, idMap: EngineIdMap, path: string): SceneOverlay;
|
|
31
|
+
export declare function fromEngineOverlay(engine: Overlay, source: OverlaySourceSnapshot, idMap: EngineIdMap, path: string, pricePrecision: number): SceneOverlay;
|
|
29
32
|
/** 在引擎内创建场景的全部标注。 */
|
|
30
33
|
export declare function createSceneOverlays(scene: ChartScene, chart: {
|
|
31
34
|
createOverlay(value: OverlayCreate): string | null | Array<string | null>;
|
|
@@ -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;
|
|
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"}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { SceneError } from '@baron1996/kline-scene-schema';
|
|
2
2
|
import { requireMappedId } from './id-map.js';
|
|
3
|
+
import { normalizePriceValue } from './price.js';
|
|
3
4
|
import { isSupportedOverlay } from '../registry/overlays.js';
|
|
4
5
|
function toLineStyle(style) {
|
|
5
6
|
return {
|
|
@@ -80,6 +81,7 @@ function toPoints(overlay) {
|
|
|
80
81
|
return [structuredClone(overlay.point ?? {})];
|
|
81
82
|
case 'rectangle':
|
|
82
83
|
case 'arrow':
|
|
84
|
+
case 'priceMeasurement':
|
|
83
85
|
return [structuredClone(overlay.start ?? {}), structuredClone(overlay.end ?? {})];
|
|
84
86
|
}
|
|
85
87
|
}
|
|
@@ -143,7 +145,7 @@ export function toEngineOverlayDrawing(source, idMap, callbacks) {
|
|
|
143
145
|
}
|
|
144
146
|
return value;
|
|
145
147
|
}
|
|
146
|
-
function requirePoint(points, index, requireTimestamp, requireValue, path) {
|
|
148
|
+
function requirePoint(points, index, requireTimestamp, requireValue, path, pricePrecision) {
|
|
147
149
|
const point = points[index];
|
|
148
150
|
if (point === undefined ||
|
|
149
151
|
(requireTimestamp && !Number.isSafeInteger(point.timestamp)) ||
|
|
@@ -152,7 +154,9 @@ function requirePoint(points, index, requireTimestamp, requireValue, path) {
|
|
|
152
154
|
}
|
|
153
155
|
return {
|
|
154
156
|
timestamp: point.timestamp ?? 0,
|
|
155
|
-
value:
|
|
157
|
+
value: requireValue
|
|
158
|
+
? normalizePriceValue(point.value, pricePrecision, `${path}/value`)
|
|
159
|
+
: 0,
|
|
156
160
|
};
|
|
157
161
|
}
|
|
158
162
|
function requireText(overlay, path) {
|
|
@@ -187,23 +191,24 @@ function baseFromEngine(engine, source, idMap, path) {
|
|
|
187
191
|
}
|
|
188
192
|
return base;
|
|
189
193
|
}
|
|
190
|
-
export function fromEngineOverlay(engine, source, idMap, path) {
|
|
194
|
+
export function fromEngineOverlay(engine, source, idMap, path, pricePrecision) {
|
|
191
195
|
const base = baseFromEngine(engine, source, idMap, path);
|
|
192
196
|
const points = engine.points;
|
|
197
|
+
const readPoint = (index, requireTimestamp, requireValue, pointPath) => requirePoint(points, index, requireTimestamp, requireValue, pointPath, pricePrecision);
|
|
193
198
|
switch (base.type) {
|
|
194
199
|
case 'horizontalStraightLine':
|
|
195
200
|
case 'priceLine': {
|
|
196
|
-
const point =
|
|
201
|
+
const point = readPoint(0, false, true, `${path}/anchor`);
|
|
197
202
|
return { ...base, anchor: { value: point.value } };
|
|
198
203
|
}
|
|
199
204
|
case 'verticalStraightLine': {
|
|
200
|
-
const point =
|
|
205
|
+
const point = readPoint(0, true, false, `${path}/anchor`);
|
|
201
206
|
return { ...base, anchor: { timestamp: point.timestamp } };
|
|
202
207
|
}
|
|
203
208
|
case 'horizontalRayLine':
|
|
204
209
|
case 'horizontalSegment': {
|
|
205
|
-
const start =
|
|
206
|
-
const end =
|
|
210
|
+
const start = readPoint(0, true, true, `${path}/points/0`);
|
|
211
|
+
const end = readPoint(1, true, true, `${path}/points/1`);
|
|
207
212
|
return {
|
|
208
213
|
...base,
|
|
209
214
|
value: start.value,
|
|
@@ -213,8 +218,8 @@ export function fromEngineOverlay(engine, source, idMap, path) {
|
|
|
213
218
|
}
|
|
214
219
|
case 'verticalRayLine':
|
|
215
220
|
case 'verticalSegment': {
|
|
216
|
-
const start =
|
|
217
|
-
const end =
|
|
221
|
+
const start = readPoint(0, true, true, `${path}/points/0`);
|
|
222
|
+
const end = readPoint(1, true, true, `${path}/points/1`);
|
|
218
223
|
return {
|
|
219
224
|
...base,
|
|
220
225
|
timestamp: start.timestamp,
|
|
@@ -229,8 +234,8 @@ export function fromEngineOverlay(engine, source, idMap, path) {
|
|
|
229
234
|
return {
|
|
230
235
|
...base,
|
|
231
236
|
points: [
|
|
232
|
-
|
|
233
|
-
|
|
237
|
+
readPoint(0, true, true, `${path}/points/0`),
|
|
238
|
+
readPoint(1, true, true, `${path}/points/1`),
|
|
234
239
|
],
|
|
235
240
|
};
|
|
236
241
|
}
|
|
@@ -239,14 +244,14 @@ export function fromEngineOverlay(engine, source, idMap, path) {
|
|
|
239
244
|
return {
|
|
240
245
|
...base,
|
|
241
246
|
points: [
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
247
|
+
readPoint(0, true, true, `${path}/points/0`),
|
|
248
|
+
readPoint(1, true, true, `${path}/points/1`),
|
|
249
|
+
readPoint(2, true, true, `${path}/points/2`),
|
|
245
250
|
],
|
|
246
251
|
};
|
|
247
252
|
}
|
|
248
253
|
case 'brush': {
|
|
249
|
-
const converted = points.map((_point, index) =>
|
|
254
|
+
const converted = points.map((_point, index) => readPoint(index, true, true, `${path}/points/${index}`));
|
|
250
255
|
const first = converted[0];
|
|
251
256
|
if (first === undefined) {
|
|
252
257
|
throw new SceneError('EXPORT_INVALID', `${path}/points`, 'Brush has no points.');
|
|
@@ -257,7 +262,7 @@ export function fromEngineOverlay(engine, source, idMap, path) {
|
|
|
257
262
|
};
|
|
258
263
|
}
|
|
259
264
|
case 'simpleTag': {
|
|
260
|
-
const point =
|
|
265
|
+
const point = readPoint(0, false, true, `${path}/anchor`);
|
|
261
266
|
return {
|
|
262
267
|
...base,
|
|
263
268
|
anchor: { value: point.value },
|
|
@@ -269,20 +274,21 @@ export function fromEngineOverlay(engine, source, idMap, path) {
|
|
|
269
274
|
case 'text':
|
|
270
275
|
return {
|
|
271
276
|
...base,
|
|
272
|
-
point:
|
|
277
|
+
point: readPoint(0, true, true, `${path}/point`),
|
|
273
278
|
text: requireText(engine, `${path}/text`),
|
|
274
279
|
};
|
|
275
280
|
case 'rectangle':
|
|
276
281
|
case 'arrow':
|
|
282
|
+
case 'priceMeasurement':
|
|
277
283
|
return {
|
|
278
284
|
...base,
|
|
279
|
-
start:
|
|
280
|
-
end:
|
|
285
|
+
start: readPoint(0, true, true, `${path}/start`),
|
|
286
|
+
end: readPoint(1, true, true, `${path}/end`),
|
|
281
287
|
};
|
|
282
288
|
case 'crossLine':
|
|
283
289
|
return {
|
|
284
290
|
...base,
|
|
285
|
-
point:
|
|
291
|
+
point: readPoint(0, true, true, `${path}/point`),
|
|
286
292
|
};
|
|
287
293
|
}
|
|
288
294
|
}
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
-
import type { ChartScene } from '@baron1996/kline-scene-schema';
|
|
1
|
+
import type { ChartScene, YAxis as SceneYAxis } from '@baron1996/kline-scene-schema';
|
|
2
2
|
import type { Chart } from 'klinecharts';
|
|
3
3
|
import type { EngineIdMap } from './id-map.js';
|
|
4
|
+
/** 原子提交 Scene 前,将单条轴映射到引擎的正式 normal/logarithm 名称。 */
|
|
5
|
+
export declare function overrideSceneYAxis(chart: Chart, idMap: EngineIdMap, axis: SceneYAxis, paneId: string, path: string): void;
|
|
4
6
|
/** 按 Scene 顺序创建 Pane、Y 轴和指标,并核对每个映射。 */
|
|
5
7
|
export declare function applyPanes(scene: ChartScene, chart: Chart, idMap: EngineIdMap): void;
|
|
6
8
|
//# sourceMappingURL=panes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"panes.d.ts","sourceRoot":"","sources":["../../src/conversion/panes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,
|
|
1
|
+
{"version":3,"file":"panes.d.ts","sourceRoot":"","sources":["../../src/conversion/panes.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,KAAK,IAAI,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAErF,OAAO,KAAK,EAAE,KAAK,EAAS,MAAM,aAAa,CAAC;AAEhD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AA8B/C,qDAAqD;AACrD,wBAAgB,kBAAkB,CACjC,KAAK,EAAE,KAAK,EACZ,KAAK,EAAE,WAAW,EAClB,IAAI,EAAE,UAAU,EAChB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,GACV,IAAI,CAIN;AAED,wCAAwC;AACxC,wBAAgB,UAAU,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,WAAW,GAAG,IAAI,CAuEpF"}
|
package/dist/conversion/panes.js
CHANGED
|
@@ -5,7 +5,7 @@ function axisOverride(axis, enginePaneId, engineAxisId) {
|
|
|
5
5
|
return {
|
|
6
6
|
id: engineAxisId,
|
|
7
7
|
paneId: enginePaneId,
|
|
8
|
-
name: 'normal',
|
|
8
|
+
name: axis.scale === 'logarithmic' ? 'logarithm' : 'normal',
|
|
9
9
|
reverse: axis.reverse,
|
|
10
10
|
inside: axis.inside,
|
|
11
11
|
position: axis.position,
|
|
@@ -17,6 +17,12 @@ function axisOverride(axis, enginePaneId, engineAxisId) {
|
|
|
17
17
|
needWidget: true,
|
|
18
18
|
};
|
|
19
19
|
}
|
|
20
|
+
/** 原子提交 Scene 前,将单条轴映射到引擎的正式 normal/logarithm 名称。 */
|
|
21
|
+
export function overrideSceneYAxis(chart, idMap, axis, paneId, path) {
|
|
22
|
+
const enginePaneId = requireMappedId(idMap.paneToEngine, paneId, `${path}/paneId`, 'Pane');
|
|
23
|
+
const engineAxisId = requireMappedId(idMap.yAxisToEngine, axis.id, `${path}/id`, 'Y-axis');
|
|
24
|
+
chart.overrideYAxis(axisOverride(axis, enginePaneId, engineAxisId));
|
|
25
|
+
}
|
|
20
26
|
/** 按 Scene 顺序创建 Pane、Y 轴和指标,并核对每个映射。 */
|
|
21
27
|
export function applyPanes(scene, chart, idMap) {
|
|
22
28
|
for (let paneIndex = 0; paneIndex < scene.panes.length; paneIndex++) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"price.d.ts","sourceRoot":"","sources":["../../src/conversion/price.ts"],"names":[],"mappings":"AAwBA;;;GAGG;AACH,wBAAgB,mBAAmB,CAClC,KAAK,EAAE,MAAM,EACb,cAAc,EAAE,MAAM,EACtB,IAAI,EAAE,MAAM,GACV,MAAM,CAkCR"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { SceneError } from '@baron1996/kline-scene-schema';
|
|
2
|
+
function toDecimalParts(value) {
|
|
3
|
+
const [coefficient = '', exponentText] = value.toString().toLowerCase().split('e');
|
|
4
|
+
const [integer = '', fraction = ''] = coefficient.split('.');
|
|
5
|
+
return {
|
|
6
|
+
digits: BigInt(`${integer}${fraction}`),
|
|
7
|
+
exponent: Number(exponentText ?? 0) - fraction.length,
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
function toDecimalNumber(value, precision, negative) {
|
|
11
|
+
const digits = value.toString().padStart(precision + 1, '0');
|
|
12
|
+
const unsigned = precision === 0
|
|
13
|
+
? digits
|
|
14
|
+
: `${digits.slice(0, -precision)}.${digits.slice(-precision)}`;
|
|
15
|
+
return Number(`${negative ? '-' : ''}${unsigned}`);
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 把引擎价格按 Scene 标的精度做十进制舍入。
|
|
19
|
+
* 舍入规则为最接近,恰好半值时远离零;任何负零统一输出正零。
|
|
20
|
+
*/
|
|
21
|
+
export function normalizePriceValue(value, pricePrecision, path) {
|
|
22
|
+
if (!Number.isFinite(value)) {
|
|
23
|
+
throw new SceneError('EXPORT_INVALID', path, 'KLineCharts returned a non-finite price value.');
|
|
24
|
+
}
|
|
25
|
+
if (!Number.isInteger(pricePrecision) ||
|
|
26
|
+
pricePrecision < 0 ||
|
|
27
|
+
pricePrecision > 16) {
|
|
28
|
+
throw new SceneError('EXPORT_INVALID', '/symbol/pricePrecision', 'Scene pricePrecision must be an integer from 0 through 16.');
|
|
29
|
+
}
|
|
30
|
+
const negative = value < 0;
|
|
31
|
+
const { digits, exponent } = toDecimalParts(Math.abs(value));
|
|
32
|
+
const scaledExponent = exponent + pricePrecision;
|
|
33
|
+
let rounded;
|
|
34
|
+
if (scaledExponent >= 0) {
|
|
35
|
+
rounded = digits * (10n ** BigInt(scaledExponent));
|
|
36
|
+
}
|
|
37
|
+
else {
|
|
38
|
+
const divisor = 10n ** BigInt(-scaledExponent);
|
|
39
|
+
const quotient = digits / divisor;
|
|
40
|
+
const remainder = digits % divisor;
|
|
41
|
+
rounded = quotient + (remainder * 2n >= divisor ? 1n : 0n);
|
|
42
|
+
}
|
|
43
|
+
const normalized = toDecimalNumber(rounded, pricePrecision, negative);
|
|
44
|
+
if (!Number.isFinite(normalized)) {
|
|
45
|
+
throw new SceneError('EXPORT_INVALID', path, 'Normalized price value is not finite.');
|
|
46
|
+
}
|
|
47
|
+
return Object.is(normalized, -0) ? 0 : normalized;
|
|
48
|
+
}
|
package/dist/engine.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,+BAA+B,CAAC;AAEhE,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAUzC,MAAM,MAAM,iBAAiB,GAAG,cAAc,aAAa,CAAC,CAAC;AAE7D,MAAM,WAAW,YAAY;IAC5B,QAAQ,CAAC,KAAK,EAAE,KAAK,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,iBAAiB,CAAC;CACnC;AAmBD,qCAAqC;AACrC,wBAAsB,YAAY,CACjC,SAAS,EAAE,WAAW,EACtB,KAAK,EAAE,UAAU,GACf,OAAO,CAAC,YAAY,CAAC,CA+BvB"}
|
package/dist/engine.js
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
import { SceneError } from '@baron1996/kline-scene-schema';
|
|
2
2
|
import { toKLineChartsOptions } from './conversion/chart-options.js';
|
|
3
3
|
import { createStaticDataLoader } from './static-data-loader.js';
|
|
4
|
-
import { KLINECHARTS_ENGINE_VERSION, KLINECHARTS_RUNTIME_VERSION, } from './version.js';
|
|
4
|
+
import { KLINECHARTS_ENGINE_VERSION, KLINECHARTS_RUNTIME_VERSION, SUPPORTED_KLINECHARTS_RUNTIME_VERSIONS, } from './version.js';
|
|
5
5
|
function assertRuntimeIdentity(scene, actualEngineVersion) {
|
|
6
6
|
if (scene.runtime.engine !== 'klinecharts' ||
|
|
7
7
|
scene.runtime.engineVersion !== KLINECHARTS_ENGINE_VERSION ||
|
|
8
8
|
actualEngineVersion !== KLINECHARTS_ENGINE_VERSION ||
|
|
9
|
-
scene.runtime.runtimeVersion
|
|
10
|
-
throw new SceneError('ENGINE_VERSION_MISMATCH', '/runtime', `Expected klinecharts ${KLINECHARTS_ENGINE_VERSION} and Runtime ${KLINECHARTS_RUNTIME_VERSION}; received engine ${actualEngineVersion}.`);
|
|
9
|
+
!SUPPORTED_KLINECHARTS_RUNTIME_VERSIONS.includes(scene.runtime.runtimeVersion)) {
|
|
10
|
+
throw new SceneError('ENGINE_VERSION_MISMATCH', '/runtime', `Expected klinecharts ${KLINECHARTS_ENGINE_VERSION} and a supported Runtime through ${KLINECHARTS_RUNTIME_VERSION}; received engine ${actualEngineVersion}.`);
|
|
11
11
|
}
|
|
12
12
|
}
|
|
13
13
|
/** 按固定顺序创建并初始化唯一的 KLineCharts 引擎。 */
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { registerOverlay } from 'klinecharts';
|
|
2
|
+
type KLineOverlayTemplate = Parameters<typeof registerOverlay>[0];
|
|
3
|
+
export interface PriceMeasurementDisplay {
|
|
4
|
+
readonly absoluteChange: number;
|
|
5
|
+
readonly percentageChange: number;
|
|
6
|
+
readonly label: string;
|
|
7
|
+
}
|
|
8
|
+
/** 仅派生显示值;绝对变化、百分比和 label 永不写入 Scene。 */
|
|
9
|
+
export declare function derivePriceMeasurementDisplay(startValue: number, endValue: number, pricePrecision: number): PriceMeasurementDisplay;
|
|
10
|
+
/** 量度工具只从两个引擎数据点派生显示文字,派生值不进入 Scene。 */
|
|
11
|
+
export declare const priceMeasurementOverlay: KLineOverlayTemplate;
|
|
12
|
+
export {};
|
|
13
|
+
//# sourceMappingURL=price-measurement.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"price-measurement.d.ts","sourceRoot":"","sources":["../../src/extensions/price-measurement.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAGnD,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AAOlE,MAAM,WAAW,uBAAuB;IACvC,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;IAChC,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,yCAAyC;AACzC,wBAAgB,6BAA6B,CAC5C,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GACpB,uBAAuB,CAsBzB;AAED,wCAAwC;AACxC,eAAO,MAAM,uBAAuB,EAAE,oBA6CrC,CAAC"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { SceneError } from '@baron1996/kline-scene-schema';
|
|
2
|
+
function signedFixed(value, precision) {
|
|
3
|
+
const prefix = value > 0 ? '+' : '';
|
|
4
|
+
return `${prefix}${value.toFixed(precision)}`;
|
|
5
|
+
}
|
|
6
|
+
/** 仅派生显示值;绝对变化、百分比和 label 永不写入 Scene。 */
|
|
7
|
+
export function derivePriceMeasurementDisplay(startValue, endValue, pricePrecision) {
|
|
8
|
+
if (!Number.isFinite(startValue) ||
|
|
9
|
+
!Number.isFinite(endValue) ||
|
|
10
|
+
startValue <= 0 ||
|
|
11
|
+
!Number.isInteger(pricePrecision) ||
|
|
12
|
+
pricePrecision < 0 ||
|
|
13
|
+
pricePrecision > 16) {
|
|
14
|
+
throw new SceneError('SCENE_SCHEMA_INVALID', '/overlays/priceMeasurement', 'Price measurement display inputs must be positive finite prices and a 0..16 precision.');
|
|
15
|
+
}
|
|
16
|
+
const absoluteChange = endValue - startValue;
|
|
17
|
+
const percentageChange = absoluteChange / startValue * 100;
|
|
18
|
+
return {
|
|
19
|
+
absoluteChange,
|
|
20
|
+
percentageChange,
|
|
21
|
+
label: `${signedFixed(absoluteChange, pricePrecision)} (${signedFixed(percentageChange, 2)}%)`,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** 量度工具只从两个引擎数据点派生显示文字,派生值不进入 Scene。 */
|
|
25
|
+
export const priceMeasurementOverlay = {
|
|
26
|
+
name: 'priceMeasurement',
|
|
27
|
+
totalStep: 3,
|
|
28
|
+
needDefaultPointFigure: true,
|
|
29
|
+
needDefaultXAxisFigure: true,
|
|
30
|
+
needDefaultYAxisFigure: true,
|
|
31
|
+
createPointFigures: ({ chart, coordinates, overlay }) => {
|
|
32
|
+
const startCoordinate = coordinates[0];
|
|
33
|
+
const endCoordinate = coordinates[1];
|
|
34
|
+
const startPoint = overlay.points[0];
|
|
35
|
+
const endPoint = overlay.points[1];
|
|
36
|
+
if (startCoordinate === undefined ||
|
|
37
|
+
endCoordinate === undefined ||
|
|
38
|
+
startPoint?.value === undefined ||
|
|
39
|
+
endPoint?.value === undefined) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
const pricePrecision = chart.getSymbol()?.pricePrecision ?? 0;
|
|
43
|
+
const { label } = derivePriceMeasurementDisplay(startPoint.value, endPoint.value, pricePrecision);
|
|
44
|
+
return [
|
|
45
|
+
{
|
|
46
|
+
key: 'measurement-body',
|
|
47
|
+
type: 'line',
|
|
48
|
+
attrs: { coordinates: [startCoordinate, endCoordinate] },
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
key: 'measurement-label',
|
|
52
|
+
type: 'text',
|
|
53
|
+
attrs: {
|
|
54
|
+
x: (startCoordinate.x + endCoordinate.x) / 2 + 8,
|
|
55
|
+
y: (startCoordinate.y + endCoordinate.y) / 2 - 8,
|
|
56
|
+
text: label,
|
|
57
|
+
align: 'left',
|
|
58
|
+
baseline: 'bottom',
|
|
59
|
+
},
|
|
60
|
+
ignoreEvent: true,
|
|
61
|
+
},
|
|
62
|
+
];
|
|
63
|
+
},
|
|
64
|
+
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../src/extensions/register.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"register.d.ts","sourceRoot":"","sources":["../../src/extensions/register.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AASnD,KAAK,oBAAoB,GAAG,UAAU,CAAC,OAAO,eAAe,CAAC,CAAC,CAAC,CAAC,CAAC;AAClE,KAAK,eAAe,GAAG,CAAC,QAAQ,EAAE,oBAAoB,KAAK,IAAI,CAAC;AAahE,kCAAkC;AAClC,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,eAAe,GAAG,IAAI,CAQvE;AAED,wBAAgB,4BAA4B,IAAI,OAAO,CAEtD"}
|
|
@@ -2,8 +2,10 @@ import { arrowOverlay } from './arrow.js';
|
|
|
2
2
|
import { calloutOverlay } from './callout.js';
|
|
3
3
|
import { crossLineOverlay } from './cross-line.js';
|
|
4
4
|
import { rectangleOverlay } from './rectangle.js';
|
|
5
|
+
import { priceMeasurementOverlay } from './price-measurement.js';
|
|
5
6
|
import { textOverlay } from './text.js';
|
|
6
7
|
const projectExtensions = [
|
|
8
|
+
priceMeasurementOverlay,
|
|
7
9
|
rectangleOverlay,
|
|
8
10
|
arrowOverlay,
|
|
9
11
|
crossLineOverlay,
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from './adapter.js';
|
|
2
2
|
export * from './errors.js';
|
|
3
3
|
export * from './extensions/register.js';
|
|
4
|
+
export * from './interaction/dragging.js';
|
|
5
|
+
export * from './interaction/hit-testing.js';
|
|
4
6
|
export * from './registry/indicators.js';
|
|
5
7
|
export * from './registry/overlays.js';
|
|
6
8
|
export * from './static-data-loader.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,0BAA0B,CAAC;AACzC,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,cAAc,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,cAAc,CAAC;AAC7B,cAAc,aAAa,CAAC;AAC5B,cAAc,0BAA0B,CAAC;AACzC,cAAc,2BAA2B,CAAC;AAC1C,cAAc,8BAA8B,CAAC;AAC7C,cAAc,0BAA0B,CAAC;AACzC,cAAc,wBAAwB,CAAC;AACvC,cAAc,yBAAyB,CAAC;AACxC,cAAc,cAAc,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
export * from './adapter.js';
|
|
2
2
|
export * from './errors.js';
|
|
3
3
|
export * from './extensions/register.js';
|
|
4
|
+
export * from './interaction/dragging.js';
|
|
5
|
+
export * from './interaction/hit-testing.js';
|
|
4
6
|
export * from './registry/indicators.js';
|
|
5
7
|
export * from './registry/overlays.js';
|
|
6
8
|
export * from './static-data-loader.js';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { SceneOverlay } from '@baron1996/kline-scene-schema';
|
|
2
|
+
export interface DragDataPoint {
|
|
3
|
+
readonly dataIndex: number;
|
|
4
|
+
readonly value: number;
|
|
5
|
+
}
|
|
6
|
+
export interface DragTarget {
|
|
7
|
+
readonly target: 'anchor' | 'body';
|
|
8
|
+
readonly anchorIndex: number | null;
|
|
9
|
+
}
|
|
10
|
+
/** 根据冻结的绝对平移语义构造未提交候选,不修改输入 Overlay。 */
|
|
11
|
+
export declare function createDragCandidate(before: SceneOverlay, dragTarget: DragTarget, origin: DragDataPoint, current: DragDataPoint, timestamps: readonly number[], pricePrecision: number): SceneOverlay;
|
|
12
|
+
//# sourceMappingURL=dragging.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dragging.d.ts","sourceRoot":"","sources":["../../src/interaction/dragging.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,+BAA+B,CAAC;AAKlE,MAAM,WAAW,aAAa;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,UAAU;IAC1B,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;CACpC;AAyBD,wCAAwC;AACxC,wBAAgB,mBAAmB,CAClC,MAAM,EAAE,YAAY,EACpB,UAAU,EAAE,UAAU,EACtB,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,aAAa,EACtB,UAAU,EAAE,SAAS,MAAM,EAAE,EAC7B,cAAc,EAAE,MAAM,GACpB,YAAY,CAgFd"}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { SceneError } from '@baron1996/kline-scene-schema';
|
|
2
|
+
import { normalizePriceValue } from '../conversion/price.js';
|
|
3
|
+
function requireDataIndex(value, path) {
|
|
4
|
+
if (!Number.isFinite(value)) {
|
|
5
|
+
throw new SceneError('INVALID_REFERENCE', path, 'Drag data index must be finite.');
|
|
6
|
+
}
|
|
7
|
+
return Math.round(value);
|
|
8
|
+
}
|
|
9
|
+
function requireTimestamp(timestamps, index, path) {
|
|
10
|
+
const timestamp = timestamps[index];
|
|
11
|
+
if (timestamp === undefined) {
|
|
12
|
+
throw new SceneError('INVALID_REFERENCE', path, 'Drag candidate must remain on an embedded market-data bar.');
|
|
13
|
+
}
|
|
14
|
+
return timestamp;
|
|
15
|
+
}
|
|
16
|
+
/** 根据冻结的绝对平移语义构造未提交候选,不修改输入 Overlay。 */
|
|
17
|
+
export function createDragCandidate(before, dragTarget, origin, current, timestamps, pricePrecision) {
|
|
18
|
+
const deltaValue = current.value - origin.value;
|
|
19
|
+
if (!Number.isFinite(deltaValue)) {
|
|
20
|
+
throw new SceneError('SCENE_SCHEMA_INVALID', '/overlays', 'Drag price delta must be finite.');
|
|
21
|
+
}
|
|
22
|
+
if (before.type === 'horizontalStraightLine') {
|
|
23
|
+
const anchor = before.anchor;
|
|
24
|
+
if (anchor === undefined || !('value' in anchor)) {
|
|
25
|
+
throw new SceneError('SCENE_SCHEMA_INVALID', '/overlays/anchor', 'Missing price anchor.');
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
...structuredClone(before),
|
|
29
|
+
anchor: {
|
|
30
|
+
value: normalizePriceValue(anchor.value + deltaValue, pricePrecision, '/overlays/anchor/value'),
|
|
31
|
+
},
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
if (before.type !== 'priceMeasurement' || before.start === undefined || before.end === undefined) {
|
|
35
|
+
throw new SceneError('SCENE_SCHEMA_INVALID', '/overlays/type', 'Controlled M2 dragging only supports horizontalStraightLine and priceMeasurement.');
|
|
36
|
+
}
|
|
37
|
+
const candidate = structuredClone(before);
|
|
38
|
+
if (dragTarget.target === 'anchor') {
|
|
39
|
+
if (dragTarget.anchorIndex !== 0 && dragTarget.anchorIndex !== 1) {
|
|
40
|
+
throw new SceneError('INVALID_REFERENCE', '/overlays/anchorIndex', 'Invalid anchor index.');
|
|
41
|
+
}
|
|
42
|
+
const index = requireDataIndex(current.dataIndex, '/overlays/anchor/dataIndex');
|
|
43
|
+
const point = {
|
|
44
|
+
timestamp: requireTimestamp(timestamps, index, '/overlays/anchor/timestamp'),
|
|
45
|
+
value: normalizePriceValue(current.value, pricePrecision, '/overlays/anchor/value'),
|
|
46
|
+
};
|
|
47
|
+
if (dragTarget.anchorIndex === 0) {
|
|
48
|
+
candidate.start = point;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
candidate.end = point;
|
|
52
|
+
}
|
|
53
|
+
return candidate;
|
|
54
|
+
}
|
|
55
|
+
const startIndex = timestamps.indexOf(before.start.timestamp);
|
|
56
|
+
const endIndex = timestamps.indexOf(before.end.timestamp);
|
|
57
|
+
if (startIndex < 0 || endIndex < 0) {
|
|
58
|
+
throw new SceneError('INVALID_REFERENCE', '/overlays', 'priceMeasurement endpoints must reference embedded bars before dragging.');
|
|
59
|
+
}
|
|
60
|
+
const deltaIndex = requireDataIndex(current.dataIndex, '/overlays/body/dataIndex') -
|
|
61
|
+
requireDataIndex(origin.dataIndex, '/overlays/body/originDataIndex');
|
|
62
|
+
candidate.start = {
|
|
63
|
+
timestamp: requireTimestamp(timestamps, startIndex + deltaIndex, '/overlays/start/timestamp'),
|
|
64
|
+
value: normalizePriceValue(before.start.value + deltaValue, pricePrecision, '/overlays/start/value'),
|
|
65
|
+
};
|
|
66
|
+
candidate.end = {
|
|
67
|
+
timestamp: requireTimestamp(timestamps, endIndex + deltaIndex, '/overlays/end/timestamp'),
|
|
68
|
+
value: normalizePriceValue(before.end.value + deltaValue, pricePrecision, '/overlays/end/value'),
|
|
69
|
+
};
|
|
70
|
+
return candidate;
|
|
71
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export declare const OVERLAY_BODY_HIT_THRESHOLD_CSS_PX = 12;
|
|
2
|
+
export declare const OVERLAY_ANCHOR_HIT_THRESHOLD_CSS_PX = 14;
|
|
3
|
+
export interface PixelCoordinate {
|
|
4
|
+
readonly x: number;
|
|
5
|
+
readonly y: number;
|
|
6
|
+
}
|
|
7
|
+
export interface OverlayPixelGeometry {
|
|
8
|
+
readonly overlayId: string;
|
|
9
|
+
readonly sceneIndex: number;
|
|
10
|
+
readonly zLevel: number;
|
|
11
|
+
readonly locked: boolean;
|
|
12
|
+
readonly anchors: readonly PixelCoordinate[];
|
|
13
|
+
readonly bodySegments: readonly (readonly [PixelCoordinate, PixelCoordinate])[];
|
|
14
|
+
}
|
|
15
|
+
export interface OverlayHitResult {
|
|
16
|
+
readonly overlayId: string;
|
|
17
|
+
readonly target: 'anchor' | 'body';
|
|
18
|
+
readonly anchorIndex: number | null;
|
|
19
|
+
readonly locked: boolean;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 按冻结契约在 CSS 像素坐标中执行命中测试。
|
|
23
|
+
* 锚点全局优先于主体;同类再按 zLevel、Scene 后序和锚点低索引排序。
|
|
24
|
+
*/
|
|
25
|
+
export declare function hitTestOverlayGeometries(point: PixelCoordinate, geometries: readonly OverlayPixelGeometry[]): OverlayHitResult | null;
|
|
26
|
+
//# sourceMappingURL=hit-testing.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hit-testing.d.ts","sourceRoot":"","sources":["../../src/interaction/hit-testing.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,iCAAiC,KAAK,CAAC;AACpD,eAAO,MAAM,mCAAmC,KAAK,CAAC;AAEtD,MAAM,WAAW,eAAe;IAC/B,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,oBAAoB;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,QAAQ,CAAC,YAAY,EAAE,SAAS,CAAC,SAAS,CAAC,eAAe,EAAE,eAAe,CAAC,CAAC,EAAE,CAAC;CAChF;AAED,MAAM,WAAW,gBAAgB;IAChC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,MAAM,EAAE,QAAQ,GAAG,MAAM,CAAC;IACnC,QAAQ,CAAC,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IACpC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAC;CACzB;AAmDD;;;GAGG;AACH,wBAAgB,wBAAwB,CACvC,KAAK,EAAE,eAAe,EACtB,UAAU,EAAE,SAAS,oBAAoB,EAAE,GACzC,gBAAgB,GAAG,IAAI,CAgDzB"}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
export const OVERLAY_BODY_HIT_THRESHOLD_CSS_PX = 12;
|
|
2
|
+
export const OVERLAY_ANCHOR_HIT_THRESHOLD_CSS_PX = 14;
|
|
3
|
+
function coordinateDistance(left, right) {
|
|
4
|
+
return Math.hypot(left.x - right.x, left.y - right.y);
|
|
5
|
+
}
|
|
6
|
+
function segmentDistance(point, start, end) {
|
|
7
|
+
const dx = end.x - start.x;
|
|
8
|
+
const dy = end.y - start.y;
|
|
9
|
+
const lengthSquared = dx * dx + dy * dy;
|
|
10
|
+
if (lengthSquared === 0) {
|
|
11
|
+
return coordinateDistance(point, start);
|
|
12
|
+
}
|
|
13
|
+
const projection = Math.max(0, Math.min(1, ((point.x - start.x) * dx + (point.y - start.y) * dy) / lengthSquared));
|
|
14
|
+
return coordinateDistance(point, {
|
|
15
|
+
x: start.x + projection * dx,
|
|
16
|
+
y: start.y + projection * dy,
|
|
17
|
+
});
|
|
18
|
+
}
|
|
19
|
+
function compareHits(left, right) {
|
|
20
|
+
if (left.zLevel !== right.zLevel) {
|
|
21
|
+
return right.zLevel - left.zLevel;
|
|
22
|
+
}
|
|
23
|
+
if (left.sceneIndex !== right.sceneIndex) {
|
|
24
|
+
return right.sceneIndex - left.sceneIndex;
|
|
25
|
+
}
|
|
26
|
+
if (left.overlayId === right.overlayId &&
|
|
27
|
+
left.anchorIndex !== null &&
|
|
28
|
+
right.anchorIndex !== null &&
|
|
29
|
+
left.anchorIndex !== right.anchorIndex) {
|
|
30
|
+
return left.anchorIndex - right.anchorIndex;
|
|
31
|
+
}
|
|
32
|
+
return left.distance - right.distance;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* 按冻结契约在 CSS 像素坐标中执行命中测试。
|
|
36
|
+
* 锚点全局优先于主体;同类再按 zLevel、Scene 后序和锚点低索引排序。
|
|
37
|
+
*/
|
|
38
|
+
export function hitTestOverlayGeometries(point, geometries) {
|
|
39
|
+
const anchorHits = [];
|
|
40
|
+
const bodyHits = [];
|
|
41
|
+
for (const geometry of geometries) {
|
|
42
|
+
for (let anchorIndex = 0; anchorIndex < geometry.anchors.length; anchorIndex++) {
|
|
43
|
+
const anchor = geometry.anchors[anchorIndex];
|
|
44
|
+
if (anchor === undefined) {
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
const distance = coordinateDistance(point, anchor);
|
|
48
|
+
if (distance <= OVERLAY_ANCHOR_HIT_THRESHOLD_CSS_PX) {
|
|
49
|
+
anchorHits.push({
|
|
50
|
+
overlayId: geometry.overlayId,
|
|
51
|
+
target: 'anchor',
|
|
52
|
+
anchorIndex,
|
|
53
|
+
locked: geometry.locked,
|
|
54
|
+
distance,
|
|
55
|
+
sceneIndex: geometry.sceneIndex,
|
|
56
|
+
zLevel: geometry.zLevel,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
let distance = Number.POSITIVE_INFINITY;
|
|
61
|
+
for (const [start, end] of geometry.bodySegments) {
|
|
62
|
+
distance = Math.min(distance, segmentDistance(point, start, end));
|
|
63
|
+
}
|
|
64
|
+
if (distance <= OVERLAY_BODY_HIT_THRESHOLD_CSS_PX) {
|
|
65
|
+
bodyHits.push({
|
|
66
|
+
overlayId: geometry.overlayId,
|
|
67
|
+
target: 'body',
|
|
68
|
+
anchorIndex: null,
|
|
69
|
+
locked: geometry.locked,
|
|
70
|
+
distance,
|
|
71
|
+
sceneIndex: geometry.sceneIndex,
|
|
72
|
+
zLevel: geometry.zLevel,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
const winner = (anchorHits.length > 0 ? anchorHits : bodyHits).sort(compareHits)[0];
|
|
77
|
+
if (winner === undefined) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
overlayId: winner.overlayId,
|
|
82
|
+
target: winner.target,
|
|
83
|
+
anchorIndex: winner.anchorIndex,
|
|
84
|
+
locked: winner.locked,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { type OverlayPixelGeometry, type PixelCoordinate } from './hit-testing.js';
|
|
2
|
+
/**
|
|
3
|
+
* 仅忽略引擎旧命中区域产生的取消选择:回调坐标必须仍命中当前选中 Overlay 的规范几何。
|
|
4
|
+
*/
|
|
5
|
+
export declare function shouldIgnoreStaleOverlayDeselection(selectedOverlayId: string | null, deselectedOverlayId: string, coordinate: PixelCoordinate | undefined, currentGeometries: readonly OverlayPixelGeometry[]): boolean;
|
|
6
|
+
//# sourceMappingURL=selection-arbitration.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selection-arbitration.d.ts","sourceRoot":"","sources":["../../src/interaction/selection-arbitration.ts"],"names":[],"mappings":"AAAA,OAAO,EAEN,KAAK,oBAAoB,EACzB,KAAK,eAAe,EACpB,MAAM,kBAAkB,CAAC;AAE1B;;GAEG;AACH,wBAAgB,mCAAmC,CAClD,iBAAiB,EAAE,MAAM,GAAG,IAAI,EAChC,mBAAmB,EAAE,MAAM,EAC3B,UAAU,EAAE,eAAe,GAAG,SAAS,EACvC,iBAAiB,EAAE,SAAS,oBAAoB,EAAE,GAChD,OAAO,CAKT"}
|