@zuttora/charts 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +48 -2
- package/dist/chart.d.ts +8 -0
- package/dist/chart.js +214 -0
- package/dist/chartState.d.ts +41 -0
- package/dist/chartState.js +59 -0
- package/dist/core/emitter.d.ts +8 -0
- package/dist/core/emitter.js +26 -0
- package/dist/core/format.d.ts +9 -0
- package/dist/core/format.js +35 -0
- package/dist/core/heikinAshi.d.ts +2 -0
- package/dist/core/heikinAshi.js +14 -0
- package/dist/core/priceScale.d.ts +22 -0
- package/dist/core/priceScale.js +92 -0
- package/dist/core/series.d.ts +41 -0
- package/dist/core/series.js +221 -0
- package/dist/core/time.d.ts +25 -0
- package/dist/core/time.js +44 -0
- package/dist/core/timeLabels.d.ts +11 -0
- package/dist/core/timeLabels.js +52 -0
- package/dist/core/types.d.ts +113 -0
- package/dist/core/types.js +1 -0
- package/dist/core/viewport.d.ts +41 -0
- package/dist/core/viewport.js +116 -0
- package/dist/core/warn.d.ts +1 -0
- package/dist/core/warn.js +3 -0
- package/dist/createChart.d.ts +2 -0
- package/dist/createChart.js +7 -0
- package/dist/dom/environment.d.ts +11 -0
- package/dist/dom/environment.js +49 -0
- package/dist/dom/legend.d.ts +14 -0
- package/dist/dom/legend.js +53 -0
- package/dist/dom/legendSync.d.ts +5 -0
- package/dist/dom/legendSync.js +23 -0
- package/dist/dom/root.d.ts +9 -0
- package/dist/dom/root.js +19 -0
- package/dist/host.d.ts +32 -0
- package/dist/host.js +62 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +5 -0
- package/dist/interact/attach.d.ts +2 -0
- package/dist/interact/attach.js +127 -0
- package/dist/interact/gestures.d.ts +47 -0
- package/dist/interact/gestures.js +179 -0
- package/dist/interact/keyboard.d.ts +19 -0
- package/dist/interact/keyboard.js +34 -0
- package/dist/interact/wheel.d.ts +15 -0
- package/dist/interact/wheel.js +18 -0
- package/dist/render/bars.d.ts +17 -0
- package/dist/render/bars.js +55 -0
- package/dist/render/canvas.d.ts +2 -0
- package/dist/render/canvas.js +13 -0
- package/dist/render/drawAxes.d.ts +2 -0
- package/dist/render/drawAxes.js +34 -0
- package/dist/render/drawCandles.d.ts +3 -0
- package/dist/render/drawCandles.js +68 -0
- package/dist/render/drawCrosshair.d.ts +7 -0
- package/dist/render/drawCrosshair.js +39 -0
- package/dist/render/drawGrid.d.ts +2 -0
- package/dist/render/drawGrid.js +22 -0
- package/dist/render/drawLastPrice.d.ts +10 -0
- package/dist/render/drawLastPrice.js +53 -0
- package/dist/render/drawLine.d.ts +3 -0
- package/dist/render/drawLine.js +43 -0
- package/dist/render/drawMain.d.ts +2 -0
- package/dist/render/drawMain.js +30 -0
- package/dist/render/drawMarkers.d.ts +2 -0
- package/dist/render/drawMarkers.js +39 -0
- package/dist/render/drawPriceLines.d.ts +4 -0
- package/dist/render/drawPriceLines.js +60 -0
- package/dist/render/frame.d.ts +53 -0
- package/dist/render/frame.js +5 -0
- package/dist/render/frameBuilder.d.ts +12 -0
- package/dist/render/frameBuilder.js +119 -0
- package/dist/render/layout.d.ts +4 -0
- package/dist/render/layout.js +26 -0
- package/dist/render/paintFrame.d.ts +11 -0
- package/dist/render/paintFrame.js +30 -0
- package/dist/render/scheduler.d.ts +17 -0
- package/dist/render/scheduler.js +38 -0
- package/dist/render/tween.d.ts +12 -0
- package/dist/render/tween.js +38 -0
- package/dist/theme.d.ts +2 -0
- package/dist/theme.js +16 -0
- package/package.json +37 -4
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ChartState } from '../chartState.ts';
|
|
2
|
+
import type { BarSource, Ctx, Frame } from './frame.ts';
|
|
3
|
+
export declare const RANGE_TWEEN_MS = 150;
|
|
4
|
+
export declare const CLOSE_TWEEN_MS = 180;
|
|
5
|
+
export declare const SCROLL_TWEEN_MS = 250;
|
|
6
|
+
export declare function sourceOf(s: ChartState): BarSource;
|
|
7
|
+
export interface Built {
|
|
8
|
+
frame: Frame;
|
|
9
|
+
animating: boolean;
|
|
10
|
+
relayout: boolean;
|
|
11
|
+
}
|
|
12
|
+
export declare function buildFrame(s: ChartState, ctx: Ctx, animNow: number): Built;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { autoRange, axisTicks } from "../core/priceScale.js";
|
|
2
|
+
import { priceFormatter } from "../core/format.js";
|
|
3
|
+
import { INTERVAL_MS } from "../core/time.js";
|
|
4
|
+
import { pickTimeLabels } from "../core/timeLabels.js";
|
|
5
|
+
import { fontOf } from "./frame.js";
|
|
6
|
+
import { computeLayout } from "./layout.js";
|
|
7
|
+
export const RANGE_TWEEN_MS = 150;
|
|
8
|
+
export const CLOSE_TWEEN_MS = 180;
|
|
9
|
+
export const SCROLL_TWEEN_MS = 250;
|
|
10
|
+
const bar = { o: 0, h: 0, l: 0, c: 0 };
|
|
11
|
+
export function sourceOf(s) {
|
|
12
|
+
return s.options.type === 'heikinAshi' ? s.haBars : s.plainBars;
|
|
13
|
+
}
|
|
14
|
+
function extremes(src, from, to) {
|
|
15
|
+
let min = Infinity;
|
|
16
|
+
let max = -Infinity;
|
|
17
|
+
for (let i = from; i <= to; i++) {
|
|
18
|
+
src.at(i, bar);
|
|
19
|
+
if (bar.l < min)
|
|
20
|
+
min = bar.l;
|
|
21
|
+
if (bar.h > max)
|
|
22
|
+
max = bar.h;
|
|
23
|
+
}
|
|
24
|
+
return { min, max };
|
|
25
|
+
}
|
|
26
|
+
function textWidth(s, ctx, text) {
|
|
27
|
+
let w = s.textWidths.get(text);
|
|
28
|
+
if (w === undefined) {
|
|
29
|
+
w = ctx.measureText(text).width;
|
|
30
|
+
if (s.textWidths.size > 500)
|
|
31
|
+
s.textWidths.clear();
|
|
32
|
+
s.textWidths.set(text, w);
|
|
33
|
+
}
|
|
34
|
+
return w;
|
|
35
|
+
}
|
|
36
|
+
export function buildFrame(s, ctx, animNow) {
|
|
37
|
+
const o = s.options;
|
|
38
|
+
const theme = o.theme;
|
|
39
|
+
const reduced = o.reducedMotion;
|
|
40
|
+
const font = fontOf(theme);
|
|
41
|
+
const layout = computeLayout(s.width, s.height, s.axisWidth, 0);
|
|
42
|
+
s.vp.setPlot(layout.plotLeft, layout.plotRight);
|
|
43
|
+
s.scale.setPane(layout.mainTop, layout.mainHeight);
|
|
44
|
+
let animating = false;
|
|
45
|
+
if (s.rightAnimating) {
|
|
46
|
+
s.vp.setRightIndex(s.rightTween.value(animNow));
|
|
47
|
+
if (s.rightTween.active(animNow))
|
|
48
|
+
animating = true;
|
|
49
|
+
else
|
|
50
|
+
s.rightAnimating = false;
|
|
51
|
+
}
|
|
52
|
+
const src = sourceOf(s);
|
|
53
|
+
s.plainBars.drawnClose = s.series.forming ? s.closeTween.value(animNow) : null;
|
|
54
|
+
if (s.closeTween.active(animNow))
|
|
55
|
+
animating = true;
|
|
56
|
+
const vis = s.vp.visibleRange();
|
|
57
|
+
if (s.scale.manual) {
|
|
58
|
+
s.rangeMin.jump(s.scale.min);
|
|
59
|
+
s.rangeMax.jump(s.scale.max);
|
|
60
|
+
}
|
|
61
|
+
else if (vis) {
|
|
62
|
+
const target = autoRange(extremes(src, vis.from, vis.to), [], s.lines, o.minMove);
|
|
63
|
+
if (!s.rangeReady || reduced) {
|
|
64
|
+
s.rangeMin.jump(target.min);
|
|
65
|
+
s.rangeMax.jump(target.max);
|
|
66
|
+
s.rangeReady = true;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
s.rangeMin.set(target.min, animNow, RANGE_TWEEN_MS);
|
|
70
|
+
s.rangeMax.set(target.max, animNow, RANGE_TWEEN_MS);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
if (s.rangeMin.active(animNow) || s.rangeMax.active(animNow))
|
|
74
|
+
animating = true;
|
|
75
|
+
const range = { min: s.rangeMin.value(animNow), max: s.rangeMax.value(animNow) };
|
|
76
|
+
s.scale.setRange(range);
|
|
77
|
+
const ticks = axisTicks(range, layout.mainHeight, o.minMove);
|
|
78
|
+
const fmt = priceFormatter(o.priceDecimals, o.locale);
|
|
79
|
+
ctx.font = font;
|
|
80
|
+
let widest = 0;
|
|
81
|
+
for (const p of ticks)
|
|
82
|
+
widest = Math.max(widest, textWidth(s, ctx, fmt(p)));
|
|
83
|
+
if (s.series.forming) {
|
|
84
|
+
widest = Math.max(widest, textWidth(s, ctx, fmt(s.series.forming.c)));
|
|
85
|
+
widest = Math.max(widest, textWidth(s, ctx, o.intervalMs >= INTERVAL_MS['1h'] ? '00:00:00' : '0:00'));
|
|
86
|
+
}
|
|
87
|
+
const wanted = Math.ceil(widest) + 12;
|
|
88
|
+
const relayout = wanted > s.axisWidth || wanted < s.axisWidth - 8;
|
|
89
|
+
if (relayout)
|
|
90
|
+
s.axisWidth = wanted;
|
|
91
|
+
const labelBars = [];
|
|
92
|
+
if (vis) {
|
|
93
|
+
for (let i = vis.from; i <= vis.to; i++)
|
|
94
|
+
labelBars.push({ t: s.series.timeAt(i), x: s.vp.x(i) });
|
|
95
|
+
}
|
|
96
|
+
const prevT = vis && vis.from > 0 ? s.series.timeAt(vis.from - 1) : null;
|
|
97
|
+
const timeLabels = pickTimeLabels(labelBars, prevT, o.intervalMs, o.tzOffsetMs);
|
|
98
|
+
const frame = {
|
|
99
|
+
theme,
|
|
100
|
+
layout,
|
|
101
|
+
vp: s.vp,
|
|
102
|
+
scale: s.scale,
|
|
103
|
+
series: s.series,
|
|
104
|
+
source: src,
|
|
105
|
+
type: o.type,
|
|
106
|
+
candleStyle: o.candleStyle,
|
|
107
|
+
range,
|
|
108
|
+
ticks,
|
|
109
|
+
timeLabels,
|
|
110
|
+
lines: s.lines,
|
|
111
|
+
markers: s.markers,
|
|
112
|
+
fmt,
|
|
113
|
+
font,
|
|
114
|
+
noDataText: o.labels.noData,
|
|
115
|
+
intervalMs: o.intervalMs,
|
|
116
|
+
tzOffsetMs: o.tzOffsetMs,
|
|
117
|
+
};
|
|
118
|
+
return { frame, animating, relayout };
|
|
119
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
export const TIME_AXIS_HEIGHT = 22;
|
|
2
|
+
export const MIN_AXIS_WIDTH = 56;
|
|
3
|
+
export function computeLayout(width, height, axisWidth, subCount) {
|
|
4
|
+
const axisW = Math.max(MIN_AXIS_WIDTH, Math.ceil(axisWidth));
|
|
5
|
+
const avail = Math.max(0, height - TIME_AXIS_HEIGHT);
|
|
6
|
+
const share = width < 360 ? 0.18 : 0.22;
|
|
7
|
+
let subH = subCount > 0 ? Math.max(64, Math.round(avail * share)) : 0;
|
|
8
|
+
if (subCount * subH > avail * 0.7)
|
|
9
|
+
subH = Math.floor((avail * 0.7) / subCount);
|
|
10
|
+
const mainH = avail - subCount * subH;
|
|
11
|
+
const subs = [];
|
|
12
|
+
for (let i = 0; i < subCount; i++)
|
|
13
|
+
subs.push({ top: mainH + i * subH, height: subH });
|
|
14
|
+
return {
|
|
15
|
+
width,
|
|
16
|
+
height,
|
|
17
|
+
plotLeft: 0,
|
|
18
|
+
plotRight: Math.max(0, width - axisW),
|
|
19
|
+
mainTop: 0,
|
|
20
|
+
mainHeight: mainH,
|
|
21
|
+
axisWidth: axisW,
|
|
22
|
+
timeTop: avail,
|
|
23
|
+
timeHeight: TIME_AXIS_HEIGHT,
|
|
24
|
+
subs,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ChartState } from '../chartState.ts';
|
|
2
|
+
import type { Frame } from './frame.ts';
|
|
3
|
+
import type { Flags } from './scheduler.ts';
|
|
4
|
+
export interface PaintTarget {
|
|
5
|
+
mainCanvas: HTMLCanvasElement;
|
|
6
|
+
overlayCanvas: HTMLCanvasElement;
|
|
7
|
+
dpr(): number;
|
|
8
|
+
frameBuilt(frame: Frame): void;
|
|
9
|
+
afterDraw(): void;
|
|
10
|
+
}
|
|
11
|
+
export declare function paintFrame(s: ChartState, t: PaintTarget, flags: Flags, now: number): boolean;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { fitCanvas } from "./canvas.js";
|
|
2
|
+
import { drawCrosshair } from "./drawCrosshair.js";
|
|
3
|
+
import { drawLastPrice, PULSE_MS } from "./drawLastPrice.js";
|
|
4
|
+
import { drawMain } from "./drawMain.js";
|
|
5
|
+
import { buildFrame } from "./frameBuilder.js";
|
|
6
|
+
export function paintFrame(s, t, flags, now) {
|
|
7
|
+
if (s.width <= 0 || s.height <= 0)
|
|
8
|
+
return false;
|
|
9
|
+
const mainCtx = fitCanvas(t.mainCanvas, s.width, s.height, t.dpr());
|
|
10
|
+
const built = buildFrame(s, mainCtx, now);
|
|
11
|
+
t.frameBuilt(built.frame);
|
|
12
|
+
if (flags.main || built.animating || built.relayout)
|
|
13
|
+
drawMain(mainCtx, built.frame);
|
|
14
|
+
const overlayCtx = fitCanvas(t.overlayCanvas, s.width, s.height, t.dpr());
|
|
15
|
+
overlayCtx.clearRect(0, 0, s.width, s.height);
|
|
16
|
+
s.pulses = s.pulses.filter((p) => now - p < PULSE_MS);
|
|
17
|
+
if (s.series.forming) {
|
|
18
|
+
drawLastPrice(overlayCtx, built.frame, {
|
|
19
|
+
close: s.closeTween.value(now),
|
|
20
|
+
wallNow: s.options.now(),
|
|
21
|
+
animNow: now,
|
|
22
|
+
pulses: s.pulses,
|
|
23
|
+
reducedMotion: s.options.reducedMotion,
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
if (s.crosshair)
|
|
27
|
+
drawCrosshair(overlayCtx, built.frame, s.crosshair);
|
|
28
|
+
t.afterDraw();
|
|
29
|
+
return built.animating || built.relayout || s.pulses.length > 0;
|
|
30
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type Layer = 'main' | 'overlay';
|
|
2
|
+
export interface Flags {
|
|
3
|
+
main: boolean;
|
|
4
|
+
overlay: boolean;
|
|
5
|
+
}
|
|
6
|
+
export declare class Scheduler {
|
|
7
|
+
private flags;
|
|
8
|
+
private handle;
|
|
9
|
+
private draw;
|
|
10
|
+
private raf;
|
|
11
|
+
private caf;
|
|
12
|
+
private stopped;
|
|
13
|
+
constructor(draw: (flags: Flags, now: number) => boolean, raf?: (cb: (now: number) => void) => number, caf?: (handle: number) => void);
|
|
14
|
+
invalidate(layer: Layer): void;
|
|
15
|
+
stop(): void;
|
|
16
|
+
private frame;
|
|
17
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
export class Scheduler {
|
|
2
|
+
constructor(draw, raf = (cb) => requestAnimationFrame(cb), caf = (h) => cancelAnimationFrame(h)) {
|
|
3
|
+
this.flags = { main: false, overlay: false };
|
|
4
|
+
this.handle = null;
|
|
5
|
+
this.stopped = false;
|
|
6
|
+
this.frame = (now) => {
|
|
7
|
+
this.handle = null;
|
|
8
|
+
if (this.stopped)
|
|
9
|
+
return;
|
|
10
|
+
const flags = { main: this.flags.main, overlay: this.flags.overlay };
|
|
11
|
+
this.flags.main = false;
|
|
12
|
+
this.flags.overlay = false;
|
|
13
|
+
const again = this.draw(flags, now);
|
|
14
|
+
if (again) {
|
|
15
|
+
this.flags.main = true;
|
|
16
|
+
this.flags.overlay = true;
|
|
17
|
+
if (this.handle === null)
|
|
18
|
+
this.handle = this.raf(this.frame);
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
this.draw = draw;
|
|
22
|
+
this.raf = raf;
|
|
23
|
+
this.caf = caf;
|
|
24
|
+
}
|
|
25
|
+
invalidate(layer) {
|
|
26
|
+
if (this.stopped)
|
|
27
|
+
return;
|
|
28
|
+
this.flags[layer] = true;
|
|
29
|
+
if (this.handle === null)
|
|
30
|
+
this.handle = this.raf(this.frame);
|
|
31
|
+
}
|
|
32
|
+
stop() {
|
|
33
|
+
this.stopped = true;
|
|
34
|
+
if (this.handle !== null)
|
|
35
|
+
this.caf(this.handle);
|
|
36
|
+
this.handle = null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export declare class Tween {
|
|
2
|
+
private from;
|
|
3
|
+
private to;
|
|
4
|
+
private start;
|
|
5
|
+
private dur;
|
|
6
|
+
constructor(value: number);
|
|
7
|
+
get target(): number;
|
|
8
|
+
set(target: number, now: number, duration: number): void;
|
|
9
|
+
jump(value: number): void;
|
|
10
|
+
value(now: number): number;
|
|
11
|
+
active(now: number): boolean;
|
|
12
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
const easeOutCubic = (x) => 1 - (1 - x) ** 3;
|
|
2
|
+
export class Tween {
|
|
3
|
+
constructor(value) {
|
|
4
|
+
this.start = 0;
|
|
5
|
+
this.dur = 0;
|
|
6
|
+
this.from = value;
|
|
7
|
+
this.to = value;
|
|
8
|
+
}
|
|
9
|
+
get target() {
|
|
10
|
+
return this.to;
|
|
11
|
+
}
|
|
12
|
+
set(target, now, duration) {
|
|
13
|
+
if (target === this.to)
|
|
14
|
+
return;
|
|
15
|
+
this.from = this.value(now);
|
|
16
|
+
this.to = target;
|
|
17
|
+
this.start = now;
|
|
18
|
+
this.dur = duration;
|
|
19
|
+
}
|
|
20
|
+
jump(value) {
|
|
21
|
+
this.from = value;
|
|
22
|
+
this.to = value;
|
|
23
|
+
this.dur = 0;
|
|
24
|
+
}
|
|
25
|
+
value(now) {
|
|
26
|
+
if (this.dur <= 0)
|
|
27
|
+
return this.to;
|
|
28
|
+
const p = (now - this.start) / this.dur;
|
|
29
|
+
if (p >= 1)
|
|
30
|
+
return this.to;
|
|
31
|
+
if (p <= 0)
|
|
32
|
+
return this.from;
|
|
33
|
+
return this.from + (this.to - this.from) * easeOutCubic(p);
|
|
34
|
+
}
|
|
35
|
+
active(now) {
|
|
36
|
+
return this.dur > 0 && now - this.start < this.dur;
|
|
37
|
+
}
|
|
38
|
+
}
|
package/dist/theme.d.ts
ADDED
package/dist/theme.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const zuttoraTheme = {
|
|
2
|
+
background: '#12100E',
|
|
3
|
+
text: '#F3EDE2',
|
|
4
|
+
textMuted: '#91887A',
|
|
5
|
+
grid: 'rgba(56, 49, 42, 0.6)',
|
|
6
|
+
up: '#3EE89A',
|
|
7
|
+
down: '#FF9640',
|
|
8
|
+
line: '#FF3D7F',
|
|
9
|
+
areaTop: 'rgba(255, 61, 127, 0.28)',
|
|
10
|
+
areaBottom: 'rgba(255, 61, 127, 0)',
|
|
11
|
+
crosshair: '#CBC2B4',
|
|
12
|
+
accent: '#FF3D7F',
|
|
13
|
+
accentText: '#12100E',
|
|
14
|
+
fontFamily: '"IBM Plex Mono", monospace',
|
|
15
|
+
fontSize: 12,
|
|
16
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,39 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@zuttora/charts",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Zuttora's own canvas charting package: candlesticks, square corners, no runtime dependencies.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"lint": "eslint .",
|
|
24
|
+
"test": "node --test \"test/**/*.test.mjs\"",
|
|
25
|
+
"build": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.build.json",
|
|
26
|
+
"size": "node scripts/size.mjs",
|
|
27
|
+
"prepublishOnly": "yarn typecheck && yarn lint && yarn test && yarn build && yarn size"
|
|
28
|
+
},
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=22.18"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"@eslint/js": "^10.0.1",
|
|
34
|
+
"@types/node": "^26.6.5",
|
|
35
|
+
"eslint": "^10.13.0",
|
|
36
|
+
"typescript": "^6",
|
|
37
|
+
"typescript-eslint": "^8.71.2"
|
|
38
|
+
}
|
|
39
|
+
}
|