layerchart 2.0.0-next.4 → 2.0.0-next.40
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/AnnotationLine.svelte +15 -2
- package/dist/components/AnnotationPoint.svelte +29 -11
- package/dist/components/AnnotationRange.svelte +18 -4
- package/dist/components/Arc.svelte +5 -5
- package/dist/components/Area.svelte +10 -2
- package/dist/components/Axis.svelte +175 -58
- package/dist/components/Axis.svelte.d.ts +23 -6
- package/dist/components/Bar.svelte +20 -15
- package/dist/components/Bar.svelte.d.ts +2 -2
- package/dist/components/Bars.svelte +4 -4
- package/dist/components/Blur.svelte +7 -6
- package/dist/components/Blur.svelte.d.ts +2 -5
- package/dist/components/BrushContext.svelte +45 -45
- package/dist/components/Calendar.svelte +31 -10
- package/dist/components/Calendar.svelte.d.ts +2 -1
- package/dist/components/Chart.svelte +76 -27
- package/dist/components/Chart.svelte.d.ts +26 -8
- package/dist/components/ChartClipPath.svelte +1 -1
- package/dist/components/Circle.svelte +44 -3
- package/dist/components/CircleClipPath.svelte +8 -1
- package/dist/components/ClipPath.svelte +1 -2
- package/dist/components/ColorRamp.svelte +1 -1
- package/dist/components/ComputedStyles.svelte +9 -2
- package/dist/components/Connector.svelte +3 -3
- package/dist/components/Connector.svelte.d.ts +1 -1
- package/dist/components/Ellipse.svelte +228 -0
- package/dist/components/Ellipse.svelte.d.ts +64 -0
- package/dist/components/ForceSimulation.svelte +184 -50
- package/dist/components/ForceSimulation.svelte.d.ts +95 -21
- package/dist/components/Frame.svelte +1 -1
- package/dist/components/GeoCircle.svelte +1 -1
- package/dist/components/GeoEdgeFade.svelte +1 -1
- package/dist/components/GeoPath.svelte +30 -8
- package/dist/components/GeoPoint.svelte +4 -5
- package/dist/components/GeoSpline.svelte +5 -5
- package/dist/components/GeoSpline.svelte.d.ts +1 -1
- package/dist/components/GeoTile.svelte +1 -1
- package/dist/components/Graticule.svelte +5 -5
- package/dist/components/Grid.svelte +60 -63
- package/dist/components/Group.svelte +13 -8
- package/dist/components/Group.svelte.d.ts +10 -3
- package/dist/components/Highlight.svelte +55 -28
- package/dist/components/Highlight.svelte.d.ts +4 -0
- package/dist/components/Hull.svelte +12 -5
- package/dist/components/Labels.svelte +24 -13
- package/dist/components/Labels.svelte.d.ts +12 -5
- package/dist/components/Legend.svelte +143 -70
- package/dist/components/Legend.svelte.d.ts +12 -8
- package/dist/components/Line.svelte +40 -3
- package/dist/components/LinearGradient.svelte +35 -4
- package/dist/components/Link.svelte +1 -1
- package/dist/components/Marker.svelte +37 -26
- package/dist/components/MonthPath.svelte +26 -12
- package/dist/components/MonthPath.svelte.d.ts +4 -3
- package/dist/components/MotionPath.svelte +1 -1
- package/dist/components/Pack.svelte.d.ts +10 -3
- package/dist/components/Partition.svelte.d.ts +10 -3
- package/dist/components/Pattern.svelte +5 -5
- package/dist/components/Pie.svelte +1 -2
- package/dist/components/Points.svelte +11 -72
- package/dist/components/Points.svelte.d.ts +1 -8
- package/dist/components/Polygon.svelte +309 -0
- package/dist/components/Polygon.svelte.d.ts +115 -0
- package/dist/components/RadialGradient.svelte +4 -6
- package/dist/components/Rect.svelte +55 -5
- package/dist/components/Rect.svelte.d.ts +2 -2
- package/dist/components/RectClipPath.svelte +4 -3
- package/dist/components/RectClipPath.svelte.d.ts +2 -2
- package/dist/components/Rule.svelte +167 -77
- package/dist/components/Rule.svelte.d.ts +7 -2
- package/dist/components/Spline.svelte +59 -28
- package/dist/components/Spline.svelte.d.ts +12 -4
- package/dist/components/Text.svelte +121 -73
- package/dist/components/Text.svelte.d.ts +6 -0
- package/dist/components/TileImage.svelte +19 -4
- package/dist/components/TransformContext.svelte +9 -3
- package/dist/components/TransformControls.svelte +89 -38
- package/dist/components/Tree.svelte.d.ts +10 -3
- package/dist/components/Treemap.svelte +63 -26
- package/dist/components/Treemap.svelte.d.ts +21 -14
- package/dist/components/Voronoi.svelte +12 -13
- package/dist/components/charts/ArcChart.svelte +43 -71
- package/dist/components/charts/ArcChart.svelte.d.ts +10 -3
- package/dist/components/charts/AreaChart.svelte +29 -59
- package/dist/components/charts/AreaChart.svelte.d.ts +10 -3
- package/dist/components/charts/BarChart.svelte +79 -71
- package/dist/components/charts/BarChart.svelte.d.ts +10 -3
- package/dist/components/charts/DefaultTooltip.svelte +3 -3
- package/dist/components/charts/DefaultTooltip.svelte.d.ts +1 -1
- package/dist/components/charts/LineChart.svelte +69 -75
- package/dist/components/charts/LineChart.svelte.d.ts +21 -8
- package/dist/components/charts/PieChart.svelte +44 -71
- package/dist/components/charts/PieChart.svelte.d.ts +10 -3
- package/dist/components/charts/ScatterChart.svelte +10 -39
- package/dist/components/charts/ScatterChart.svelte.d.ts +10 -3
- package/dist/components/charts/utils.svelte.d.ts +1 -19
- package/dist/components/charts/utils.svelte.js +7 -35
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.js +5 -1
- package/dist/components/layout/Canvas.svelte +96 -69
- package/dist/components/layout/Canvas.svelte.d.ts +6 -0
- package/dist/components/layout/Html.svelte +15 -9
- package/dist/components/layout/Layer.svelte +6 -4
- package/dist/components/layout/Layer.svelte.d.ts +6 -4
- package/dist/components/layout/Svg.svelte +19 -11
- package/dist/components/layout/WebGL.svelte +26 -6
- package/dist/components/layout/WebGL.svelte.d.ts +5 -2
- package/dist/components/tooltip/Tooltip.svelte +65 -27
- package/dist/components/tooltip/Tooltip.svelte.d.ts +10 -3
- package/dist/components/tooltip/TooltipContext.svelte +167 -54
- package/dist/components/tooltip/TooltipContext.svelte.d.ts +19 -5
- package/dist/components/tooltip/TooltipHeader.svelte +32 -18
- package/dist/components/tooltip/TooltipHeader.svelte.d.ts +3 -3
- package/dist/components/tooltip/TooltipItem.svelte +46 -37
- package/dist/components/tooltip/TooltipItem.svelte.d.ts +3 -3
- package/dist/components/tooltip/TooltipList.svelte +12 -10
- package/dist/components/tooltip/TooltipSeparator.svelte +18 -10
- package/dist/components/tooltip/tooltipMetaContext.d.ts +2 -2
- package/dist/docs/Blockquote.svelte +6 -4
- package/dist/docs/Blockquote.svelte.d.ts +4 -19
- package/dist/docs/Code.svelte +70 -28
- package/dist/docs/Code.svelte.d.ts +9 -24
- package/dist/docs/Header1.svelte +4 -2
- package/dist/docs/Header1.svelte.d.ts +4 -28
- package/dist/docs/Json.svelte +11 -3
- package/dist/docs/Json.svelte.d.ts +9 -21
- package/dist/docs/Layout.svelte +10 -7
- package/dist/docs/Layout.svelte.d.ts +4 -19
- package/dist/docs/Link.svelte +7 -3
- package/dist/docs/Link.svelte.d.ts +4 -38
- package/dist/docs/Preview.svelte +22 -23
- package/dist/docs/Preview.svelte.d.ts +5 -6
- package/dist/docs/TilesetField.svelte +20 -19
- package/dist/docs/TilesetField.svelte.d.ts +5 -22
- package/dist/docs/ViewSourceButton.svelte +10 -7
- package/dist/docs/ViewSourceButton.svelte.d.ts +7 -21
- package/dist/states/series.svelte.d.ts +30 -0
- package/dist/states/series.svelte.js +54 -0
- package/dist/styles/daisyui-5.css +6 -0
- package/dist/styles/shadcn-svelte.css +11 -0
- package/dist/styles/skeleton-3.css +15 -0
- package/dist/utils/arcText.svelte.js +4 -4
- package/dist/utils/array.d.ts +11 -0
- package/dist/utils/array.js +23 -0
- package/dist/utils/array.test.d.ts +1 -0
- package/dist/utils/array.test.js +200 -0
- package/dist/utils/attributes.d.ts +3 -13
- package/dist/utils/attributes.js +4 -18
- package/dist/utils/canvas.d.ts +77 -0
- package/dist/utils/canvas.js +105 -41
- package/dist/utils/common.d.ts +9 -0
- package/dist/utils/common.js +18 -1
- package/dist/utils/common.test.js +26 -1
- package/dist/utils/genData.d.ts +22 -8
- package/dist/utils/genData.js +34 -14
- package/dist/utils/graph/dagre.d.ts +4 -4
- package/dist/utils/graph/dagre.js +5 -7
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.js +1 -0
- package/dist/utils/math.d.ts +17 -0
- package/dist/utils/math.js +17 -0
- package/dist/utils/path.d.ts +10 -0
- package/dist/utils/path.js +30 -0
- package/dist/utils/rect.svelte.d.ts +2 -2
- package/dist/utils/rect.svelte.js +73 -1
- package/dist/utils/scales.svelte.d.ts +9 -3
- package/dist/utils/scales.svelte.js +47 -4
- package/dist/utils/shape.d.ts +43 -0
- package/dist/utils/shape.js +59 -0
- package/dist/utils/stack.js +1 -1
- package/dist/utils/string.d.ts +49 -0
- package/dist/utils/string.js +4 -2
- package/dist/utils/ticks.d.ts +15 -4
- package/dist/utils/ticks.js +140 -159
- package/dist/utils/ticks.test.js +16 -26
- package/dist/utils/treemap.d.ts +1 -1
- package/dist/utils/types.d.ts +15 -2
- package/package.json +35 -35
- package/dist/utils/object.js +0 -2
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { applyLanes } from './array.js';
|
|
3
|
+
describe('applyLanes', () => {
|
|
4
|
+
it('should assign same lane to non-overlapping events', () => {
|
|
5
|
+
const data = [
|
|
6
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-02') },
|
|
7
|
+
{ id: 2, start: new Date('2023-01-03'), end: new Date('2023-01-05') },
|
|
8
|
+
{ id: 3, start: new Date('2023-01-06'), end: new Date('2023-01-08') },
|
|
9
|
+
];
|
|
10
|
+
const result = applyLanes(data);
|
|
11
|
+
expect(result).toEqual([
|
|
12
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-02'), lane: 0 },
|
|
13
|
+
{ id: 2, start: new Date('2023-01-03'), end: new Date('2023-01-05'), lane: 0 },
|
|
14
|
+
{ id: 3, start: new Date('2023-01-06'), end: new Date('2023-01-08'), lane: 0 },
|
|
15
|
+
]);
|
|
16
|
+
});
|
|
17
|
+
it('should assign different lanes to overlapping events', () => {
|
|
18
|
+
const data = [
|
|
19
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-03') },
|
|
20
|
+
{ id: 2, start: new Date('2023-01-02'), end: new Date('2023-01-04') },
|
|
21
|
+
{ id: 3, start: new Date('2023-01-02T12:00:00'), end: new Date('2023-01-05') },
|
|
22
|
+
];
|
|
23
|
+
const result = applyLanes(data);
|
|
24
|
+
expect(result).toEqual([
|
|
25
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-03'), lane: 0 },
|
|
26
|
+
{ id: 2, start: new Date('2023-01-02'), end: new Date('2023-01-04'), lane: 1 },
|
|
27
|
+
{ id: 3, start: new Date('2023-01-02T12:00:00'), end: new Date('2023-01-05'), lane: 2 },
|
|
28
|
+
]);
|
|
29
|
+
});
|
|
30
|
+
it('should reuse lanes when events no longer overlap', () => {
|
|
31
|
+
const data = [
|
|
32
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-02') },
|
|
33
|
+
{ id: 2, start: new Date('2023-01-01T12:00:00'), end: new Date('2023-01-03') },
|
|
34
|
+
{ id: 3, start: new Date('2023-01-04'), end: new Date('2023-01-06') }, // starts after id: 1 ends
|
|
35
|
+
{ id: 4, start: new Date('2023-01-05'), end: new Date('2023-01-07') }, // starts after id: 2 ends
|
|
36
|
+
];
|
|
37
|
+
const result = applyLanes(data);
|
|
38
|
+
expect(result).toEqual([
|
|
39
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-02'), lane: 0 },
|
|
40
|
+
{ id: 2, start: new Date('2023-01-01T12:00:00'), end: new Date('2023-01-03'), lane: 1 },
|
|
41
|
+
{ id: 3, start: new Date('2023-01-04'), end: new Date('2023-01-06'), lane: 0 }, // reuses lane 0
|
|
42
|
+
{ id: 4, start: new Date('2023-01-05'), end: new Date('2023-01-07'), lane: 1 }, // reuses lane 1
|
|
43
|
+
]);
|
|
44
|
+
});
|
|
45
|
+
it('should handle events that start exactly when another ends', () => {
|
|
46
|
+
const data = [
|
|
47
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-02') },
|
|
48
|
+
{ id: 2, start: new Date('2023-01-02'), end: new Date('2023-01-04') }, // starts exactly when id: 1 ends
|
|
49
|
+
{ id: 3, start: new Date('2023-01-01T12:00:00'), end: new Date('2023-01-03') }, // overlaps with both
|
|
50
|
+
];
|
|
51
|
+
const result = applyLanes(data);
|
|
52
|
+
expect(result).toEqual([
|
|
53
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-02'), lane: 0 },
|
|
54
|
+
{ id: 2, start: new Date('2023-01-02'), end: new Date('2023-01-04'), lane: 0 }, // can reuse lane 0
|
|
55
|
+
{ id: 3, start: new Date('2023-01-01T12:00:00'), end: new Date('2023-01-03'), lane: 1 }, // overlaps, needs new lane
|
|
56
|
+
]);
|
|
57
|
+
});
|
|
58
|
+
it('should work with string keys for start and end', () => {
|
|
59
|
+
const data = [
|
|
60
|
+
{ name: 'Task 1', startTime: new Date('2023-01-01'), endTime: new Date('2023-01-03') },
|
|
61
|
+
{ name: 'Task 2', startTime: new Date('2023-01-02'), endTime: new Date('2023-01-04') },
|
|
62
|
+
];
|
|
63
|
+
const result = applyLanes(data, { start: 'startTime', end: 'endTime' });
|
|
64
|
+
expect(result).toEqual([
|
|
65
|
+
{
|
|
66
|
+
name: 'Task 1',
|
|
67
|
+
startTime: new Date('2023-01-01'),
|
|
68
|
+
endTime: new Date('2023-01-03'),
|
|
69
|
+
lane: 0,
|
|
70
|
+
},
|
|
71
|
+
{
|
|
72
|
+
name: 'Task 2',
|
|
73
|
+
startTime: new Date('2023-01-02'),
|
|
74
|
+
endTime: new Date('2023-01-04'),
|
|
75
|
+
lane: 1,
|
|
76
|
+
},
|
|
77
|
+
]);
|
|
78
|
+
});
|
|
79
|
+
it('should work with nested string keys for start and end', () => {
|
|
80
|
+
const data = [
|
|
81
|
+
{ name: 'Task 1', duration: { start: new Date('2023-01-01'), end: new Date('2023-01-02') } },
|
|
82
|
+
{ name: 'Task 2', duration: { start: new Date('2023-01-03'), end: new Date('2023-01-04') } },
|
|
83
|
+
];
|
|
84
|
+
const result = applyLanes(data, { start: 'duration.start', end: 'duration.end' });
|
|
85
|
+
expect(result).toEqual([
|
|
86
|
+
{
|
|
87
|
+
name: 'Task 1',
|
|
88
|
+
duration: { start: new Date('2023-01-01'), end: new Date('2023-01-02') },
|
|
89
|
+
lane: 0,
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
name: 'Task 2',
|
|
93
|
+
duration: { start: new Date('2023-01-03'), end: new Date('2023-01-04') },
|
|
94
|
+
lane: 0,
|
|
95
|
+
},
|
|
96
|
+
]);
|
|
97
|
+
});
|
|
98
|
+
it('should work with function accessors for start and end', () => {
|
|
99
|
+
const data = [
|
|
100
|
+
{ name: 'Task 1', duration: { start: new Date('2023-01-01'), end: new Date('2023-01-02') } },
|
|
101
|
+
{ name: 'Task 2', duration: { start: new Date('2023-01-03'), end: new Date('2023-01-04') } },
|
|
102
|
+
];
|
|
103
|
+
const result = applyLanes(data, { start: (d) => d.duration.start, end: (d) => d.duration.end });
|
|
104
|
+
expect(result).toEqual([
|
|
105
|
+
{
|
|
106
|
+
name: 'Task 1',
|
|
107
|
+
duration: { start: new Date('2023-01-01'), end: new Date('2023-01-02') },
|
|
108
|
+
lane: 0,
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
name: 'Task 2',
|
|
112
|
+
duration: { start: new Date('2023-01-03'), end: new Date('2023-01-04') },
|
|
113
|
+
lane: 0,
|
|
114
|
+
},
|
|
115
|
+
]);
|
|
116
|
+
});
|
|
117
|
+
it('should handle empty array', () => {
|
|
118
|
+
const data = [];
|
|
119
|
+
const result = applyLanes(data);
|
|
120
|
+
expect(result).toEqual([]);
|
|
121
|
+
});
|
|
122
|
+
it('should handle single event', () => {
|
|
123
|
+
const data = [{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-02') }];
|
|
124
|
+
const result = applyLanes(data);
|
|
125
|
+
expect(result).toEqual([
|
|
126
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-02'), lane: 0 },
|
|
127
|
+
]);
|
|
128
|
+
});
|
|
129
|
+
it('should handle complex overlapping scenario', () => {
|
|
130
|
+
const data = [
|
|
131
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-05') }, // long event
|
|
132
|
+
{ id: 2, start: new Date('2023-01-02'), end: new Date('2023-01-03') }, // short event inside
|
|
133
|
+
{ id: 3, start: new Date('2023-01-02T12:00:00'), end: new Date('2023-01-04') }, // overlaps with both
|
|
134
|
+
{ id: 4, start: new Date('2023-01-03'), end: new Date('2023-01-04T12:00:00') }, // overlaps with 1 and 3
|
|
135
|
+
{ id: 5, start: new Date('2023-01-06'), end: new Date('2023-01-08') }, // separate event
|
|
136
|
+
];
|
|
137
|
+
const result = applyLanes(data);
|
|
138
|
+
expect(result).toEqual([
|
|
139
|
+
{ id: 1, start: new Date('2023-01-01'), end: new Date('2023-01-05'), lane: 0 },
|
|
140
|
+
{ id: 2, start: new Date('2023-01-02'), end: new Date('2023-01-03'), lane: 1 },
|
|
141
|
+
{ id: 3, start: new Date('2023-01-02T12:00:00'), end: new Date('2023-01-04'), lane: 2 },
|
|
142
|
+
{ id: 4, start: new Date('2023-01-03'), end: new Date('2023-01-04T12:00:00'), lane: 1 }, // can reuse lane 1 since id: 2 ended
|
|
143
|
+
{ id: 5, start: new Date('2023-01-06'), end: new Date('2023-01-08'), lane: 0 }, // can reuse lane 0 since id: 1 ended
|
|
144
|
+
]);
|
|
145
|
+
});
|
|
146
|
+
it('should preserve all original properties', () => {
|
|
147
|
+
const data = [
|
|
148
|
+
{
|
|
149
|
+
id: 1,
|
|
150
|
+
start: new Date('2023-01-01'),
|
|
151
|
+
end: new Date('2023-01-02'),
|
|
152
|
+
name: 'First',
|
|
153
|
+
priority: 'high',
|
|
154
|
+
metadata: { foo: 'bar' },
|
|
155
|
+
},
|
|
156
|
+
{
|
|
157
|
+
id: 2,
|
|
158
|
+
start: new Date('2023-01-01T12:00:00'),
|
|
159
|
+
end: new Date('2023-01-03'),
|
|
160
|
+
name: 'Second',
|
|
161
|
+
priority: 'low',
|
|
162
|
+
metadata: { baz: 'qux' },
|
|
163
|
+
},
|
|
164
|
+
];
|
|
165
|
+
const result = applyLanes(data);
|
|
166
|
+
expect(result).toEqual([
|
|
167
|
+
{
|
|
168
|
+
id: 1,
|
|
169
|
+
start: new Date('2023-01-01'),
|
|
170
|
+
end: new Date('2023-01-02'),
|
|
171
|
+
name: 'First',
|
|
172
|
+
priority: 'high',
|
|
173
|
+
metadata: { foo: 'bar' },
|
|
174
|
+
lane: 0,
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
id: 2,
|
|
178
|
+
start: new Date('2023-01-01T12:00:00'),
|
|
179
|
+
end: new Date('2023-01-03'),
|
|
180
|
+
name: 'Second',
|
|
181
|
+
priority: 'low',
|
|
182
|
+
metadata: { baz: 'qux' },
|
|
183
|
+
lane: 1,
|
|
184
|
+
},
|
|
185
|
+
]);
|
|
186
|
+
});
|
|
187
|
+
it('should work with numeric values', () => {
|
|
188
|
+
const data = [
|
|
189
|
+
{ id: 1, start: 0, end: 3 },
|
|
190
|
+
{ id: 2, start: 1, end: 4 },
|
|
191
|
+
{ id: 3, start: 5, end: 7 },
|
|
192
|
+
];
|
|
193
|
+
const result = applyLanes(data);
|
|
194
|
+
expect(result).toEqual([
|
|
195
|
+
{ id: 1, start: 0, end: 3, lane: 0 },
|
|
196
|
+
{ id: 2, start: 1, end: 4, lane: 1 },
|
|
197
|
+
{ id: 3, start: 5, end: 7, lane: 0 }, // can reuse lane 0 since id: 1 ended
|
|
198
|
+
]);
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -1,14 +1,4 @@
|
|
|
1
|
-
|
|
2
|
-
* Creates a string containing a class name that can be used by
|
|
3
|
-
* developers to target a specific layer/element within a LayerChart.
|
|
4
|
-
*
|
|
5
|
-
* This is a function so that the class names remain consistent and the
|
|
6
|
-
* prefix/structure can be changed in the future if needed
|
|
7
|
-
*
|
|
8
|
-
* @param layerName - the name of the layer to be appended to the generated class name
|
|
9
|
-
* @returns a string to be used as a class on an element
|
|
10
|
-
*/
|
|
11
|
-
export declare function layerClass(layerName: string): string;
|
|
1
|
+
import type { ClassValue } from 'svelte/elements';
|
|
12
2
|
type ExtractObjectType<T> = T extends object ? (T extends Function ? never : T) : never;
|
|
13
3
|
type WithClass<T> = T & {
|
|
14
4
|
class?: string;
|
|
@@ -21,9 +11,9 @@ type DefaultProps = WithClass<{
|
|
|
21
11
|
* a class name to its class property to identify the layer for CSS targeting.
|
|
22
12
|
*
|
|
23
13
|
* @param props The props to be extracted, can be an object, function or any other type
|
|
24
|
-
* @param
|
|
14
|
+
* @param className The class name to be applied to the layer for targeting styling (e.g. 'lc-layer')
|
|
25
15
|
* @param extraClasses Additional classes to be applied to the layer if they don't exist in the props already
|
|
26
16
|
* @returns a typed spreadable object with props for the layer
|
|
27
17
|
*/
|
|
28
|
-
export declare function extractLayerProps<T>(props: T,
|
|
18
|
+
export declare function extractLayerProps<T>(props: T, className: string, ...extraClasses: ClassValue[]): WithClass<ExtractObjectType<T> extends never ? DefaultProps : ExtractObjectType<T>>;
|
|
29
19
|
export {};
|
package/dist/utils/attributes.js
CHANGED
|
@@ -1,17 +1,4 @@
|
|
|
1
1
|
import { cls } from '@layerstack/tailwind';
|
|
2
|
-
/**
|
|
3
|
-
* Creates a string containing a class name that can be used by
|
|
4
|
-
* developers to target a specific layer/element within a LayerChart.
|
|
5
|
-
*
|
|
6
|
-
* This is a function so that the class names remain consistent and the
|
|
7
|
-
* prefix/structure can be changed in the future if needed
|
|
8
|
-
*
|
|
9
|
-
* @param layerName - the name of the layer to be appended to the generated class name
|
|
10
|
-
* @returns a string to be used as a class on an element
|
|
11
|
-
*/
|
|
12
|
-
export function layerClass(layerName) {
|
|
13
|
-
return `lc-${layerName}`;
|
|
14
|
-
}
|
|
15
2
|
// type guard to narrow props to an object with optional class
|
|
16
3
|
// for extractLayerProps
|
|
17
4
|
function isObjectWithClass(val) {
|
|
@@ -22,19 +9,18 @@ function isObjectWithClass(val) {
|
|
|
22
9
|
* a class name to its class property to identify the layer for CSS targeting.
|
|
23
10
|
*
|
|
24
11
|
* @param props The props to be extracted, can be an object, function or any other type
|
|
25
|
-
* @param
|
|
12
|
+
* @param className The class name to be applied to the layer for targeting styling (e.g. 'lc-layer')
|
|
26
13
|
* @param extraClasses Additional classes to be applied to the layer if they don't exist in the props already
|
|
27
14
|
* @returns a typed spreadable object with props for the layer
|
|
28
15
|
*/
|
|
29
|
-
export function extractLayerProps(props,
|
|
30
|
-
const className = layerClass(layerName);
|
|
16
|
+
export function extractLayerProps(props, className, ...extraClasses) {
|
|
31
17
|
if (isObjectWithClass(props)) {
|
|
32
18
|
return {
|
|
33
19
|
...props,
|
|
34
|
-
class: cls(className, props.class
|
|
20
|
+
class: cls(className, ...extraClasses, props.class),
|
|
35
21
|
};
|
|
36
22
|
}
|
|
37
23
|
return {
|
|
38
|
-
class: cls(className, extraClasses),
|
|
24
|
+
class: cls(className, ...extraClasses),
|
|
39
25
|
};
|
|
40
26
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { ClassValue } from 'svelte/elements';
|
|
2
|
+
import type { PatternShape } from '../components/Pattern.svelte';
|
|
3
|
+
export declare const DEFAULT_FILL = "rgb(0, 0, 0)";
|
|
4
|
+
type StyleOptions = Partial<Omit<CSSStyleDeclaration, 'fillOpacity' | 'strokeWidth' | 'opacity'> & {
|
|
5
|
+
fillOpacity?: number | string;
|
|
6
|
+
strokeWidth?: number | string;
|
|
7
|
+
opacity?: number | string;
|
|
8
|
+
}>;
|
|
9
|
+
export type ComputedStylesOptions = {
|
|
10
|
+
styles?: StyleOptions;
|
|
11
|
+
classes?: ClassValue | null;
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Appends or reuses `<svg>` element below `<canvas>` to resolve CSS variables and classes (ex. `stroke: var(--color-primary)` => `stroke: rgb(...)` )
|
|
15
|
+
*/
|
|
16
|
+
export declare function _getComputedStyles(canvas: HTMLCanvasElement, { styles, classes }?: ComputedStylesOptions): CSSStyleDeclaration;
|
|
17
|
+
export declare const getComputedStyles: any;
|
|
18
|
+
/** Render SVG path data onto canvas context. Supports CSS variables and classes by tranferring to hidden `<svg>` element before retrieval) */
|
|
19
|
+
export declare function renderPathData(ctx: CanvasRenderingContext2D, pathData: string | null | undefined, styleOptions?: ComputedStylesOptions): void;
|
|
20
|
+
export declare function renderText(ctx: CanvasRenderingContext2D, text: string | number | null | undefined, coords: {
|
|
21
|
+
x: number;
|
|
22
|
+
y: number;
|
|
23
|
+
}, styleOptions?: ComputedStylesOptions): void;
|
|
24
|
+
export declare function renderRect(ctx: CanvasRenderingContext2D, coords: {
|
|
25
|
+
x: number;
|
|
26
|
+
y: number;
|
|
27
|
+
width: number;
|
|
28
|
+
height: number;
|
|
29
|
+
}, styleOptions?: ComputedStylesOptions): void;
|
|
30
|
+
export declare function renderCircle(ctx: CanvasRenderingContext2D, coords: {
|
|
31
|
+
cx: number;
|
|
32
|
+
cy: number;
|
|
33
|
+
r: number;
|
|
34
|
+
}, styleOptions?: ComputedStylesOptions): void;
|
|
35
|
+
export declare function renderEllipse(ctx: CanvasRenderingContext2D, coords: {
|
|
36
|
+
cx: number;
|
|
37
|
+
cy: number;
|
|
38
|
+
rx: number;
|
|
39
|
+
ry: number;
|
|
40
|
+
}, styleOptions?: ComputedStylesOptions): void;
|
|
41
|
+
/** Clear canvas accounting for Canvas `context.translate(...)` */
|
|
42
|
+
export declare function clearCanvasContext(ctx: CanvasRenderingContext2D, options: {
|
|
43
|
+
containerWidth: number;
|
|
44
|
+
containerHeight: number;
|
|
45
|
+
padding: {
|
|
46
|
+
top: number;
|
|
47
|
+
bottom: number;
|
|
48
|
+
left: number;
|
|
49
|
+
right: number;
|
|
50
|
+
};
|
|
51
|
+
}): void;
|
|
52
|
+
/**
|
|
53
|
+
Scales a canvas for high DPI / retina displays.
|
|
54
|
+
@see: https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio#examples
|
|
55
|
+
@see: https://web.dev/articles/canvas-hidipi
|
|
56
|
+
*/
|
|
57
|
+
export declare function scaleCanvas(ctx: CanvasRenderingContext2D, width: number, height: number): {
|
|
58
|
+
width: number;
|
|
59
|
+
height: number;
|
|
60
|
+
};
|
|
61
|
+
/** Get pixel color (r,g,b,a) at canvas coordinates */
|
|
62
|
+
export declare function getPixelColor(ctx: CanvasRenderingContext2D, x: number, y: number): {
|
|
63
|
+
r: number;
|
|
64
|
+
g: number;
|
|
65
|
+
b: number;
|
|
66
|
+
a: number;
|
|
67
|
+
};
|
|
68
|
+
export declare function _createLinearGradient(ctx: CanvasRenderingContext2D, x0: number, y0: number, x1: number, y1: number, stops: {
|
|
69
|
+
offset: number;
|
|
70
|
+
color: string;
|
|
71
|
+
}[]): CanvasGradient;
|
|
72
|
+
/** Create linear gradient and memoize result to fix reactivity */
|
|
73
|
+
export declare const createLinearGradient: any;
|
|
74
|
+
export declare function _createPattern(ctx: CanvasRenderingContext2D, width: number, height: number, shapes: PatternShape[], background?: string): CanvasPattern | null;
|
|
75
|
+
/** Create pattern and memoize result to fix reactivity */
|
|
76
|
+
export declare const createPattern: any;
|
|
77
|
+
export {};
|
package/dist/utils/canvas.js
CHANGED
|
@@ -1,11 +1,25 @@
|
|
|
1
|
+
import memoize from 'memoize';
|
|
1
2
|
import { cls } from '@layerstack/tailwind';
|
|
2
|
-
import { memoize } from 'lodash-es';
|
|
3
3
|
export const DEFAULT_FILL = 'rgb(0, 0, 0)';
|
|
4
4
|
const CANVAS_STYLES_ELEMENT_ID = '__layerchart_canvas_styles_id';
|
|
5
|
+
const supportedStyles = [
|
|
6
|
+
'fill',
|
|
7
|
+
'fillOpacity',
|
|
8
|
+
'stroke',
|
|
9
|
+
'strokeWidth',
|
|
10
|
+
'opacity',
|
|
11
|
+
'fontWeight',
|
|
12
|
+
'fontSize',
|
|
13
|
+
'fontFamily',
|
|
14
|
+
'textAnchor',
|
|
15
|
+
'textAlign',
|
|
16
|
+
'paintOrder',
|
|
17
|
+
];
|
|
5
18
|
/**
|
|
6
19
|
* Appends or reuses `<svg>` element below `<canvas>` to resolve CSS variables and classes (ex. `stroke: var(--color-primary)` => `stroke: rgb(...)` )
|
|
7
20
|
*/
|
|
8
|
-
export function
|
|
21
|
+
export function _getComputedStyles(canvas, { styles, classes } = {}) {
|
|
22
|
+
// console.count(`getComputedStyles: ${getComputedStylesKey(canvas, { styles, classes })}`);
|
|
9
23
|
try {
|
|
10
24
|
// Get or create `<svg>` below `<canvas>`
|
|
11
25
|
let svg = document.getElementById(CANVAS_STYLES_ELEMENT_ID);
|
|
@@ -24,13 +38,19 @@ export function getComputedStyles(canvas, { styles, classes } = {}) {
|
|
|
24
38
|
if (styles) {
|
|
25
39
|
Object.assign(svg.style, styles);
|
|
26
40
|
}
|
|
41
|
+
// Make sure `<svg>` is not visible
|
|
42
|
+
svg.style.display = 'none';
|
|
27
43
|
if (classes) {
|
|
28
44
|
svg.setAttribute('class', cls(classes)
|
|
29
45
|
.split(' ')
|
|
30
46
|
.filter((s) => !s.startsWith('transition-'))
|
|
31
47
|
.join(' '));
|
|
32
48
|
}
|
|
33
|
-
|
|
49
|
+
// Capture copy to enable memoization and avoid capturing all styles (which is very slow)
|
|
50
|
+
const computedStyles = supportedStyles.reduce((acc, style) => {
|
|
51
|
+
acc[style] = window.getComputedStyle(svg)[style];
|
|
52
|
+
return acc;
|
|
53
|
+
}, {});
|
|
34
54
|
return computedStyles;
|
|
35
55
|
}
|
|
36
56
|
catch (e) {
|
|
@@ -38,38 +58,71 @@ export function getComputedStyles(canvas, { styles, classes } = {}) {
|
|
|
38
58
|
return {};
|
|
39
59
|
}
|
|
40
60
|
}
|
|
61
|
+
function getComputedStylesKey(canvas, { styles, classes } = {}) {
|
|
62
|
+
return JSON.stringify({ canvasId: canvas.id, styles, classes });
|
|
63
|
+
}
|
|
64
|
+
export const getComputedStyles = memoize(_getComputedStyles, {
|
|
65
|
+
cacheKey: ([canvas, styleOptions]) => {
|
|
66
|
+
return getComputedStylesKey(canvas, styleOptions);
|
|
67
|
+
},
|
|
68
|
+
});
|
|
41
69
|
/** Render onto canvas context. Supports CSS variables and classes by tranferring to hidden `<svg>` element before retrieval) */
|
|
42
|
-
function render(ctx, render, styleOptions = {}) {
|
|
70
|
+
function render(ctx, render, styleOptions = {}, { applyText, } = {}) {
|
|
43
71
|
// console.count('render');
|
|
44
72
|
// TODO: Consider memoizing? How about reactiving to CSS variable changes (light/dark mode toggle)
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
73
|
+
let resolvedStyles;
|
|
74
|
+
if (styleOptions.classes == null &&
|
|
75
|
+
!Object.values(styleOptions.styles ?? {}).some((v) => typeof v === 'string' && v.includes('var('))) {
|
|
76
|
+
// Skip resolving styles if no classes are provided and no styles are using CSS variables
|
|
77
|
+
resolvedStyles = styleOptions.styles ?? {};
|
|
50
78
|
}
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
79
|
+
else {
|
|
80
|
+
// Remove constant non-css variable properties (ex. `strokeWidth: 0.5`, `fill: #123456`) as not needed and improves memoization cache hit
|
|
81
|
+
const { constantStyles, variableStyles } = Object.entries(styleOptions.styles ?? {}).reduce((acc, [key, value]) => {
|
|
82
|
+
if (typeof value === 'number' || (typeof value === 'string' && !value.includes('var('))) {
|
|
83
|
+
acc.constantStyles[key] = value;
|
|
84
|
+
}
|
|
85
|
+
else if (typeof value === 'string' && value.includes('var(')) {
|
|
86
|
+
acc.variableStyles[key] = value;
|
|
87
|
+
}
|
|
88
|
+
return acc;
|
|
89
|
+
}, { constantStyles: {}, variableStyles: {} });
|
|
90
|
+
const computedStyles = getComputedStyles(ctx.canvas, {
|
|
91
|
+
styles: variableStyles,
|
|
92
|
+
classes: styleOptions.classes,
|
|
93
|
+
});
|
|
94
|
+
resolvedStyles = { ...computedStyles, ...constantStyles };
|
|
56
95
|
}
|
|
57
|
-
|
|
58
|
-
|
|
96
|
+
// Adhere to CSS paint order: https://developer.mozilla.org/en-US/docs/Web/CSS/paint-order
|
|
97
|
+
const paintOrder = resolvedStyles?.paintOrder === 'stroke' ? ['stroke', 'fill'] : ['fill', 'stroke'];
|
|
98
|
+
if (resolvedStyles?.opacity) {
|
|
99
|
+
ctx.globalAlpha = Number(resolvedStyles?.opacity);
|
|
59
100
|
}
|
|
60
|
-
|
|
61
|
-
|
|
101
|
+
// font/text properties can be expensive to set (not sure why), so only apply if needed (renderText())
|
|
102
|
+
if (applyText) {
|
|
103
|
+
// Text properties
|
|
104
|
+
ctx.font = `${resolvedStyles.fontWeight} ${resolvedStyles.fontSize} ${resolvedStyles.fontFamily}`; // build string instead of using `computedStyles.font` to fix/workaround `tabular-nums` returning `null`
|
|
105
|
+
// TODO: Hack to handle `textAnchor` with canvas. Try to find a better approach
|
|
106
|
+
if (resolvedStyles.textAnchor === 'middle') {
|
|
107
|
+
ctx.textAlign = 'center';
|
|
108
|
+
}
|
|
109
|
+
else if (resolvedStyles.textAnchor === 'end') {
|
|
110
|
+
ctx.textAlign = 'right';
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
ctx.textAlign = resolvedStyles.textAlign; // TODO: Handle/map `justify` and `match-parent`?
|
|
114
|
+
}
|
|
115
|
+
// TODO: Handle `textBaseline` / `verticalAnchor` (Text)
|
|
116
|
+
// ctx.textBaseline = 'top';
|
|
117
|
+
// ctx.textBaseline = 'middle';
|
|
118
|
+
// ctx.textBaseline = 'bottom';
|
|
119
|
+
// ctx.textBaseline = 'alphabetic';
|
|
120
|
+
// ctx.textBaseline = 'hanging';
|
|
121
|
+
// ctx.textBaseline = 'ideographic';
|
|
62
122
|
}
|
|
63
|
-
// TODO: Handle `textBaseline` / `verticalAnchor` (Text)
|
|
64
|
-
// ctx.textBaseline = 'top';
|
|
65
|
-
// ctx.textBaseline = 'middle';
|
|
66
|
-
// ctx.textBaseline = 'bottom';
|
|
67
|
-
// ctx.textBaseline = 'alphabetic';
|
|
68
|
-
// ctx.textBaseline = 'hanging';
|
|
69
|
-
// ctx.textBaseline = 'ideographic';
|
|
70
123
|
// Dashed lines
|
|
71
|
-
if (
|
|
72
|
-
const dashArray =
|
|
124
|
+
if (resolvedStyles.strokeDasharray?.includes(',')) {
|
|
125
|
+
const dashArray = resolvedStyles.strokeDasharray
|
|
73
126
|
.split(',')
|
|
74
127
|
.map((s) => Number(s.replace('px', '')));
|
|
75
128
|
ctx.setLineDash(dashArray);
|
|
@@ -81,11 +134,11 @@ function render(ctx, render, styleOptions = {}) {
|
|
|
81
134
|
styleOptions.styles?.fill instanceof CanvasPattern ||
|
|
82
135
|
!styleOptions.styles?.fill?.includes('var'))
|
|
83
136
|
? styleOptions.styles.fill
|
|
84
|
-
:
|
|
137
|
+
: resolvedStyles?.fill;
|
|
85
138
|
if (fill && !['none', DEFAULT_FILL].includes(fill)) {
|
|
86
139
|
const currentGlobalAlpha = ctx.globalAlpha;
|
|
87
|
-
const fillOpacity = Number(
|
|
88
|
-
const opacity = Number(
|
|
140
|
+
const fillOpacity = Number(resolvedStyles?.fillOpacity);
|
|
141
|
+
const opacity = Number(resolvedStyles?.opacity);
|
|
89
142
|
ctx.globalAlpha = fillOpacity * opacity;
|
|
90
143
|
ctx.fillStyle = fill;
|
|
91
144
|
render.fill(ctx);
|
|
@@ -98,12 +151,12 @@ function render(ctx, render, styleOptions = {}) {
|
|
|
98
151
|
(styleOptions.styles?.stroke instanceof CanvasGradient ||
|
|
99
152
|
!styleOptions.styles?.stroke?.includes('var'))
|
|
100
153
|
? styleOptions.styles?.stroke
|
|
101
|
-
:
|
|
154
|
+
: resolvedStyles?.stroke;
|
|
102
155
|
if (stroke && !['none'].includes(stroke)) {
|
|
103
156
|
ctx.lineWidth =
|
|
104
|
-
typeof
|
|
105
|
-
? Number(
|
|
106
|
-
: (
|
|
157
|
+
typeof resolvedStyles?.strokeWidth === 'string'
|
|
158
|
+
? Number(resolvedStyles?.strokeWidth?.replace('px', ''))
|
|
159
|
+
: (resolvedStyles?.strokeWidth ?? 1);
|
|
107
160
|
ctx.strokeStyle = stroke;
|
|
108
161
|
render.stroke(ctx);
|
|
109
162
|
}
|
|
@@ -123,7 +176,7 @@ export function renderText(ctx, text, coords, styleOptions = {}) {
|
|
|
123
176
|
render(ctx, {
|
|
124
177
|
fill: (ctx) => ctx.fillText(text.toString(), coords.x, coords.y),
|
|
125
178
|
stroke: (ctx) => ctx.strokeText(text.toString(), coords.x, coords.y),
|
|
126
|
-
}, styleOptions);
|
|
179
|
+
}, styleOptions, { applyText: true });
|
|
127
180
|
}
|
|
128
181
|
}
|
|
129
182
|
export function renderRect(ctx, coords, styleOptions = {}) {
|
|
@@ -145,6 +198,19 @@ export function renderCircle(ctx, coords, styleOptions = {}) {
|
|
|
145
198
|
}, styleOptions);
|
|
146
199
|
ctx.closePath();
|
|
147
200
|
}
|
|
201
|
+
export function renderEllipse(ctx, coords, styleOptions = {}) {
|
|
202
|
+
ctx.beginPath();
|
|
203
|
+
ctx.ellipse(coords.cx, coords.cy, coords.rx, coords.ry, 0, 0, 2 * Math.PI);
|
|
204
|
+
render(ctx, {
|
|
205
|
+
fill: (ctx) => {
|
|
206
|
+
ctx.fill();
|
|
207
|
+
},
|
|
208
|
+
stroke: (ctx) => {
|
|
209
|
+
ctx.stroke();
|
|
210
|
+
},
|
|
211
|
+
}, styleOptions);
|
|
212
|
+
ctx.closePath();
|
|
213
|
+
}
|
|
148
214
|
/** Clear canvas accounting for Canvas `context.translate(...)` */
|
|
149
215
|
export function clearCanvasContext(ctx, options) {
|
|
150
216
|
// Clear with negative offset due to Canvas `context.translate(...)`
|
|
@@ -179,9 +245,8 @@ export function _createLinearGradient(ctx, x0, y0, x1, y1, stops) {
|
|
|
179
245
|
return gradient;
|
|
180
246
|
}
|
|
181
247
|
/** Create linear gradient and memoize result to fix reactivity */
|
|
182
|
-
export const createLinearGradient = memoize(_createLinearGradient,
|
|
183
|
-
|
|
184
|
-
return key;
|
|
248
|
+
export const createLinearGradient = memoize(_createLinearGradient, {
|
|
249
|
+
cacheKey: (args) => JSON.stringify(args.slice(1)), // Ignore `ctx` argument
|
|
185
250
|
});
|
|
186
251
|
export function _createPattern(ctx, width, height, shapes, background) {
|
|
187
252
|
const patternCanvas = document.createElement('canvas');
|
|
@@ -214,7 +279,6 @@ export function _createPattern(ctx, width, height, shapes, background) {
|
|
|
214
279
|
return pattern;
|
|
215
280
|
}
|
|
216
281
|
/** Create pattern and memoize result to fix reactivity */
|
|
217
|
-
export const createPattern = memoize(_createPattern,
|
|
218
|
-
|
|
219
|
-
return key;
|
|
282
|
+
export const createPattern = memoize(_createPattern, {
|
|
283
|
+
cacheKey: (args) => JSON.stringify(args.slice(1)), // Ignore `ctx` argument
|
|
220
284
|
});
|
package/dist/utils/common.d.ts
CHANGED
|
@@ -16,3 +16,12 @@ export declare function defaultChartPadding<TData, SeriesComponent extends Compo
|
|
|
16
16
|
* Handles complex objects such as `Date` by invoking `.valueOf()`
|
|
17
17
|
*/
|
|
18
18
|
export declare function findRelatedData(data: any[], original: any, accessor: Function): any;
|
|
19
|
+
/**
|
|
20
|
+
* Return the object if the value is an object, otherwise return null.
|
|
21
|
+
* Functions (including Snippet types) are treated as non-objects and return null.
|
|
22
|
+
*/
|
|
23
|
+
export declare function getObjectOrNull<T>(value: T): T extends object ? T extends Function ? null : T : T extends null ? null : T extends undefined ? undefined : null;
|
|
24
|
+
/**
|
|
25
|
+
* Call with args if function, otherwise return the value.
|
|
26
|
+
*/
|
|
27
|
+
export declare function resolveMaybeFn<T>(value: T | ((...args: any[]) => T), ...args: any[]): any;
|
package/dist/utils/common.js
CHANGED
|
@@ -40,7 +40,7 @@ export function defaultChartPadding(axis = true, legend = false) {
|
|
|
40
40
|
return {
|
|
41
41
|
top: axis === true || axis === 'y' ? 4 : 0,
|
|
42
42
|
left: axis === true || axis === 'y' ? 20 : 0,
|
|
43
|
-
bottom: (axis === true || axis === 'x' ? 20 : 0) + (legend
|
|
43
|
+
bottom: (axis === true || axis === 'x' ? 20 : 0) + (legend ? 32 : 0),
|
|
44
44
|
right: axis === true || axis === 'x' ? 4 : 0,
|
|
45
45
|
};
|
|
46
46
|
}
|
|
@@ -54,3 +54,20 @@ export function findRelatedData(data, original, accessor) {
|
|
|
54
54
|
return accessor(d)?.valueOf() === accessor(original)?.valueOf();
|
|
55
55
|
});
|
|
56
56
|
}
|
|
57
|
+
/**
|
|
58
|
+
* Return the object if the value is an object, otherwise return null.
|
|
59
|
+
* Functions (including Snippet types) are treated as non-objects and return null.
|
|
60
|
+
*/
|
|
61
|
+
export function getObjectOrNull(value) {
|
|
62
|
+
if (typeof value === 'object')
|
|
63
|
+
return value;
|
|
64
|
+
if (value === undefined)
|
|
65
|
+
return undefined;
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Call with args if function, otherwise return the value.
|
|
70
|
+
*/
|
|
71
|
+
export function resolveMaybeFn(value, ...args) {
|
|
72
|
+
return typeof value === 'function' ? value(...args) : value;
|
|
73
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { describe, it, expect } from 'vitest';
|
|
2
|
-
import { accessor } from './common.js';
|
|
2
|
+
import { accessor, resolveMaybeFn, getObjectOrNull } from './common.js';
|
|
3
3
|
export const testData = {
|
|
4
4
|
one: 1,
|
|
5
5
|
two: 2,
|
|
@@ -36,3 +36,28 @@ describe('accessor', () => {
|
|
|
36
36
|
expect(actual).toEqual(testData);
|
|
37
37
|
});
|
|
38
38
|
});
|
|
39
|
+
describe('getObjectOrNull', () => {
|
|
40
|
+
it('returns null for non-object values', () => {
|
|
41
|
+
expect(getObjectOrNull(5)).toBeNull();
|
|
42
|
+
expect(getObjectOrNull('string')).toBeNull();
|
|
43
|
+
expect(getObjectOrNull(null)).toBeNull();
|
|
44
|
+
expect(getObjectOrNull(undefined)).toBeUndefined();
|
|
45
|
+
});
|
|
46
|
+
it('returns null for functions', () => {
|
|
47
|
+
const fn = () => { };
|
|
48
|
+
expect(getObjectOrNull(fn)).toBeNull();
|
|
49
|
+
});
|
|
50
|
+
it('returns the object if value is an object', () => {
|
|
51
|
+
const obj = { a: 1 };
|
|
52
|
+
expect(getObjectOrNull(obj)).toBe(obj);
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
describe('resolveMaybeFn', () => {
|
|
56
|
+
it('returns value if not a function', () => {
|
|
57
|
+
expect(resolveMaybeFn(5)).toBe(5);
|
|
58
|
+
});
|
|
59
|
+
it('calls function with args', () => {
|
|
60
|
+
const fn = (a, b) => a + b;
|
|
61
|
+
expect(resolveMaybeFn(fn, 2, 3)).toBe(5);
|
|
62
|
+
});
|
|
63
|
+
});
|