basalt-ui 1.14.0 → 1.16.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/AGENTS.md +1 -1
- package/CHANGELOG.md +15 -0
- package/README.md +38 -2
- package/agent/rules/basalt-charts.md +279 -108
- package/agent/rules/basalt-mantine.md +4 -2
- package/agent/skills/basalt-charts/SKILL.md +114 -64
- package/agent/skills/basalt-design/SKILL.md +4 -3
- package/agent/templates/CLAUDE-block.md.tpl +9 -6
- package/configs/oxlint-plugin.js +152 -0
- package/configs/oxlint-plugin.test.ts +107 -0
- package/configs/oxlint.json +2 -0
- package/dist/charts/cursor/resolve.d.ts +33 -0
- package/dist/charts/cursor/resolve.d.ts.map +1 -0
- package/dist/charts/cursor/resolve.js +61 -0
- package/dist/charts/cursor/resolve.test.d.ts +2 -0
- package/dist/charts/cursor/resolve.test.d.ts.map +1 -0
- package/dist/charts/cursor/scope.d.ts +17 -0
- package/dist/charts/cursor/scope.d.ts.map +1 -0
- package/dist/charts/cursor/scope.js +20 -0
- package/dist/charts/cursor/store.d.ts +31 -0
- package/dist/charts/cursor/store.d.ts.map +1 -0
- package/dist/charts/cursor/store.js +25 -0
- package/dist/charts/cursor/store.test.d.ts +2 -0
- package/dist/charts/cursor/store.test.d.ts.map +1 -0
- package/dist/charts/hooks/useChartCursor.d.ts +39 -0
- package/dist/charts/hooks/useChartCursor.d.ts.map +1 -0
- package/dist/charts/hooks/useChartCursor.js +104 -0
- package/dist/charts/hooks/useChartSize.d.ts +8 -4
- package/dist/charts/hooks/useChartSize.d.ts.map +1 -1
- package/dist/charts/index.d.ts +16 -13
- package/dist/charts/index.d.ts.map +1 -1
- package/dist/charts/index.js +38 -19
- package/dist/charts/kinds/Bars.d.ts +45 -54
- package/dist/charts/kinds/Bars.d.ts.map +1 -1
- package/dist/charts/kinds/Bars.js +235 -392
- package/dist/charts/kinds/Bars.test.d.ts +2 -0
- package/dist/charts/kinds/Bars.test.d.ts.map +1 -0
- package/dist/charts/kinds/Donut.d.ts +5 -4
- package/dist/charts/kinds/Donut.d.ts.map +1 -1
- package/dist/charts/kinds/Donut.js +17 -14
- package/dist/charts/kinds/DualPanel.d.ts +13 -0
- package/dist/charts/kinds/DualPanel.d.ts.map +1 -1
- package/dist/charts/kinds/DualPanel.js +185 -92
- package/dist/charts/kinds/DualPanel.test.d.ts +2 -0
- package/dist/charts/kinds/DualPanel.test.d.ts.map +1 -0
- package/dist/charts/kinds/Heatmap.d.ts +15 -11
- package/dist/charts/kinds/Heatmap.d.ts.map +1 -1
- package/dist/charts/kinds/Heatmap.js +38 -22
- package/dist/charts/kinds/MultiLine.d.ts +27 -35
- package/dist/charts/kinds/MultiLine.d.ts.map +1 -1
- package/dist/charts/kinds/MultiLine.js +122 -235
- package/dist/charts/kinds/MultiLine.test.d.ts +2 -0
- package/dist/charts/kinds/MultiLine.test.d.ts.map +1 -0
- package/dist/charts/kinds/StackedArea.d.ts +21 -10
- package/dist/charts/kinds/StackedArea.d.ts.map +1 -1
- package/dist/charts/kinds/StackedArea.js +74 -132
- package/dist/charts/kinds/ZonedLine.d.ts +24 -40
- package/dist/charts/kinds/ZonedLine.d.ts.map +1 -1
- package/dist/charts/kinds/ZonedLine.js +104 -184
- package/dist/charts/kinds/ZonedLine.test.d.ts +2 -0
- package/dist/charts/kinds/ZonedLine.test.d.ts.map +1 -0
- package/dist/charts/layout/auto-margin.d.ts +60 -0
- package/dist/charts/layout/auto-margin.d.ts.map +1 -0
- package/dist/charts/layout/auto-margin.js +37 -0
- package/dist/charts/layout/auto-margin.test.d.ts +2 -0
- package/dist/charts/layout/auto-margin.test.d.ts.map +1 -0
- package/dist/charts/primitives/CartesianChart.d.ts +161 -0
- package/dist/charts/primitives/CartesianChart.d.ts.map +1 -0
- package/dist/charts/primitives/CartesianChart.js +359 -0
- package/dist/charts/primitives/CartesianChart.test.d.ts +2 -0
- package/dist/charts/primitives/CartesianChart.test.d.ts.map +1 -0
- package/dist/charts/primitives/ChartFrame.d.ts +16 -5
- package/dist/charts/primitives/ChartFrame.d.ts.map +1 -1
- package/dist/charts/primitives/ChartFrame.js +17 -2
- package/dist/charts/primitives/ChartLegend.d.ts +15 -1
- package/dist/charts/primitives/ChartLegend.d.ts.map +1 -1
- package/dist/charts/primitives/ChartLegend.js +25 -3
- package/dist/charts/primitives/ChartLegend.test.d.ts +2 -0
- package/dist/charts/primitives/ChartLegend.test.d.ts.map +1 -0
- package/dist/charts/primitives/ChartTooltip.d.ts +16 -16
- package/dist/charts/primitives/ChartTooltip.d.ts.map +1 -1
- package/dist/charts/primitives/ChartTooltip.js +45 -19
- package/dist/charts/primitives/HoverOverlay.d.ts +19 -4
- package/dist/charts/primitives/HoverOverlay.d.ts.map +1 -1
- package/dist/charts/primitives/HoverOverlay.js +22 -1
- package/dist/charts/primitives/XZoneRects.d.ts +54 -0
- package/dist/charts/primitives/XZoneRects.d.ts.map +1 -0
- package/dist/charts/primitives/XZoneRects.js +65 -0
- package/dist/charts/primitives/XZoneRects.test.d.ts +2 -0
- package/dist/charts/primitives/XZoneRects.test.d.ts.map +1 -0
- package/dist/charts/series.d.ts +28 -3
- package/dist/charts/series.d.ts.map +1 -1
- package/dist/charts/series.js +5 -2
- package/dist/charts/series.test.d.ts +2 -0
- package/dist/charts/series.test.d.ts.map +1 -0
- package/dist/charts/utils/measure-text.d.ts +25 -0
- package/dist/charts/utils/measure-text.d.ts.map +1 -0
- package/dist/charts/utils/measure-text.js +64 -0
- package/dist/charts/utils/measure-text.test.d.ts +2 -0
- package/dist/charts/utils/measure-text.test.d.ts.map +1 -0
- package/dist/content/fence-block.d.ts.map +1 -1
- package/dist/surfaces.d.ts +1 -1
- package/dist/surfaces.d.ts.map +1 -1
- package/dist/surfaces.js +1 -1
- package/dist/tokens/chart-margin.test.d.ts +2 -0
- package/dist/tokens/chart-margin.test.d.ts.map +1 -0
- package/dist/tokens/index.d.ts +29 -0
- package/dist/tokens/index.d.ts.map +1 -1
- package/dist/tokens/index.js +10 -0
- package/dist/tokens/palette.d.ts +4 -0
- package/dist/tokens/palette.d.ts.map +1 -1
- package/dist/tokens/palette.js +3 -0
- package/llms.txt +3 -3
- package/package.json +1 -1
- package/src/charts/cursor/resolve.test.ts +61 -0
- package/src/charts/cursor/resolve.ts +102 -0
- package/src/charts/cursor/scope.tsx +32 -0
- package/src/charts/cursor/store.test.ts +46 -0
- package/src/charts/cursor/store.ts +53 -0
- package/src/charts/hooks/useChartCursor.ts +163 -0
- package/src/charts/hooks/useChartSize.ts +8 -4
- package/src/charts/index.ts +35 -14
- package/src/charts/kinds/Bars.test.tsx +98 -0
- package/src/charts/kinds/Bars.tsx +350 -467
- package/src/charts/kinds/Donut.tsx +31 -23
- package/src/charts/kinds/DualPanel.test.tsx +131 -0
- package/src/charts/kinds/DualPanel.tsx +301 -135
- package/src/charts/kinds/Heatmap.tsx +72 -47
- package/src/charts/kinds/MultiLine.test.tsx +37 -0
- package/src/charts/kinds/MultiLine.tsx +171 -290
- package/src/charts/kinds/StackedArea.tsx +116 -175
- package/src/charts/kinds/ZonedLine.test.tsx +37 -0
- package/src/charts/kinds/ZonedLine.tsx +149 -255
- package/src/charts/layout/auto-margin.test.ts +42 -0
- package/src/charts/layout/auto-margin.ts +110 -0
- package/src/charts/primitives/CartesianChart.test.tsx +160 -0
- package/src/charts/primitives/CartesianChart.tsx +598 -0
- package/src/charts/primitives/ChartFrame.test.tsx +33 -0
- package/src/charts/primitives/ChartFrame.tsx +40 -4
- package/src/charts/primitives/ChartLegend.test.tsx +78 -0
- package/src/charts/primitives/ChartLegend.tsx +52 -2
- package/src/charts/primitives/ChartTooltip.tsx +67 -37
- package/src/charts/primitives/HoverOverlay.tsx +39 -3
- package/src/charts/primitives/XZoneRects.test.tsx +123 -0
- package/src/charts/primitives/XZoneRects.tsx +132 -0
- package/src/charts/series.test.ts +112 -0
- package/src/charts/series.ts +33 -5
- package/src/charts/utils/measure-text.test.ts +36 -0
- package/src/charts/utils/measure-text.ts +109 -0
- package/src/content/fence-block.tsx +2 -3
- package/src/surfaces.ts +2 -1
- package/src/theme/spacing.test.ts +2 -0
- package/src/tokens/chart-margin.test.ts +39 -0
- package/src/tokens/density.test.ts +1 -0
- package/src/tokens/index.ts +36 -0
- package/src/tokens/palette.ts +3 -0
- package/dist/charts/hooks/useChartTooltip.d.ts +0 -13
- package/dist/charts/hooks/useChartTooltip.d.ts.map +0 -1
- package/dist/charts/hooks/useChartTooltip.js +0 -26
- package/dist/charts/hooks/useHoverSync.d.ts +0 -46
- package/dist/charts/hooks/useHoverSync.d.ts.map +0 -1
- package/dist/charts/hooks/useHoverSync.js +0 -75
- package/dist/charts/hover-context.d.ts +0 -21
- package/dist/charts/hover-context.d.ts.map +0 -1
- package/dist/charts/hover-context.js +0 -12
- package/dist/charts/hover-sync.d.ts +0 -14
- package/dist/charts/hover-sync.d.ts.map +0 -1
- package/dist/charts/hover-sync.js +0 -17
- package/dist/charts/primitives/ResponsiveChart.d.ts +0 -45
- package/dist/charts/primitives/ResponsiveChart.d.ts.map +0 -1
- package/dist/charts/primitives/ResponsiveChart.js +0 -24
- package/src/charts/hooks/useChartTooltip.ts +0 -31
- package/src/charts/hooks/useHoverSync.ts +0 -152
- package/src/charts/hover-context.ts +0 -28
- package/src/charts/hover-sync.tsx +0 -37
- package/src/charts/primitives/ResponsiveChart.tsx +0 -64
package/src/charts/index.ts
CHANGED
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* `./charts` — Mantine-free visx chart system (ZERO `@mantine/*` imports, lint-enforced).
|
|
3
3
|
*
|
|
4
4
|
* Re-exports the framework token layer (so chart consumers have one import surface), the chart
|
|
5
|
-
* theme context, the shared
|
|
6
|
-
*
|
|
5
|
+
* theme context, the shared cursor (module-level store — charts sync with no provider; see
|
|
6
|
+
* `docs/CHARTS-SPEC.md` §3), the primitives, kinds, sparklines, hooks, utils, and a curated set of
|
|
7
|
+
* raw @visx primitives for bespoke charts.
|
|
7
8
|
*
|
|
8
9
|
* The framework ships ONLY generic primitives + framework palette data — no domain series tree
|
|
9
10
|
* (apps rebuild that app-side with `seriesTokens` / `groupTokens` against their own series maps).
|
|
@@ -20,15 +21,22 @@ export {
|
|
|
20
21
|
seriesTokens,
|
|
21
22
|
defineSeries,
|
|
22
23
|
groupTokens,
|
|
24
|
+
chartMargin,
|
|
25
|
+
type ChartMargin,
|
|
23
26
|
} from '../tokens'
|
|
24
27
|
|
|
25
28
|
// ── Design seam type (type-only; erased at runtime — no @mantine value) ─
|
|
26
29
|
export type { SeriesKey } from '../register'
|
|
27
30
|
|
|
28
|
-
// ── Chart theme +
|
|
31
|
+
// ── Chart theme + shared cursor ──────────────────────────────────────────
|
|
29
32
|
export { VxThemeProvider, useVxTheme, type VxTheme } from './theme'
|
|
30
|
-
export {
|
|
31
|
-
export {
|
|
33
|
+
export { ChartCursorScope, type ChartCursorScopeProps, useCursorState } from './cursor/scope'
|
|
34
|
+
export {
|
|
35
|
+
createCursorStore,
|
|
36
|
+
globalCursorStore,
|
|
37
|
+
type CursorState,
|
|
38
|
+
type CursorStore,
|
|
39
|
+
} from './cursor/store'
|
|
32
40
|
|
|
33
41
|
// ── Series descriptor (the legend/tooltip single source of truth) ────────
|
|
34
42
|
export {
|
|
@@ -44,8 +52,22 @@ export {
|
|
|
44
52
|
} from './series'
|
|
45
53
|
|
|
46
54
|
// ── Primitives ───────────────────────────────────────────────────────────
|
|
47
|
-
export {
|
|
48
|
-
|
|
55
|
+
export {
|
|
56
|
+
CartesianChart,
|
|
57
|
+
resolveAxisDomain,
|
|
58
|
+
type CartesianChartProps,
|
|
59
|
+
type CartesianTooltipConfig,
|
|
60
|
+
type CartesianTooltipRowContext,
|
|
61
|
+
type AxisConfig,
|
|
62
|
+
type PlotContext,
|
|
63
|
+
} from './primitives/CartesianChart'
|
|
64
|
+
export {
|
|
65
|
+
ChartFrame,
|
|
66
|
+
type ChartFrameProps,
|
|
67
|
+
type ChartFrameLegend,
|
|
68
|
+
type PlotRect,
|
|
69
|
+
resolveLegend,
|
|
70
|
+
} from './primitives/ChartFrame'
|
|
49
71
|
export {
|
|
50
72
|
ChartCenter,
|
|
51
73
|
type ChartCenterProps,
|
|
@@ -56,25 +78,26 @@ export { Crosshair, SeriesDot } from './primitives/Crosshair'
|
|
|
56
78
|
export { ChartCard } from './primitives/ChartCard'
|
|
57
79
|
export { ChartLegend, type LegendEntry } from './primitives/ChartLegend'
|
|
58
80
|
export {
|
|
59
|
-
|
|
81
|
+
ChartTooltipFloat,
|
|
60
82
|
TooltipHeader,
|
|
61
83
|
TooltipRow,
|
|
62
84
|
TooltipBody,
|
|
63
|
-
useTooltipStyles,
|
|
64
85
|
} from './primitives/ChartTooltip'
|
|
65
86
|
export { AxisBottomDate, AxisLeftNumeric, AxisRightNumeric } from './primitives/Axes'
|
|
66
87
|
export { HoverOverlay } from './primitives/HoverOverlay'
|
|
67
88
|
export { ZoneRects, type ZoneSpec } from './primitives/ZoneRects'
|
|
89
|
+
export { XZoneRects, type XZoneSpec } from './primitives/XZoneRects'
|
|
68
90
|
export { AreaGradient, areaFillUrl } from './primitives/AreaGradient'
|
|
69
91
|
|
|
70
92
|
// ── Hooks ────────────────────────────────────────────────────────────────
|
|
71
|
-
export {
|
|
72
|
-
export { useHoverSync } from './hooks/useHoverSync'
|
|
93
|
+
export { useChartCursor, type ChartCursor, type CursorAnchor } from './hooks/useChartCursor'
|
|
73
94
|
export { useChartSize, type UseChartSizeResult, type ChartSize } from './hooks/useChartSize'
|
|
74
95
|
|
|
75
96
|
// ── Utils ────────────────────────────────────────────────────────────────
|
|
76
97
|
export { fmtAxisDate, fmtTooltipDate } from './utils/format'
|
|
77
|
-
export { smartTicks } from './utils/ticks'
|
|
98
|
+
export { smartTicks, smartTicksEvery } from './utils/ticks'
|
|
99
|
+
export { autoMargin, probeAxisLabels, type AutoMarginInput } from './layout/auto-margin'
|
|
100
|
+
export { measureText, maxTextWidth } from './utils/measure-text'
|
|
78
101
|
|
|
79
102
|
// ── Kind components (owned by a sibling agent under ./kinds) ──────────────
|
|
80
103
|
export {
|
|
@@ -83,7 +106,6 @@ export {
|
|
|
83
106
|
type ZonedLineZone,
|
|
84
107
|
type ZonedLineThreshold,
|
|
85
108
|
type ZonedLineRefLine,
|
|
86
|
-
type ZonedLineTooltipLabel,
|
|
87
109
|
} from './kinds/ZonedLine'
|
|
88
110
|
|
|
89
111
|
export {
|
|
@@ -93,7 +115,6 @@ export {
|
|
|
93
115
|
type BarsLine,
|
|
94
116
|
type BarsZone,
|
|
95
117
|
type BarsRefLine,
|
|
96
|
-
type BarsAxisConfig,
|
|
97
118
|
} from './kinds/Bars'
|
|
98
119
|
|
|
99
120
|
export { StackedArea, type StackedAreaProps } from './kinds/StackedArea'
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `Bars` — the `tooltip: false` seam threaded from `SeriesStyle.tooltip` through `BarsBar`/
|
|
3
|
+
* `BarsLine` (replacing the removed per-kind `hideBarTooltipRows`), plus the `BarsBar<T>`
|
|
4
|
+
* generic-default assignability guarantee. Real DOM harness (`tests/setup/dom.ts`): the cursor
|
|
5
|
+
* only advances via `useChartCursor`'s keyboard path (`ArrowRight` on the overlay's `role="slider"`),
|
|
6
|
+
* which is deterministic regardless of happy-dom's zeroed layout geometry — unlike the pointer path,
|
|
7
|
+
* which needs a real `getBoundingClientRect`.
|
|
8
|
+
*/
|
|
9
|
+
import { fireEvent, render, screen, waitFor } from '@testing-library/react'
|
|
10
|
+
import { describe, expect, test } from 'bun:test'
|
|
11
|
+
import { Bars } from './Bars'
|
|
12
|
+
import type { BarsBar } from './Bars'
|
|
13
|
+
|
|
14
|
+
type Row = { date: string; shown: number; quiet: number; trend: number }
|
|
15
|
+
|
|
16
|
+
const rows: Row[] = [{ date: '2026-08-01', shown: 10, quiet: 5, trend: 7 }]
|
|
17
|
+
|
|
18
|
+
describe('Bars — tooltip: false drops only that row', () => {
|
|
19
|
+
test('a quiet bar is drawn and legended but produces no tooltip row', async () => {
|
|
20
|
+
render(
|
|
21
|
+
<Bars<Row>
|
|
22
|
+
data={rows}
|
|
23
|
+
chartId="bars-tooltip"
|
|
24
|
+
getX={(d) => d.date}
|
|
25
|
+
getValue={(d, key) => d[key as 'shown' | 'quiet']}
|
|
26
|
+
positiveBars={[
|
|
27
|
+
{ key: 'shown', label: 'Shown', color: '#111' },
|
|
28
|
+
{ key: 'quiet', label: 'Quiet', color: '#222', tooltip: false },
|
|
29
|
+
]}
|
|
30
|
+
/>,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
// Both series are legended regardless of the tooltip flag.
|
|
34
|
+
expect(screen.getByText('Shown')).toBeTruthy()
|
|
35
|
+
expect(screen.getByText('Quiet')).toBeTruthy()
|
|
36
|
+
|
|
37
|
+
const slider = screen.getByRole('slider')
|
|
38
|
+
fireEvent.keyDown(slider, { key: 'ArrowRight' })
|
|
39
|
+
|
|
40
|
+
// The tooltip row duplicates 'Shown' (legend + row); 'Quiet' stays legend-only. The tooltip
|
|
41
|
+
// anchor is set through a `requestAnimationFrame` coalesce, so wait for the SECOND 'Shown' to
|
|
42
|
+
// land rather than resolving on the legend's already-present first one.
|
|
43
|
+
await waitFor(() => expect(screen.getAllByText('Shown')).toHaveLength(2))
|
|
44
|
+
expect(screen.getAllByText('Quiet')).toHaveLength(1)
|
|
45
|
+
})
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
describe('Bars — lines[].tooltip: false drops only that row', () => {
|
|
49
|
+
test('a quiet line is drawn and legended but produces no tooltip row', async () => {
|
|
50
|
+
render(
|
|
51
|
+
<Bars<Row>
|
|
52
|
+
data={rows}
|
|
53
|
+
chartId="bars-line-tooltip"
|
|
54
|
+
getX={(d) => d.date}
|
|
55
|
+
getValue={(d, key) => d[key as 'shown' | 'quiet' | 'trend']}
|
|
56
|
+
positiveBars={[{ key: 'shown', label: 'Shown', color: '#111' }]}
|
|
57
|
+
lines={[{ key: 'trend', label: 'Trend', color: '#333', tooltip: false }]}
|
|
58
|
+
/>,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
// Both series are legended regardless of the tooltip flag.
|
|
62
|
+
expect(screen.getByText('Shown')).toBeTruthy()
|
|
63
|
+
expect(screen.getByText('Trend')).toBeTruthy()
|
|
64
|
+
|
|
65
|
+
const slider = screen.getByRole('slider')
|
|
66
|
+
fireEvent.keyDown(slider, { key: 'ArrowRight' })
|
|
67
|
+
|
|
68
|
+
// The tooltip row duplicates 'Shown' (legend + row); 'Trend' stays legend-only.
|
|
69
|
+
await waitFor(() => expect(screen.getAllByText('Shown')).toHaveLength(2))
|
|
70
|
+
expect(screen.getAllByText('Trend')).toHaveLength(1)
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
describe('Bars — lines[].strokeOpacity dims the plotted stroke', () => {
|
|
75
|
+
test('the line path carries the configured stroke-opacity attribute', () => {
|
|
76
|
+
const { container } = render(
|
|
77
|
+
<Bars<Row>
|
|
78
|
+
data={rows}
|
|
79
|
+
chartId="bars-line-opacity"
|
|
80
|
+
getX={(d) => d.date}
|
|
81
|
+
getValue={(d, key) => d[key as 'shown' | 'quiet' | 'trend']}
|
|
82
|
+
positiveBars={[{ key: 'shown', label: 'Shown', color: '#111' }]}
|
|
83
|
+
lines={[{ key: 'trend', label: 'Trend', color: '#333', strokeOpacity: 0.4 }]}
|
|
84
|
+
/>,
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
const path = container.querySelector('path[stroke="#333"]')
|
|
88
|
+
expect(path?.getAttribute('stroke-opacity')).toBe('0.4')
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
describe('BarsBar<T> — the `= unknown` default stays assignable', () => {
|
|
93
|
+
test('an existing untyped BarsBar[] array is assignable to BarsBar<Row>[] contravariantly', () => {
|
|
94
|
+
const bars: BarsBar[] = [{ key: 'shown', label: 'Shown', color: '#111' }]
|
|
95
|
+
const typed: BarsBar<Row>[] = bars
|
|
96
|
+
expect(typed).toBe(bars)
|
|
97
|
+
})
|
|
98
|
+
})
|