react-native-livechart 4.12.0 → 4.13.0
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/components/CrosshairLine.d.ts +12 -4
- package/dist/components/CrosshairLine.d.ts.map +1 -1
- package/dist/components/CrosshairOverlay.d.ts +12 -2
- package/dist/components/CrosshairOverlay.d.ts.map +1 -1
- package/dist/components/CustomTooltipOverlay.d.ts +5 -2
- package/dist/components/CustomTooltipOverlay.d.ts.map +1 -1
- package/dist/components/LiveChart.d.ts.map +1 -1
- package/dist/components/LiveChartSeries.d.ts.map +1 -1
- package/dist/components/PerSeriesTooltipOverlay.d.ts +3 -1
- package/dist/components/PerSeriesTooltipOverlay.d.ts.map +1 -1
- package/dist/components/ReferenceLineOverlay.d.ts +8 -1
- package/dist/components/ReferenceLineOverlay.d.ts.map +1 -1
- package/dist/components/YAxisOverlay.d.ts +6 -2
- package/dist/components/YAxisOverlay.d.ts.map +1 -1
- package/dist/core/resolveConfig.d.ts +16 -0
- package/dist/core/resolveConfig.d.ts.map +1 -1
- package/dist/draw/grid.d.ts +13 -0
- package/dist/draw/grid.d.ts.map +1 -1
- package/dist/hooks/crosshairShared.d.ts +22 -3
- package/dist/hooks/crosshairShared.d.ts.map +1 -1
- package/dist/hooks/useCrosshair.d.ts +7 -1
- package/dist/hooks/useCrosshair.d.ts.map +1 -1
- package/dist/hooks/useCrosshairSeries.d.ts +6 -1
- package/dist/hooks/useCrosshairSeries.d.ts.map +1 -1
- package/dist/hooks/useCrosshairVisibleOpacity.d.ts +8 -0
- package/dist/hooks/useCrosshairVisibleOpacity.d.ts.map +1 -0
- package/dist/hooks/useReferenceLine.d.ts +10 -1
- package/dist/hooks/useReferenceLine.d.ts.map +1 -1
- package/dist/types.d.ts +47 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/CrosshairLine.tsx +39 -11
- package/src/components/CrosshairOverlay.tsx +49 -15
- package/src/components/CustomTooltipOverlay.tsx +15 -3
- package/src/components/LiveChart.tsx +25 -2
- package/src/components/LiveChartSeries.tsx +83 -16
- package/src/components/PerSeriesTooltipOverlay.tsx +4 -1
- package/src/components/ReferenceLineOverlay.tsx +14 -0
- package/src/components/YAxisOverlay.tsx +34 -9
- package/src/core/resolveConfig.ts +29 -0
- package/src/draw/grid.ts +33 -0
- package/src/hooks/crosshairShared.ts +81 -4
- package/src/hooks/useCrosshair.ts +46 -28
- package/src/hooks/useCrosshairSeries.ts +31 -4
- package/src/hooks/useCrosshairVisibleOpacity.ts +57 -0
- package/src/hooks/useReferenceLine.ts +26 -1
- package/src/types.ts +47 -0
|
@@ -20,6 +20,7 @@ import type {
|
|
|
20
20
|
ScrubPoint,
|
|
21
21
|
} from "../types";
|
|
22
22
|
import {
|
|
23
|
+
clampPlotX,
|
|
23
24
|
computeActionBadgeLayout,
|
|
24
25
|
computeCandleTooltipLayout,
|
|
25
26
|
computeCrosshairOpacity,
|
|
@@ -32,10 +33,13 @@ import {
|
|
|
32
33
|
HIDDEN_TIME_BADGE,
|
|
33
34
|
HIDDEN_TOOLTIP,
|
|
34
35
|
pointInRect,
|
|
36
|
+
resolveScrubHitSlop,
|
|
35
37
|
SCRUB_ACTIVATE_X_PX,
|
|
36
38
|
SCRUB_FAIL_Y_PX,
|
|
37
39
|
snapPrice,
|
|
40
|
+
startPlainScrub,
|
|
38
41
|
type CrosshairState,
|
|
42
|
+
updatePlainScrub,
|
|
39
43
|
} from "./crosshairShared";
|
|
40
44
|
import {
|
|
41
45
|
delayedPanTouchCancelled,
|
|
@@ -64,18 +68,6 @@ const SCRUB_ACTION_TAP_SLOP = 10;
|
|
|
64
68
|
*/
|
|
65
69
|
const SCRUB_ACTION_PRESS_HOLD_MS = 200;
|
|
66
70
|
|
|
67
|
-
/** Clamp an X pixel to the plot's horizontal bounds. */
|
|
68
|
-
/* istanbul ignore next -- worklet, called only from UI-thread gesture handlers */
|
|
69
|
-
function clampPlotX(
|
|
70
|
-
x: number,
|
|
71
|
-
padLeft: number,
|
|
72
|
-
canvasWidth: number,
|
|
73
|
-
padRight: number,
|
|
74
|
-
): number {
|
|
75
|
-
"worklet";
|
|
76
|
-
return Math.min(canvasWidth - padRight, Math.max(padLeft, x));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
71
|
/** Clamp a Y pixel to the plot's vertical bounds. */
|
|
80
72
|
/* istanbul ignore next -- worklet, called only from UI-thread gesture handlers */
|
|
81
73
|
function clampPlotY(
|
|
@@ -159,6 +151,12 @@ export function useCrosshair(
|
|
|
159
151
|
* mature into a scrub (see `delayedPanGuard.shouldStartDelayedPan`).
|
|
160
152
|
*/
|
|
161
153
|
scrollActive?: SharedValue<boolean>,
|
|
154
|
+
/**
|
|
155
|
+
* Reject plain-scrub starts beyond either horizontal plot edge and clamp
|
|
156
|
+
* active drags to those bounds. Scrub-action behavior is unchanged.
|
|
157
|
+
* Default `false`.
|
|
158
|
+
*/
|
|
159
|
+
clampToPlot = false,
|
|
162
160
|
): CrosshairState {
|
|
163
161
|
const scrubX = useSharedValue(-1);
|
|
164
162
|
const scrubActive = useSharedValue(false);
|
|
@@ -195,6 +193,7 @@ export function useCrosshair(
|
|
|
195
193
|
// tracks the price as the axis rescales instead of drifting under a fixed pixel.
|
|
196
194
|
const lockPriceValue = useSharedValue<number | null>(null);
|
|
197
195
|
const hasScrubAction = scrubAction != null;
|
|
196
|
+
const clampPlainScrubToPlot = clampToPlot && !hasScrubAction;
|
|
198
197
|
const hasOnScrubAction = onScrubAction != null;
|
|
199
198
|
const dismissOnTapOutside = scrubAction?.dismissOnTapOutside ?? false;
|
|
200
199
|
const dismissOnAction = scrubAction?.dismissOnAction ?? false;
|
|
@@ -618,9 +617,15 @@ export function useCrosshair(
|
|
|
618
617
|
// also keeps a follow-on drag from showing a crosshair — no `onUpdate`
|
|
619
618
|
// guard needed. (Plain-scrub counterpart of the scrub-action tap defer.)
|
|
620
619
|
if (deferTapHit !== undefined && deferTapHit(e.x, e.y)) return;
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
620
|
+
startPlainScrub(
|
|
621
|
+
e.x,
|
|
622
|
+
padding,
|
|
623
|
+
engine.canvasWidth.get(),
|
|
624
|
+
clampPlainScrubToPlot,
|
|
625
|
+
scrubX,
|
|
626
|
+
scrubActive,
|
|
627
|
+
gestureStarted,
|
|
628
|
+
);
|
|
624
629
|
if (hasOnGestureStart) scheduleOnRN(handleGestureStart);
|
|
625
630
|
},
|
|
626
631
|
)
|
|
@@ -647,7 +652,14 @@ export function useCrosshair(
|
|
|
647
652
|
}
|
|
648
653
|
return;
|
|
649
654
|
}
|
|
650
|
-
|
|
655
|
+
updatePlainScrub(
|
|
656
|
+
e.x,
|
|
657
|
+
padding,
|
|
658
|
+
engine.canvasWidth.get(),
|
|
659
|
+
clampPlainScrubToPlot,
|
|
660
|
+
scrubX,
|
|
661
|
+
scrubActive,
|
|
662
|
+
);
|
|
651
663
|
},
|
|
652
664
|
)
|
|
653
665
|
.onFinalize(
|
|
@@ -686,13 +698,15 @@ export function useCrosshair(
|
|
|
686
698
|
.failOffsetY([-SCRUB_FAIL_Y_PX, SCRUB_FAIL_Y_PX]);
|
|
687
699
|
}
|
|
688
700
|
|
|
689
|
-
//
|
|
690
|
-
//
|
|
691
|
-
//
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
701
|
+
// Restrict recognition by touch-down position. This rejects before ACTIVE,
|
|
702
|
+
// leaving outside starts to competing/parent gestures. Once accepted,
|
|
703
|
+
// `shouldCancelWhenOutside(false)` keeps tracking beyond these bounds.
|
|
704
|
+
const scrubHitSlop = resolveScrubHitSlop(
|
|
705
|
+
padding,
|
|
706
|
+
clampPlainScrubToPlot,
|
|
707
|
+
scrubBottomExclude,
|
|
708
|
+
);
|
|
709
|
+
if (scrubHitSlop) gesture = gesture.hitSlop(scrubHitSlop);
|
|
696
710
|
|
|
697
711
|
// Tap: place/move the reticle, press the action badge, or dismiss the lock.
|
|
698
712
|
// Composed ahead of the pan by the controller, so a tap is never swallowed.
|
|
@@ -767,11 +781,15 @@ export function useCrosshair(
|
|
|
767
781
|
.maxDistance(SCRUB_ACTION_TAP_SLOP)
|
|
768
782
|
.onEnd(handleActionTap)
|
|
769
783
|
: undefined;
|
|
770
|
-
// Keep the axis
|
|
771
|
-
//
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
784
|
+
// Keep the bottom axis scroll-only. Do not apply horizontal plot bounds here:
|
|
785
|
+
// the action badge is intentionally tappable in the right gutter.
|
|
786
|
+
const tapHitSlop = resolveScrubHitSlop(
|
|
787
|
+
padding,
|
|
788
|
+
false,
|
|
789
|
+
scrubBottomExclude,
|
|
790
|
+
);
|
|
791
|
+
if (tapGesture && tapHitSlop)
|
|
792
|
+
tapGesture = tapGesture.hitSlop(tapHitSlop);
|
|
775
793
|
|
|
776
794
|
return {
|
|
777
795
|
scrubX,
|
|
@@ -22,9 +22,12 @@ import {
|
|
|
22
22
|
computeScrubDotY,
|
|
23
23
|
computeScrubTime,
|
|
24
24
|
HIDDEN_TOOLTIP,
|
|
25
|
+
resolveScrubHitSlop,
|
|
25
26
|
SCRUB_ACTIVATE_X_PX,
|
|
26
27
|
SCRUB_FAIL_Y_PX,
|
|
28
|
+
startPlainScrub,
|
|
27
29
|
type CrosshairState,
|
|
30
|
+
updatePlainScrub,
|
|
28
31
|
} from "./crosshairShared";
|
|
29
32
|
import {
|
|
30
33
|
delayedPanTouchCancelled,
|
|
@@ -65,6 +68,11 @@ export function useCrosshairSeries(
|
|
|
65
68
|
*/
|
|
66
69
|
scrollActive?: SharedValue<boolean>,
|
|
67
70
|
tooltip?: CrosshairSeriesTooltipOptions,
|
|
71
|
+
/**
|
|
72
|
+
* Reject scrub starts beyond either horizontal plot edge and clamp active
|
|
73
|
+
* drags to those bounds. Default `false`.
|
|
74
|
+
*/
|
|
75
|
+
clampToPlot = false,
|
|
68
76
|
): CrosshairState {
|
|
69
77
|
const scrubX = useSharedValue(-1);
|
|
70
78
|
const scrubActive = useSharedValue(false);
|
|
@@ -290,9 +298,15 @@ export function useCrosshairSeries(
|
|
|
290
298
|
)
|
|
291
299
|
return;
|
|
292
300
|
if (!enabled) return;
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
301
|
+
startPlainScrub(
|
|
302
|
+
e.x,
|
|
303
|
+
padding,
|
|
304
|
+
engine.canvasWidth.get(),
|
|
305
|
+
clampToPlot,
|
|
306
|
+
scrubX,
|
|
307
|
+
scrubActive,
|
|
308
|
+
gestureStarted,
|
|
309
|
+
);
|
|
296
310
|
if (hasOnGestureStart) scheduleOnRN(handleGestureStart);
|
|
297
311
|
},
|
|
298
312
|
)
|
|
@@ -300,7 +314,14 @@ export function useCrosshairSeries(
|
|
|
300
314
|
/* istanbul ignore next */ (e) => {
|
|
301
315
|
"worklet";
|
|
302
316
|
if (!enabled) return;
|
|
303
|
-
|
|
317
|
+
updatePlainScrub(
|
|
318
|
+
e.x,
|
|
319
|
+
padding,
|
|
320
|
+
engine.canvasWidth.get(),
|
|
321
|
+
clampToPlot,
|
|
322
|
+
scrubX,
|
|
323
|
+
scrubActive,
|
|
324
|
+
);
|
|
304
325
|
},
|
|
305
326
|
)
|
|
306
327
|
.onFinalize(
|
|
@@ -326,6 +347,12 @@ export function useCrosshairSeries(
|
|
|
326
347
|
.activeOffsetX([-SCRUB_ACTIVATE_X_PX, SCRUB_ACTIVATE_X_PX])
|
|
327
348
|
.failOffsetY([-SCRUB_FAIL_Y_PX, SCRUB_FAIL_Y_PX]);
|
|
328
349
|
|
|
350
|
+
// Gate by touch-down position so outside starts fail before activation and
|
|
351
|
+
// remain available to competing/parent gestures. Accepted pans keep tracking
|
|
352
|
+
// outside because `shouldCancelWhenOutside(false)` is set above.
|
|
353
|
+
const scrubHitSlop = resolveScrubHitSlop(padding, clampToPlot);
|
|
354
|
+
if (scrubHitSlop) gesture = gesture.hitSlop(scrubHitSlop);
|
|
355
|
+
|
|
329
356
|
return {
|
|
330
357
|
scrubX,
|
|
331
358
|
scrubActive,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import {
|
|
2
|
+
useDerivedValue,
|
|
3
|
+
type SharedValue,
|
|
4
|
+
} from "react-native-reanimated";
|
|
5
|
+
import { computeCrosshairOpacity } from "./crosshairShared";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Opacity for the visible crosshair UI. The trailing-content dim keeps using
|
|
9
|
+
* the edge-faded opacity directly, independent of this setting.
|
|
10
|
+
*/
|
|
11
|
+
export function useCrosshairVisibleOpacity(
|
|
12
|
+
scrubX: SharedValue<number>,
|
|
13
|
+
canvasWidth: SharedValue<number>,
|
|
14
|
+
paddingRight: number,
|
|
15
|
+
active: SharedValue<number> | SharedValue<boolean>,
|
|
16
|
+
fade: boolean,
|
|
17
|
+
fadeDistance = 4,
|
|
18
|
+
) {
|
|
19
|
+
return useDerivedValue(
|
|
20
|
+
() =>
|
|
21
|
+
resolveCrosshairVisibleOpacity(
|
|
22
|
+
Boolean(active.get()),
|
|
23
|
+
scrubX.get(),
|
|
24
|
+
canvasWidth.get(),
|
|
25
|
+
paddingRight,
|
|
26
|
+
fade,
|
|
27
|
+
fadeDistance,
|
|
28
|
+
),
|
|
29
|
+
[
|
|
30
|
+
scrubX,
|
|
31
|
+
canvasWidth,
|
|
32
|
+
paddingRight,
|
|
33
|
+
active,
|
|
34
|
+
fade,
|
|
35
|
+
fadeDistance,
|
|
36
|
+
],
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function resolveCrosshairVisibleOpacity(
|
|
41
|
+
active: boolean,
|
|
42
|
+
scrubX: number,
|
|
43
|
+
canvasWidth: number,
|
|
44
|
+
paddingRight: number,
|
|
45
|
+
fade: boolean,
|
|
46
|
+
fadeDistance = 4,
|
|
47
|
+
): number {
|
|
48
|
+
"worklet";
|
|
49
|
+
if (!fade) return active ? 1 : 0;
|
|
50
|
+
return computeCrosshairOpacity(
|
|
51
|
+
active,
|
|
52
|
+
scrubX,
|
|
53
|
+
canvasWidth,
|
|
54
|
+
paddingRight,
|
|
55
|
+
fadeDistance,
|
|
56
|
+
);
|
|
57
|
+
}
|
|
@@ -2,6 +2,10 @@ import { useDerivedValue, type SharedValue } from "react-native-reanimated";
|
|
|
2
2
|
|
|
3
3
|
import type { SkFont } from "@shopify/react-native-skia";
|
|
4
4
|
import type { ChartEngineLayout } from "../core/useLiveChartEngine";
|
|
5
|
+
import {
|
|
6
|
+
rightAnchoredYAxisColumnLayout,
|
|
7
|
+
type YAxisEntry,
|
|
8
|
+
} from "../draw/grid";
|
|
5
9
|
import type { ChartPadding } from "../draw/line";
|
|
6
10
|
import { measureFontTextWidth } from "../lib/measureFontTextWidth";
|
|
7
11
|
import {
|
|
@@ -293,6 +297,14 @@ export function useReferenceLine(
|
|
|
293
297
|
*/
|
|
294
298
|
dragValues?: SharedValue<number[]>,
|
|
295
299
|
index = 0,
|
|
300
|
+
/** Y-axis labels used to match the right-anchored grid endpoint. */
|
|
301
|
+
yAxisEntries?: SharedValue<YAxisEntry[]>,
|
|
302
|
+
/** Enables clipping when set; matches {@link YAxisConfig.labelRightMargin}. */
|
|
303
|
+
labelRightMargin?: number,
|
|
304
|
+
/** Gap before the label column; matches {@link YAxisConfig.gridEndGap}. */
|
|
305
|
+
gridEndGap = 0,
|
|
306
|
+
/** Font used by the Y-axis labels; badge font overrides must not move the line. */
|
|
307
|
+
yAxisFont: SkFont = font,
|
|
296
308
|
): SharedValue<ReferenceLineLayout> {
|
|
297
309
|
const form = line ? referenceLineForm(line) : "none";
|
|
298
310
|
// Badge presentation depends only on the (stable) line props — resolve once.
|
|
@@ -314,7 +326,20 @@ export function useReferenceLine(
|
|
|
314
326
|
// the badge/label anchor (x1/x2) stays at the plot edges either way.
|
|
315
327
|
const fullWidth = line.fullWidth ?? false;
|
|
316
328
|
const lineX1 = fullWidth ? 0 : x1;
|
|
317
|
-
const
|
|
329
|
+
const yAxisColumn =
|
|
330
|
+
form === "line" &&
|
|
331
|
+
!fullWidth &&
|
|
332
|
+
yAxisEntries !== undefined &&
|
|
333
|
+
labelRightMargin !== undefined
|
|
334
|
+
? rightAnchoredYAxisColumnLayout(
|
|
335
|
+
w,
|
|
336
|
+
yAxisEntries.value,
|
|
337
|
+
yAxisFont,
|
|
338
|
+
labelRightMargin,
|
|
339
|
+
gridEndGap,
|
|
340
|
+
)
|
|
341
|
+
: null;
|
|
342
|
+
const lineX2 = fullWidth ? w : (yAxisColumn?.gridEndX ?? x2);
|
|
318
343
|
|
|
319
344
|
const fm = font.getMetrics();
|
|
320
345
|
const baselineOffset = (fm.ascent + fm.descent) / 2;
|
package/src/types.ts
CHANGED
|
@@ -647,6 +647,18 @@ export interface YAxisConfig {
|
|
|
647
647
|
* (the label pool size). Omit (or `0`) for the default dynamic grid.
|
|
648
648
|
*/
|
|
649
649
|
count?: number;
|
|
650
|
+
/**
|
|
651
|
+
* Place price labels in a shared left-aligned column whose right edge sits
|
|
652
|
+
* this many pixels from the canvas edge. When omitted, labels keep the
|
|
653
|
+
* default centered-gutter placement.
|
|
654
|
+
*/
|
|
655
|
+
labelRightMargin?: number;
|
|
656
|
+
/**
|
|
657
|
+
* Gap (px) between the shared price-label column and the end of each grid or
|
|
658
|
+
* plain reference line. Only applies when {@link labelRightMargin} is set.
|
|
659
|
+
* Default `0`.
|
|
660
|
+
*/
|
|
661
|
+
gridEndGap?: number;
|
|
650
662
|
/**
|
|
651
663
|
* Float the price axis over a full-width plot instead of reserving a right
|
|
652
664
|
* gutter for it. The line/candles run all the way to the right edge, and the
|
|
@@ -831,6 +843,33 @@ export interface ScrubConfig {
|
|
|
831
843
|
dimOpacity?: number;
|
|
832
844
|
/** Vertical crosshair line stroke. Omit to use theme `crosshairLine`. */
|
|
833
845
|
crosshairLineColor?: string;
|
|
846
|
+
/** Vertical crosshair line width in px. Default `1`. */
|
|
847
|
+
crosshairStrokeWidth?: number;
|
|
848
|
+
/**
|
|
849
|
+
* Extend the vertical crosshair line past the plot's top and bottom edges by
|
|
850
|
+
* this many px. A measured top custom-tooltip stop remains unchanged so the
|
|
851
|
+
* line does not draw through the tooltip. Negative values are clamped to `0`.
|
|
852
|
+
* Default `0`.
|
|
853
|
+
*/
|
|
854
|
+
crosshairOvershoot?: number;
|
|
855
|
+
/**
|
|
856
|
+
* Fade the crosshair as it approaches the live edge. Set `false` to keep the
|
|
857
|
+
* line, selection dot, and tooltip fully opaque while scrubbing. The trailing
|
|
858
|
+
* content dim keeps its own edge fade. Default `true`.
|
|
859
|
+
*/
|
|
860
|
+
crosshairFade?: boolean;
|
|
861
|
+
/**
|
|
862
|
+
* Distance in px over which the visible crosshair fades as it approaches the
|
|
863
|
+
* live edge. Applies to the line, selection dot, and tooltip when
|
|
864
|
+
* `crosshairFade` is enabled; the trailing-content dim keeps its existing
|
|
865
|
+
* fade. Negative values are clamped to `0`. Default `4`.
|
|
866
|
+
*/
|
|
867
|
+
crosshairFadeDistance?: number;
|
|
868
|
+
/**
|
|
869
|
+
* Cap style for the vertical crosshair line. Omit to preserve the existing
|
|
870
|
+
* Skia default.
|
|
871
|
+
*/
|
|
872
|
+
crosshairLineCap?: "butt" | "round" | "square";
|
|
834
873
|
/**
|
|
835
874
|
* Dash the vertical crosshair line. `true` → a default `[4, 4]` dash; an array
|
|
836
875
|
* sets explicit Skia dash intervals `[on, off, …]` in px. Omit / `false` → a
|
|
@@ -898,6 +937,14 @@ export interface ScrubConfig {
|
|
|
898
937
|
* `false` / omitted keeps the overlays visible while scrubbing (default).
|
|
899
938
|
*/
|
|
900
939
|
hideOverlaysOnScrub?: boolean;
|
|
940
|
+
/**
|
|
941
|
+
* Keep plain scrubbing inside the horizontal plot bounds. A gesture that
|
|
942
|
+
* starts in the Y-axis gutter (or beyond either horizontal edge) is ignored;
|
|
943
|
+
* one that starts inside remains active and clamps to the nearest plot edge
|
|
944
|
+
* while dragging. Scrub-action gestures retain their existing behavior.
|
|
945
|
+
* Default `false`.
|
|
946
|
+
*/
|
|
947
|
+
clampToPlot?: boolean;
|
|
901
948
|
}
|
|
902
949
|
|
|
903
950
|
/**
|