@cosmicdrift/kumiko-renderer 0.389.0 → 0.390.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/projection-detail-body.js +59 -394
- package/dist/app/projection-detail-header-model.d.ts +11 -0
- package/dist/app/projection-detail-header-model.js +33 -0
- package/dist/app/projection-detail-header-region.d.ts +28 -0
- package/dist/app/projection-detail-header-region.js +81 -0
- package/dist/app/projection-detail-header.d.ts +29 -0
- package/dist/app/projection-detail-header.js +84 -0
- package/dist/app/projection-detail-navigation.d.ts +13 -0
- package/dist/app/projection-detail-navigation.js +91 -0
- package/dist/app/projection-detail-states.d.ts +22 -0
- package/dist/app/projection-detail-states.js +26 -0
- package/dist/app/projection-drawer-host.d.ts +12 -0
- package/dist/app/projection-drawer-host.js +10 -0
- package/dist/app/projection-list-body.d.ts +1 -1
- package/dist/app/projection-list-body.js +69 -189
- package/dist/app/projection-list-render-props.d.ts +56 -0
- package/dist/app/projection-list-render-props.js +47 -0
- package/dist/app/projection-list-render.d.ts +26 -0
- package/dist/app/projection-list-render.js +20 -0
- package/dist/app/projection-list-row-click.d.ts +12 -0
- package/dist/app/projection-list-row-click.js +14 -0
- package/dist/app/use-projection-detail-actions.d.ts +35 -0
- package/dist/app/use-projection-detail-actions.js +156 -0
- package/dist/app/use-projection-detail-data.d.ts +19 -0
- package/dist/app/use-projection-detail-data.js +47 -0
- package/dist/app/use-projection-detail-layout.d.ts +17 -0
- package/dist/app/use-projection-detail-layout.js +23 -0
- package/dist/app/use-projection-list-actions.d.ts +31 -0
- package/dist/app/use-projection-list-actions.js +73 -0
- package/dist/app/use-projection-list-data.d.ts +20 -0
- package/dist/app/use-projection-list-data.js +26 -0
- package/dist/app/use-projection-list-definitions.d.ts +11 -0
- package/dist/app/use-projection-list-definitions.js +20 -0
- package/dist/app/use-projection-list-query.d.ts +28 -0
- package/dist/app/use-projection-list-query.js +83 -0
- package/dist/components/render-edit-banners.d.ts +12 -0
- package/dist/components/render-edit-banners.js +90 -0
- package/dist/components/render-edit-draft-save-helpers.d.ts +19 -0
- package/dist/components/render-edit-draft-save-helpers.js +42 -0
- package/dist/components/render-edit-field-grid.d.ts +4 -0
- package/dist/components/render-edit-field-grid.js +40 -0
- package/dist/components/render-edit-fields-section.d.ts +4 -0
- package/dist/components/render-edit-fields-section.js +76 -0
- package/dist/components/render-edit-form-actions.d.ts +4 -0
- package/dist/components/render-edit-form-actions.js +66 -0
- package/dist/components/render-edit-header-actions.d.ts +11 -0
- package/dist/components/render-edit-header-actions.js +120 -0
- package/dist/components/render-edit-layout-flags.d.ts +29 -0
- package/dist/components/render-edit-layout-flags.js +106 -0
- package/dist/components/render-edit-layout.d.ts +4 -0
- package/dist/components/render-edit-layout.js +79 -0
- package/dist/components/render-edit-render-model.d.ts +63 -0
- package/dist/components/render-edit-render-model.js +14 -0
- package/dist/components/render-edit-section-kinds.d.ts +4 -0
- package/dist/components/render-edit-section-kinds.js +115 -0
- package/dist/components/render-edit-submit-result.d.ts +8 -0
- package/dist/components/render-edit-submit-result.js +77 -0
- package/dist/components/render-edit-submit.d.ts +7 -0
- package/dist/components/render-edit-submit.js +113 -0
- package/dist/components/render-edit-titles.d.ts +17 -0
- package/dist/components/render-edit-titles.js +99 -0
- package/dist/components/render-edit-wizard-navigation.d.ts +11 -0
- package/dist/components/render-edit-wizard-navigation.js +115 -0
- package/dist/components/render-edit.d.ts +2 -2
- package/dist/components/render-edit.js +63 -1128
- package/dist/components/render-field-choice-inputs.d.ts +7 -0
- package/dist/components/render-field-choice-inputs.js +52 -0
- package/dist/components/render-field-control.d.ts +12 -0
- package/dist/components/render-field-control.js +93 -0
- package/dist/components/render-field-display-text.d.ts +4 -0
- package/dist/components/render-field-display-text.js +48 -0
- package/dist/components/render-field-fill-action.d.ts +13 -0
- package/dist/components/render-field-fill-action.js +63 -0
- package/dist/components/render-field-input-shared.d.ts +39 -0
- package/dist/components/render-field-input-shared.js +20 -0
- package/dist/components/render-field-input.d.ts +3 -0
- package/dist/components/render-field-input.js +34 -0
- package/dist/components/render-field-meta.d.ts +6 -0
- package/dist/components/render-field-meta.js +51 -0
- package/dist/components/render-field-read-only.d.ts +11 -0
- package/dist/components/render-field-read-only.js +75 -0
- package/dist/components/render-field-reference.d.ts +15 -0
- package/dist/components/render-field-reference.js +103 -0
- package/dist/components/render-field-scalar-inputs.d.ts +10 -0
- package/dist/components/render-field-scalar-inputs.js +35 -0
- package/dist/components/render-field-text-inputs.d.ts +3 -0
- package/dist/components/render-field-text-inputs.js +34 -0
- package/dist/components/render-field-values.d.ts +18 -0
- package/dist/components/render-field-values.js +111 -0
- package/dist/components/render-field-write-only.d.ts +9 -0
- package/dist/components/render-field-write-only.js +29 -0
- package/dist/components/render-field.d.ts +2 -2
- package/dist/components/render-field.js +39 -670
- package/dist/components/render-list-header-slot.d.ts +5 -0
- package/dist/components/render-list-header-slot.js +30 -0
- package/dist/components/render-list-reference-bridge.d.ts +3 -0
- package/dist/components/render-list-reference-bridge.js +25 -0
- package/dist/components/render-list-reference-columns.d.ts +25 -0
- package/dist/components/render-list-reference-columns.js +137 -0
- package/dist/components/render-list-table-props.d.ts +52 -0
- package/dist/components/render-list-table-props.js +53 -0
- package/dist/components/render-list-toolbar-model.d.ts +42 -0
- package/dist/components/render-list-toolbar-model.js +82 -0
- package/dist/components/render-list-toolbar.d.ts +38 -0
- package/dist/components/render-list-toolbar.js +40 -0
- package/dist/components/render-list-types.d.ts +113 -0
- package/dist/components/render-list-types.js +1 -0
- package/dist/components/render-list.d.ts +4 -113
- package/dist/components/render-list.js +91 -307
- package/dist/components/use-reference-input-state.d.ts +20 -0
- package/dist/components/use-reference-input-state.js +85 -0
- package/dist/components/use-render-edit-controlled-sync.d.ts +3 -0
- package/dist/components/use-render-edit-controlled-sync.js +26 -0
- package/dist/components/use-render-edit-draft-save.d.ts +29 -0
- package/dist/components/use-render-edit-draft-save.js +141 -0
- package/dist/components/use-render-edit-form.d.ts +10 -0
- package/dist/components/use-render-edit-form.js +101 -0
- package/dist/components/use-render-edit-sections.d.ts +15 -0
- package/dist/components/use-render-edit-sections.js +100 -0
- package/dist/components/use-render-edit-state.d.ts +65 -0
- package/dist/components/use-render-edit-state.js +158 -0
- package/dist/components/use-render-edit-steps.d.ts +13 -0
- package/dist/components/use-render-edit-steps.js +52 -0
- package/dist/components/use-render-list-columns.d.ts +18 -0
- package/dist/components/use-render-list-columns.js +41 -0
- package/dist/components/use-render-list-search.d.ts +4 -0
- package/dist/components/use-render-list-search.js +50 -0
- package/dist/primitives.d.ts +1 -1
- package/dist/web/app/dashboard-body.d.ts +1 -1
- package/dist/web/app/dashboard-body.js +42 -246
- package/dist/web/app/dashboard-header.d.ts +34 -0
- package/dist/web/app/dashboard-header.js +58 -0
- package/dist/web/app/dashboard-screen-controls.d.ts +11 -0
- package/dist/web/app/dashboard-screen-controls.js +42 -0
- package/dist/web/app/dashboard-screen-tiles.d.ts +8 -0
- package/dist/web/app/dashboard-screen-tiles.js +42 -0
- package/dist/web/app/dashboard-visibility.d.ts +42 -0
- package/dist/web/app/dashboard-visibility.js +102 -0
- package/dist/web/app/use-dashboard-screen-state.d.ts +35 -0
- package/dist/web/app/use-dashboard-screen-state.js +69 -0
- package/dist/web/primitives/data-table/actions-column.js +6 -5
- package/dist/web/primitives/data-table/card-body.d.ts +6 -0
- package/dist/web/primitives/data-table/card-body.js +32 -0
- package/dist/web/primitives/data-table/card-row.d.ts +5 -0
- package/dist/web/primitives/data-table/card-row.js +31 -0
- package/dist/web/primitives/data-table/card-view.d.ts +3 -0
- package/dist/web/primitives/data-table/card-view.js +13 -0
- package/dist/web/primitives/data-table/column-resize-handle.js +3 -1
- package/dist/web/primitives/data-table/data-cell-props.d.ts +7 -0
- package/dist/web/primitives/data-table/data-cell-props.js +8 -0
- package/dist/web/primitives/data-table/row-render-parts.d.ts +8 -0
- package/dist/web/primitives/data-table/row-render-parts.js +35 -0
- package/dist/web/primitives/data-table/table-body.d.ts +3 -0
- package/dist/web/primitives/data-table/table-body.js +32 -0
- package/dist/web/primitives/data-table/table-content.d.ts +3 -0
- package/dist/web/primitives/data-table/table-content.js +41 -0
- package/dist/web/primitives/data-table/table-data-cell.d.ts +4 -0
- package/dist/web/primitives/data-table/table-data-cell.js +25 -0
- package/dist/web/primitives/data-table/table-header-row.d.ts +3 -0
- package/dist/web/primitives/data-table/table-header-row.js +21 -0
- package/dist/web/primitives/data-table/table-view.d.ts +3 -0
- package/dist/web/primitives/data-table/table-view.js +25 -0
- package/dist/web/primitives/data-table/toolbar.d.ts +6 -0
- package/dist/web/primitives/data-table/toolbar.js +53 -0
- package/dist/web/primitives/data-table/use-data-table-state.d.ts +44 -0
- package/dist/web/primitives/data-table/use-data-table-state.js +123 -0
- package/dist/web/primitives/data-table.d.ts +3 -4
- package/dist/web/primitives/data-table.js +32 -252
- package/dist/web/primitives/file-value-list.js +1 -1
- package/dist/web/primitives/form-footer-parts.d.ts +25 -0
- package/dist/web/primitives/form-footer-parts.js +55 -0
- package/dist/web/primitives/form.js +33 -43
- package/dist/web/primitives/input-choice-kinds.d.ts +4 -0
- package/dist/web/primitives/input-choice-kinds.js +67 -0
- package/dist/web/primitives/input-file-kinds.d.ts +3 -0
- package/dist/web/primitives/input-file-kinds.js +6 -0
- package/dist/web/primitives/input-number-kinds.d.ts +4 -0
- package/dist/web/primitives/input-number-kinds.js +16 -0
- package/dist/web/primitives/input-optional-props.d.ts +20 -0
- package/dist/web/primitives/input-optional-props.js +24 -0
- package/dist/web/primitives/input-select-presentation.d.ts +13 -0
- package/dist/web/primitives/input-select-presentation.js +19 -0
- package/dist/web/primitives/input-shared.d.ts +22 -0
- package/dist/web/primitives/input-shared.js +10 -0
- package/dist/web/primitives/input-temporal-kinds.d.ts +5 -0
- package/dist/web/primitives/input-temporal-kinds.js +10 -0
- package/dist/web/primitives/input-text-kinds.d.ts +7 -0
- package/dist/web/primitives/input-text-kinds.js +33 -0
- package/dist/web/primitives/input.js +35 -161
- package/dist/web/widgets/chart-data.d.ts +35 -0
- package/dist/web/widgets/chart-data.js +37 -0
- package/dist/web/widgets/chart-markers.d.ts +26 -0
- package/dist/web/widgets/chart-markers.js +64 -0
- package/dist/web/widgets/chart-parts.d.ts +20 -0
- package/dist/web/widgets/chart-parts.js +15 -0
- package/dist/web/widgets/charts.d.ts +5 -78
- package/dist/web/widgets/charts.js +7 -485
- package/dist/web/widgets/drawer-panel-class.d.ts +3 -0
- package/dist/web/widgets/drawer-panel-class.js +40 -0
- package/dist/web/widgets/drawer-resize-controls.d.ts +11 -0
- package/dist/web/widgets/drawer-resize-controls.js +12 -0
- package/dist/web/widgets/drawer-sizing.d.ts +19 -0
- package/dist/web/widgets/drawer-sizing.js +60 -0
- package/dist/web/widgets/drawer.js +17 -116
- package/dist/web/widgets/stacked-area-brush.d.ts +13 -0
- package/dist/web/widgets/stacked-area-brush.js +91 -0
- package/dist/web/widgets/stacked-area-chart.d.ts +6 -0
- package/dist/web/widgets/stacked-area-chart.js +45 -0
- package/dist/web/widgets/stacked-area-chrome.d.ts +7 -0
- package/dist/web/widgets/stacked-area-chrome.js +77 -0
- package/dist/web/widgets/stacked-area-geometry.d.ts +54 -0
- package/dist/web/widgets/stacked-area-geometry.js +83 -0
- package/dist/web/widgets/stacked-area-model.d.ts +11 -0
- package/dist/web/widgets/stacked-area-model.js +1 -0
- package/dist/web/widgets/stacked-area-plot.d.ts +3 -0
- package/dist/web/widgets/stacked-area-plot.js +51 -0
- package/dist/web/widgets/stacked-area-props.d.ts +83 -0
- package/dist/web/widgets/stacked-area-props.js +16 -0
- package/dist/web/widgets/stacked-area-svg.d.ts +3 -0
- package/dist/web/widgets/stacked-area-svg.js +38 -0
- package/dist/web/widgets/stacked-area-tooltip.d.ts +3 -0
- package/dist/web/widgets/stacked-area-tooltip.js +86 -0
- package/dist/web/widgets/use-drawer-resize.d.ts +22 -0
- package/dist/web/widgets/use-drawer-resize.js +52 -0
- package/dist/web/widgets/use-stacked-area-tooltip.d.ts +9 -0
- package/dist/web/widgets/use-stacked-area-tooltip.js +34 -0
- package/dist/web/widgets/use-stacked-area-window.d.ts +20 -0
- package/dist/web/widgets/use-stacked-area-window.js +76 -0
- package/package.json +10 -10
- package/src/changes.json +54 -0
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { Fragment as _Fragment, jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import { useId } from "react";
|
|
3
3
|
import { cn } from "../lib/cn.js";
|
|
4
4
|
import { NARROW_VIEWPORT_BREAKPOINT_PX, useIsViewportBelow, } from "../primitives/use-narrow-viewport.js";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
5
|
+
import { bucketTimes, chartColor, clampDotY, percent, SINGLE_POINT_RADIUS, seriesTotal, valueAt, } from "./chart-data.js";
|
|
6
|
+
import { MarkerLegend, MarkerPins } from "./chart-markers.js";
|
|
7
|
+
import { ChartLegend, evenYTickValues, YTicks } from "./chart-parts.js";
|
|
7
8
|
import { STATUS_TONE_TEXT } from "./status-badge.js";
|
|
8
9
|
// Inline SVG charts without a chart dependency. Colors come exclusively from the
|
|
9
10
|
// --color-status-* / --color-foreground theme tokens; axis labels are supplied
|
|
@@ -43,10 +44,6 @@ export function StatusBarChart({ entries, ariaLabel, startLabel, endLabel, highl
|
|
|
43
44
|
return (_jsxs("g", { children: [!dense && (_jsx("defs", { children: _jsxs("linearGradient", { id: gradId, x1: "0", x2: "0", y1: "0", y2: "1", children: [_jsx("stop", { offset: "0%", stopColor: color, stopOpacity: isLast ? 0.85 : 0.5 }), _jsx("stop", { offset: "100%", stopColor: color, stopOpacity: 0.05 })] }) })), isLast && (_jsx("rect", { x: x - barGap / 2, y: 0, width: barWidth + barGap, height: chartHeight, fill: "var(--color-foreground)", fillOpacity: 0.06 })), _jsx("rect", { x: x, y: barY, width: barWidth, height: barHeight, fill: dense ? color : `url(#${gradId})`, fillOpacity: dense ? (isLast ? 1 : 0.75) : undefined, children: entry.label !== undefined && _jsx("title", { children: entry.label }) }), !dense && (_jsx("rect", { x: x, y: barY - tickHeight, width: barWidth, height: tickHeight, fill: "var(--color-foreground)", fillOpacity: isLast ? 1.0 : 0.7 }))] }, entry.key));
|
|
44
45
|
})] }), (startLabel !== undefined || endLabel !== undefined) && (_jsxs("div", { className: "mt-0.5 flex justify-between text-[11px] text-muted-foreground", children: [_jsx("span", { children: startLabel }), _jsx("span", { children: endLabel })] }))] }));
|
|
45
46
|
}
|
|
46
|
-
const SINGLE_POINT_RADIUS = 3;
|
|
47
|
-
function clampDotY(y, height) {
|
|
48
|
-
return Math.max(SINGLE_POINT_RADIUS, Math.min(height - SINGLE_POINT_RADIUS, y));
|
|
49
|
-
}
|
|
50
47
|
/** Quadratic midpoint technique: smooths the zigzag line without overshoot
|
|
51
48
|
* (~5 lines instead of a Catmull-Rom/Bezier fit). */
|
|
52
49
|
export function smoothPath(pts) {
|
|
@@ -131,24 +128,6 @@ export function TimeseriesChart({ points, windowStartMs, windowEndMs, tone = "ok
|
|
|
131
128
|
yOf(line.value) >= chartHeight / 4 && "-translate-y-full", STATUS_TONE_TEXT[line.tone ?? "muted"]), style: { top: `${(yOf(line.value) / chartHeight) * 100}%` }, children: line.label }, `${line.value}:${line.label}`)))] })) : (svg), xAxis !== undefined ? (_jsx("div", { className: "mt-1 flex justify-between text-[11px] text-muted-foreground", children: evenlySpaced(xAxis.ticks).map((fraction) => (_jsx("span", { "data-x-label": "", children: xAxis.format(windowStartMs + fraction * (windowEndMs - windowStartMs)) }, fraction))) })) : (axisLabels !== undefined && (_jsxs("div", { className: "mt-1 flex justify-between text-[11px] text-muted-foreground", children: [_jsx("span", { children: axisLabels.start }), axisLabels.mid !== undefined && _jsx("span", { children: axisLabels.mid }), _jsx("span", { children: axisLabels.end })] })))] }));
|
|
132
129
|
return (_jsxs("div", { "data-testid": testId, className: STATUS_TONE_TEXT[tone], children: [yAxis === undefined ? (plot) : (_jsxs("div", { className: cn("flex gap-1.5", yAxis.unit !== undefined && "pt-4"), children: [_jsxs("div", { "aria-hidden": "true", className: cn("relative w-12 shrink-0 text-right text-[11px] text-muted-foreground", heightClass), style: heightStyle, children: [yAxis.unit !== undefined && (_jsx("span", { "data-testid": testId === undefined ? undefined : `${testId}-y-unit`, "data-y-unit": "", className: "absolute right-0 bottom-full mb-1 whitespace-nowrap leading-none", children: yAxis.unit })), yTicks.map((tick, index) => (_jsx("span", { "data-y-label": "", className: cn("absolute right-0 whitespace-nowrap leading-none", yTickPosition(index, yTicks.length)), style: { top: `${(yOf(tick) / chartHeight) * 100}%` }, children: (yAxis.format ?? String)(tick) }, tick)))] }), _jsx("div", { className: "min-w-0 flex-1", children: plot })] })), markers !== undefined && markers.length > 0 && (_jsxs("div", { className: "text-foreground", children: [_jsx(MarkerPins, { markers: markers, windowStartMs: windowStartMs, windowEndMs: windowEndMs }), _jsx("div", { className: "mt-2", children: _jsx(MarkerLegend, { markers: markers, formatMarkerTime: formatMarkerTime }) })] }))] }));
|
|
133
130
|
}
|
|
134
|
-
const CHART_TONE_VAR = {
|
|
135
|
-
positive: "var(--color-status-ok)",
|
|
136
|
-
negative: "var(--color-status-bad)",
|
|
137
|
-
active: "var(--color-status-active)",
|
|
138
|
-
neutral: "var(--color-status-neutral)",
|
|
139
|
-
};
|
|
140
|
-
export function chartToneColor(tone) {
|
|
141
|
-
return CHART_TONE_VAR[tone];
|
|
142
|
-
}
|
|
143
|
-
const CHART_FALLBACK_TONES = ["active", "positive", "negative", "neutral"];
|
|
144
|
-
function chartColor(key, index, tones, colors) {
|
|
145
|
-
const color = colors?.[key];
|
|
146
|
-
if (color !== undefined)
|
|
147
|
-
return color;
|
|
148
|
-
const tone = tones?.[key] ?? CHART_FALLBACK_TONES[index % CHART_FALLBACK_TONES.length] ?? "active";
|
|
149
|
-
return CHART_TONE_VAR[tone];
|
|
150
|
-
}
|
|
151
|
-
const Y_TICK_COUNT = 3;
|
|
152
131
|
function niceCeil(max) {
|
|
153
132
|
if (max <= 0)
|
|
154
133
|
return 1;
|
|
@@ -157,107 +136,13 @@ function niceCeil(max) {
|
|
|
157
136
|
const step = normalized <= 1 ? 1 : normalized <= 2 ? 2 : normalized <= 5 ? 5 : 10;
|
|
158
137
|
return step * magnitude;
|
|
159
138
|
}
|
|
160
|
-
const FINE_NICE_STEPS = [1, 1.2, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10];
|
|
161
|
-
// Finer than niceCeil so a 1.1 M peak tops out at 1.2 M instead of 2 M.
|
|
162
|
-
function fineNiceCeil(max) {
|
|
163
|
-
if (max <= 0)
|
|
164
|
-
return 1;
|
|
165
|
-
const magnitude = 10 ** Math.floor(Math.log10(max));
|
|
166
|
-
const normalized = max / magnitude;
|
|
167
|
-
const step = FINE_NICE_STEPS.find((candidate) => normalized <= candidate) ?? 10;
|
|
168
|
-
return step * magnitude;
|
|
169
|
-
}
|
|
170
139
|
function niceYTicks(max, tickCount) {
|
|
171
140
|
const count = clampTickCount(tickCount);
|
|
172
141
|
const step = niceCeil(max / (count - 1));
|
|
173
142
|
// toPrecision strips float noise such as 3 * 0.2 = 0.6000000000000001 from labels and keys.
|
|
174
143
|
return Array.from({ length: count }, (_, i) => Number((i * step).toPrecision(12)));
|
|
175
144
|
}
|
|
176
|
-
function bucketTimes(series) {
|
|
177
|
-
const times = new Set();
|
|
178
|
-
for (const s of series)
|
|
179
|
-
for (const p of s.points)
|
|
180
|
-
times.add(p.atMs);
|
|
181
|
-
return [...times].sort((a, b) => a - b);
|
|
182
|
-
}
|
|
183
|
-
function valueAt(series, atMs) {
|
|
184
|
-
return series.points.find((p) => p.atMs === atMs)?.value ?? 0;
|
|
185
|
-
}
|
|
186
|
-
function linePathSegments(line, xOf, yOf) {
|
|
187
|
-
const commands = [];
|
|
188
|
-
let penDown = false;
|
|
189
|
-
for (const point of [...line.points].sort((a, b) => a.atMs - b.atMs)) {
|
|
190
|
-
if (point.value === null) {
|
|
191
|
-
penDown = false;
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
commands.push(`${penDown ? "L" : "M"} ${xOf(point.atMs).toFixed(1)} ${yOf(point.value).toFixed(1)}`);
|
|
195
|
-
penDown = true;
|
|
196
|
-
}
|
|
197
|
-
return commands.join(" ");
|
|
198
|
-
}
|
|
199
|
-
function seriesTotal(series) {
|
|
200
|
-
return series.points.reduce((sum, p) => sum + (p.value ?? 0), 0);
|
|
201
|
-
}
|
|
202
|
-
function percent(fraction) {
|
|
203
|
-
return `${(fraction * 100).toFixed(2)}%`;
|
|
204
|
-
}
|
|
205
|
-
function ChartLegend({ items, formatValue, }) {
|
|
206
|
-
return (_jsx("ul", { className: "flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground", children: items.map((item) => (_jsxs("li", { "data-testid": `chart-legend-${item.testIdKey ?? item.key}`, className: "flex items-center gap-1.5", children: [item.swatch === undefined || item.swatch === "area" ? (_jsx("span", { "aria-hidden": "true", className: "size-2 rounded-[2px]", style: { backgroundColor: item.color } })) : (_jsx("span", { "aria-hidden": "true", className: "w-3 border-t-2", style: {
|
|
207
|
-
borderColor: item.color,
|
|
208
|
-
borderTopStyle: item.swatch === "dashed-line" ? "dashed" : "solid",
|
|
209
|
-
} })), _jsx("span", { children: item.label }), item.total !== undefined && (_jsx("span", { className: "tabular-nums text-foreground", children: formatValue(item.total) }))] }, item.key))) }));
|
|
210
|
-
}
|
|
211
|
-
function evenYTickValues(max) {
|
|
212
|
-
return Array.from({ length: Y_TICK_COUNT }, (_, i) => (max / (Y_TICK_COUNT - 1)) * i);
|
|
213
|
-
}
|
|
214
|
-
function YTicks({ values, formatValue, className, }) {
|
|
215
|
-
return (_jsx("div", { "aria-hidden": "true", className: cn("flex flex-col-reverse justify-between text-right text-[11px] tabular-nums text-muted-foreground", className), children: values.map((value, i) => (_jsx("span", { children: formatValue(value) }, i))) }));
|
|
216
|
-
}
|
|
217
145
|
const BAR_LABEL_TARGET = 8;
|
|
218
|
-
const STACKED_AREA_PLOT_HEIGHT_CLASS = "h-56 @2xl:h-64";
|
|
219
|
-
// A 16px pin on a ~230px-wide plot (390px viewport) collides when markers sit
|
|
220
|
-
// within ~7% of each other, so close pins are staggered into extra rows.
|
|
221
|
-
const MARKER_PIN_MIN_GAP_FRACTION = 0.07;
|
|
222
|
-
const MARKER_PIN_MAX_ROWS = 3;
|
|
223
|
-
const MARKER_PIN_ROW_HEIGHT_PX = 18;
|
|
224
|
-
const TOOLTIP_WIDTH_REM = 14;
|
|
225
|
-
const TOOLTIP_OFFSET_PX = 8;
|
|
226
|
-
// A swipe over the plot (scrolling the page or the chart) must not open the card.
|
|
227
|
-
const TOOLTIP_TAP_MAX_MOVEMENT_PX = 8;
|
|
228
|
-
export function assignPinRows(fractions) {
|
|
229
|
-
const lastFractionPerRow = [];
|
|
230
|
-
return fractions.map((fraction) => {
|
|
231
|
-
let row = lastFractionPerRow.findIndex((last) => fraction - last >= MARKER_PIN_MIN_GAP_FRACTION);
|
|
232
|
-
if (row === -1) {
|
|
233
|
-
row =
|
|
234
|
-
lastFractionPerRow.length < MARKER_PIN_MAX_ROWS
|
|
235
|
-
? lastFractionPerRow.length
|
|
236
|
-
: MARKER_PIN_MAX_ROWS - 1;
|
|
237
|
-
}
|
|
238
|
-
lastFractionPerRow[row] = fraction;
|
|
239
|
-
return row;
|
|
240
|
-
});
|
|
241
|
-
}
|
|
242
|
-
// Container-query density tiers for the x axis: label budget per tier.
|
|
243
|
-
const AXIS_TIERS = [
|
|
244
|
-
{ maxLabels: 4, visibilityClass: "@xl:hidden" },
|
|
245
|
-
{ maxLabels: 7, visibilityClass: "hidden @xl:block @4xl:hidden" },
|
|
246
|
-
{ maxLabels: 12, visibilityClass: "hidden @4xl:block" },
|
|
247
|
-
];
|
|
248
|
-
const MARKER_LABEL_FLIP_FRACTION = 0.97;
|
|
249
|
-
// Markers sharing a timestamp become one label ("A, B") in the first one's color.
|
|
250
|
-
function groupMarkerLabels(markers) {
|
|
251
|
-
const groups = new Map();
|
|
252
|
-
for (const marker of [...markers].sort((a, b) => a.atMs - b.atMs)) {
|
|
253
|
-
groups.set(marker.atMs, [...(groups.get(marker.atMs) ?? []), marker]);
|
|
254
|
-
}
|
|
255
|
-
return [...groups.entries()].map(([atMs, members]) => ({
|
|
256
|
-
atMs,
|
|
257
|
-
text: members.map((m) => m.label).join(", "),
|
|
258
|
-
color: members[0]?.color ?? "var(--color-foreground)",
|
|
259
|
-
}));
|
|
260
|
-
}
|
|
261
146
|
/** Vertically stacked bars per bucket (x axis = bucket, not time).
|
|
262
147
|
* The last bucket carries `todayLabel` when it contains `windowEndMs` and
|
|
263
148
|
* has day granularity. */
|
|
@@ -317,369 +202,6 @@ export function SegmentBarChart({ rows, tones, colors, ariaLabel, formatValue =
|
|
|
317
202
|
total: segmentTotals.get(key)?.total ?? 0,
|
|
318
203
|
})) })] }));
|
|
319
204
|
}
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
return [...markers]
|
|
324
|
-
.sort((a, b) => a.atMs - b.atMs)
|
|
325
|
-
.map((marker) => {
|
|
326
|
-
const base = `${marker.atMs}-${marker.label}`;
|
|
327
|
-
const occurrence = seen.get(base) ?? 0;
|
|
328
|
-
seen.set(base, occurrence + 1);
|
|
329
|
-
return { ...marker, key: `${base}-${occurrence}` };
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
function MarkerPins({ markers, windowStartMs, windowEndMs, numbered = true, formatMarkerTime, className, }) {
|
|
333
|
-
const span = Math.max(1, windowEndMs - windowStartMs);
|
|
334
|
-
const sorted = sortMarkers(markers);
|
|
335
|
-
const fractions = sorted.map((marker) => Math.max(0, Math.min(1, (marker.atMs - windowStartMs) / span)));
|
|
336
|
-
const rows = assignPinRows(fractions);
|
|
337
|
-
const rowCount = Math.max(1, ...rows.map((row) => row + 1));
|
|
338
|
-
return (_jsx("div", { "aria-hidden": "true", className: cn("relative mt-1", className), style: { height: rowCount * MARKER_PIN_ROW_HEIGHT_PX }, children: sorted.map((marker, i) => (_jsx("span", { "data-testid": "chart-marker-pin", title: numbered
|
|
339
|
-
? undefined
|
|
340
|
-
: formatMarkerTime === undefined
|
|
341
|
-
? marker.label
|
|
342
|
-
: `${marker.label} · ${formatMarkerTime(marker.atMs)}`, className: cn("absolute flex -translate-x-1/2 items-center justify-center rounded-full text-[10px] font-medium text-background", numbered ? "size-4" : "size-2", marker.color === undefined && "bg-foreground"), style: {
|
|
343
|
-
left: percent(fractions[i] ?? 0),
|
|
344
|
-
top: (rows[i] ?? 0) * MARKER_PIN_ROW_HEIGHT_PX + (numbered ? 0 : 4),
|
|
345
|
-
...(marker.color !== undefined && { backgroundColor: marker.color }),
|
|
346
|
-
}, children: numbered ? i + 1 : null }, marker.key))) }));
|
|
347
|
-
}
|
|
348
|
-
function markerLegendItems(markers) {
|
|
349
|
-
const items = new Map();
|
|
350
|
-
for (const marker of markers) {
|
|
351
|
-
const label = marker.legendLabel ?? marker.label;
|
|
352
|
-
const color = marker.color ?? "var(--color-foreground)";
|
|
353
|
-
const key = `marker-${label}-${color}`;
|
|
354
|
-
if (!items.has(key))
|
|
355
|
-
items.set(key, { key, label, color });
|
|
356
|
-
}
|
|
357
|
-
return [...items.values()];
|
|
358
|
-
}
|
|
359
|
-
function MarkerLegend({ markers, formatMarkerTime, className, }) {
|
|
360
|
-
return (_jsx("ol", { className: cn("flex flex-col gap-1 text-xs text-muted-foreground", className), children: sortMarkers(markers).map((marker, i) => (_jsxs("li", { "data-testid": "chart-marker-item", className: "flex gap-2", children: [_jsx("span", { className: "w-4 shrink-0 text-center tabular-nums text-foreground", style: marker.color !== undefined ? { color: marker.color } : undefined, children: i + 1 }), _jsx("span", { children: marker.label }), formatMarkerTime !== undefined && (_jsx("span", { className: "ml-auto tabular-nums", children: formatMarkerTime(marker.atMs) }))] }, marker.key))) }));
|
|
361
|
-
}
|
|
362
|
-
const SCROLLABLE_BUCKET_WIDTH_PX = 48;
|
|
363
|
-
function bucketLabelTransform(index, count) {
|
|
364
|
-
if (index === 0)
|
|
365
|
-
return "";
|
|
366
|
-
return index === count - 1 ? "-translate-x-full" : "-translate-x-1/2";
|
|
367
|
-
}
|
|
368
|
-
const DEFAULT_BRUSH_LABELS = { start: "Window start", end: "Window end" };
|
|
369
|
-
const BRUSH_OVERVIEW_WIDTH = 100;
|
|
370
|
-
const BRUSH_OVERVIEW_HEIGHT = 24;
|
|
371
|
-
function brushKeyTarget(key, index, min, max) {
|
|
372
|
-
if (key === "ArrowLeft")
|
|
373
|
-
return index - 1;
|
|
374
|
-
if (key === "ArrowRight")
|
|
375
|
-
return index + 1;
|
|
376
|
-
if (key === "Home")
|
|
377
|
-
return min;
|
|
378
|
-
return key === "End" ? max : undefined;
|
|
379
|
-
}
|
|
380
|
-
function StackedAreaBrush({ times, totals, window, labels, onChange, testId, }) {
|
|
381
|
-
const trackRef = useRef(null);
|
|
382
|
-
const dragRef = useRef(null);
|
|
383
|
-
const firstMs = times[0] ?? 0;
|
|
384
|
-
const lastMs = times[times.length - 1] ?? 1;
|
|
385
|
-
const span = Math.max(1, lastMs - firstMs);
|
|
386
|
-
const fractionOf = (atMs) => Math.max(0, Math.min(1, (atMs - firstMs) / span));
|
|
387
|
-
const startIndex = nearestIndex(times, window.startMs);
|
|
388
|
-
const endIndex = nearestIndex(times, window.endMs);
|
|
389
|
-
const lastIndex = times.length - 1;
|
|
390
|
-
const max = Math.max(...totals, 1);
|
|
391
|
-
const overviewPoints = times.map((atMs, i) => `${(fractionOf(atMs) * BRUSH_OVERVIEW_WIDTH).toFixed(1)} ${(BRUSH_OVERVIEW_HEIGHT - ((totals[i] ?? 0) / max) * BRUSH_OVERVIEW_HEIGHT).toFixed(1)}`);
|
|
392
|
-
const overviewPath = `M 0 ${BRUSH_OVERVIEW_HEIGHT} L ${overviewPoints.join(" L ")} L ${BRUSH_OVERVIEW_WIDTH} ${BRUSH_OVERVIEW_HEIGHT} Z`;
|
|
393
|
-
const commit = (startMs, endMs, anchor) => onChange(snapBrushWindow(times, startMs, endMs, anchor));
|
|
394
|
-
const beginDrag = (mode) => (e) => {
|
|
395
|
-
e.currentTarget.setPointerCapture(e.pointerId);
|
|
396
|
-
dragRef.current = {
|
|
397
|
-
mode,
|
|
398
|
-
originX: e.clientX,
|
|
399
|
-
originStartMs: window.startMs,
|
|
400
|
-
originEndMs: window.endMs,
|
|
401
|
-
};
|
|
402
|
-
};
|
|
403
|
-
const moveDrag = (e) => {
|
|
404
|
-
const drag = dragRef.current;
|
|
405
|
-
const trackWidth = trackRef.current?.getBoundingClientRect().width ?? 0;
|
|
406
|
-
if (drag === null || trackWidth <= 0)
|
|
407
|
-
return;
|
|
408
|
-
const deltaMs = ((e.clientX - drag.originX) / trackWidth) * span;
|
|
409
|
-
if (drag.mode === "move") {
|
|
410
|
-
const shift = Math.max(firstMs - drag.originStartMs, Math.min(lastMs - drag.originEndMs, deltaMs));
|
|
411
|
-
onChange(snapMovedBrushWindow(times, drag.originStartMs, drag.originEndMs, shift));
|
|
412
|
-
}
|
|
413
|
-
else if (drag.mode === "start") {
|
|
414
|
-
commit(drag.originStartMs + deltaMs, drag.originEndMs, "start");
|
|
415
|
-
}
|
|
416
|
-
else {
|
|
417
|
-
commit(drag.originStartMs, drag.originEndMs + deltaMs, "end");
|
|
418
|
-
}
|
|
419
|
-
};
|
|
420
|
-
const endDrag = () => {
|
|
421
|
-
dragRef.current = null;
|
|
422
|
-
};
|
|
423
|
-
const dragHandlers = (mode) => ({
|
|
424
|
-
onPointerDown: beginDrag(mode),
|
|
425
|
-
onPointerMove: moveDrag,
|
|
426
|
-
onPointerUp: endDrag,
|
|
427
|
-
onPointerCancel: endDrag,
|
|
428
|
-
});
|
|
429
|
-
const handleKeyDown = (handle) => (e) => {
|
|
430
|
-
const isStart = handle === "start";
|
|
431
|
-
const index = isStart ? startIndex : endIndex;
|
|
432
|
-
const min = isStart ? 0 : startIndex + 1;
|
|
433
|
-
const maxIndex = isStart ? endIndex - 1 : lastIndex;
|
|
434
|
-
const target = brushKeyTarget(e.key, index, min, maxIndex);
|
|
435
|
-
if (target === undefined)
|
|
436
|
-
return;
|
|
437
|
-
e.preventDefault();
|
|
438
|
-
const next = Math.max(min, Math.min(maxIndex, target));
|
|
439
|
-
if (next === index)
|
|
440
|
-
return;
|
|
441
|
-
const nextStart = times[isStart ? next : startIndex];
|
|
442
|
-
const nextEnd = times[isStart ? endIndex : next];
|
|
443
|
-
if (nextStart !== undefined && nextEnd !== undefined) {
|
|
444
|
-
onChange({ startMs: nextStart, endMs: nextEnd });
|
|
445
|
-
}
|
|
446
|
-
};
|
|
447
|
-
const startFraction = fractionOf(window.startMs);
|
|
448
|
-
const endFraction = fractionOf(window.endMs);
|
|
449
|
-
const handleClass = "absolute top-0 z-10 h-full w-6 -translate-x-1/2 cursor-ew-resize touch-none focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring";
|
|
450
|
-
const renderHandle = (handle) => (_jsx("div", { "data-testid": `${testId}-${handle}`, role: "slider", tabIndex: 0, "aria-label": labels[handle], "aria-valuemin": handle === "start" ? 0 : startIndex + 1, "aria-valuemax": handle === "start" ? endIndex - 1 : lastIndex, "aria-valuenow": handle === "start" ? startIndex : endIndex, className: handleClass, style: { left: percent(handle === "start" ? startFraction : endFraction) }, onKeyDown: handleKeyDown(handle), ...dragHandlers(handle), children: _jsx("span", { "aria-hidden": "true", className: "pointer-events-none absolute inset-y-0 left-1/2 w-2 -translate-x-1/2 rounded-sm bg-foreground/60" }) }));
|
|
451
|
-
return (_jsxs("div", { ref: trackRef, "data-testid": testId, className: "relative h-8 w-full select-none rounded-sm border border-border", children: [_jsx("svg", { viewBox: `0 0 ${BRUSH_OVERVIEW_WIDTH} ${BRUSH_OVERVIEW_HEIGHT}`, preserveAspectRatio: "none", className: "absolute inset-0 size-full", role: "presentation", children: _jsx("path", { d: overviewPath, fill: "var(--color-muted-foreground)", fillOpacity: 0.25 }) }), _jsx("div", { "data-testid": `${testId}-window`, className: "absolute top-0 h-full cursor-grab touch-none border-x border-primary bg-primary/10", style: {
|
|
452
|
-
left: percent(startFraction),
|
|
453
|
-
width: percent(Math.max(0, endFraction - startFraction)),
|
|
454
|
-
}, ...dragHandlers("move") }), renderHandle("start"), renderHandle("end")] }));
|
|
455
|
-
}
|
|
456
|
-
/** Stacked bands over time. Right of `todayMs` is the forecast: lighter
|
|
457
|
-
* fill plus a vertical "today" line. */
|
|
458
|
-
export function StackedAreaChart({ series: allSeries, windowStartMs: fullWindowStartMs, windowEndMs: fullWindowEndMs, todayMs, tones, colors, markers: allMarkers, lines: allLines = [], ranges, initialWindow, brush = false, windowSelection, onWindowSelectionChange, brushLabels = DEFAULT_BRUSH_LABELS, ariaLabel, todayLabel, formatBucketLabel, formatValue = String, formatAxisValue = formatValue, formatMarkerTime, dateFormat, timeZone, markerLegend = "list", markerLabels = false, scrollable = false, showLegendTotals = true, emptyContent, testId, }) {
|
|
459
|
-
const clipId = useId();
|
|
460
|
-
const scrollRef = useRef(null);
|
|
461
|
-
const [internalSelection, setInternalSelection] = useState(initialWindowSelection(ranges, initialWindow));
|
|
462
|
-
const isControlled = onWindowSelectionChange !== undefined;
|
|
463
|
-
const selection = isControlled ? windowSelection : internalSelection;
|
|
464
|
-
const changeSelection = (next) => {
|
|
465
|
-
if (isControlled)
|
|
466
|
-
onWindowSelectionChange(next);
|
|
467
|
-
else
|
|
468
|
-
setInternalSelection(next);
|
|
469
|
-
};
|
|
470
|
-
const allTimes = bucketTimes([...allSeries, ...allLines]);
|
|
471
|
-
const isWindowing = ranges !== undefined || brush || selection !== undefined;
|
|
472
|
-
const fullWindow = { startMs: fullWindowStartMs, endMs: fullWindowEndMs };
|
|
473
|
-
const visibleWindow = isWindowing
|
|
474
|
-
? resolveStackedAreaWindow({
|
|
475
|
-
selection,
|
|
476
|
-
ranges,
|
|
477
|
-
brush,
|
|
478
|
-
todayMs,
|
|
479
|
-
bucketTimes: allTimes,
|
|
480
|
-
fullWindow,
|
|
481
|
-
})
|
|
482
|
-
: fullWindow;
|
|
483
|
-
const windowStartMs = visibleWindow.startMs;
|
|
484
|
-
const windowEndMs = visibleWindow.endMs;
|
|
485
|
-
const effectiveDateFormat = dateFormat ?? stackedAreaDateFormatFor(visibleWindow);
|
|
486
|
-
const formatBucket = (atMs) => formatBucketLabel(atMs, effectiveDateFormat);
|
|
487
|
-
const tierTicks = useMemo(() => AXIS_TIERS.map((tier) => stackedAreaAxisTicks({ startMs: windowStartMs, endMs: windowEndMs }, tier.maxLabels, effectiveDateFormat, timeZone)), [windowStartMs, windowEndMs, effectiveDateFormat, timeZone]);
|
|
488
|
-
const formatMarkerTimeInWindow = formatMarkerTime === undefined
|
|
489
|
-
? undefined
|
|
490
|
-
: (atMs) => formatMarkerTime(atMs, effectiveDateFormat);
|
|
491
|
-
const isNarrowed = windowStartMs !== fullWindowStartMs || windowEndMs !== fullWindowEndMs;
|
|
492
|
-
const series = isNarrowed
|
|
493
|
-
? allSeries.map((s) => ({ ...s, points: pointsWithin(s.points, visibleWindow) }))
|
|
494
|
-
: allSeries;
|
|
495
|
-
const lines = isNarrowed
|
|
496
|
-
? allLines.map((line) => ({ ...line, points: pointsWithin(line.points, visibleWindow) }))
|
|
497
|
-
: allLines;
|
|
498
|
-
const markers = isNarrowed && allMarkers !== undefined ? pointsWithin(allMarkers, visibleWindow) : allMarkers;
|
|
499
|
-
const times = bucketTimes([...series, ...lines]);
|
|
500
|
-
const bucketCount = times.length;
|
|
501
|
-
const span = Math.max(1, windowEndMs - windowStartMs);
|
|
502
|
-
const fractionOf = (atMs) => Math.max(0, Math.min(1, (atMs - windowStartMs) / span));
|
|
503
|
-
const todayFraction = todayMs !== undefined ? fractionOf(todayMs) : undefined;
|
|
504
|
-
const [selectedTooltipAtMs, setSelectedTooltipAtMs] = useState(null);
|
|
505
|
-
// Stored as a time, not an index: a shifted window must not retarget the card to another bucket.
|
|
506
|
-
const tooltipAtMs = selectedTooltipAtMs !== null && times.includes(selectedTooltipAtMs)
|
|
507
|
-
? selectedTooltipAtMs
|
|
508
|
-
: undefined;
|
|
509
|
-
const tooltipOverlayRef = useRef(null);
|
|
510
|
-
const touchStartRef = useRef(null);
|
|
511
|
-
const isTooltipOpen = tooltipAtMs !== undefined;
|
|
512
|
-
useEffect(() => {
|
|
513
|
-
if (!isTooltipOpen)
|
|
514
|
-
return;
|
|
515
|
-
const closeOnEscape = (event) => {
|
|
516
|
-
if (event.key === "Escape")
|
|
517
|
-
setSelectedTooltipAtMs(null);
|
|
518
|
-
};
|
|
519
|
-
const closeOnOutsidePointerDown = (event) => {
|
|
520
|
-
const overlay = tooltipOverlayRef.current;
|
|
521
|
-
if (overlay !== null && event.target instanceof Node && overlay.contains(event.target))
|
|
522
|
-
return;
|
|
523
|
-
setSelectedTooltipAtMs(null);
|
|
524
|
-
};
|
|
525
|
-
document.addEventListener("keydown", closeOnEscape);
|
|
526
|
-
document.addEventListener("pointerdown", closeOnOutsidePointerDown);
|
|
527
|
-
return () => {
|
|
528
|
-
document.removeEventListener("keydown", closeOnEscape);
|
|
529
|
-
document.removeEventListener("pointerdown", closeOnOutsidePointerDown);
|
|
530
|
-
};
|
|
531
|
-
}, [isTooltipOpen]);
|
|
532
|
-
useEffect(() => {
|
|
533
|
-
const el = scrollRef.current;
|
|
534
|
-
if (!scrollable || el === null || bucketCount === 0)
|
|
535
|
-
return;
|
|
536
|
-
el.scrollLeft =
|
|
537
|
-
todayFraction === undefined
|
|
538
|
-
? el.scrollWidth
|
|
539
|
-
: Math.max(0, todayFraction * el.scrollWidth - el.clientWidth / 2);
|
|
540
|
-
}, [scrollable, todayFraction, bucketCount]);
|
|
541
|
-
if (times.length === 0)
|
|
542
|
-
return _jsx("div", { "data-testid": testId, children: emptyContent });
|
|
543
|
-
const width = 300;
|
|
544
|
-
const height = 120;
|
|
545
|
-
const totals = times.map((atMs) => series.reduce((sum, s) => sum + valueAt(s, atMs), 0));
|
|
546
|
-
const lineValues = lines.flatMap((line) => line.points.flatMap((p) => (p.value === null ? [] : [p.value])));
|
|
547
|
-
const isSingleBucket = times.length === 1;
|
|
548
|
-
if (isSingleBucket && totals[0] === 0) {
|
|
549
|
-
return _jsx("div", { "data-testid": testId, children: emptyContent });
|
|
550
|
-
}
|
|
551
|
-
// Doubling keeps a lone bucket away from the top edge of the y-scale.
|
|
552
|
-
const max = fineNiceCeil(Math.max(...totals, ...lineValues) * (isSingleBucket ? 2 : 1));
|
|
553
|
-
const yTickValues = evenYTickValues(max);
|
|
554
|
-
const yOf = (value) => height - (value / max) * height;
|
|
555
|
-
const lower = times.map(() => 0);
|
|
556
|
-
const bands = series.map((s, si) => {
|
|
557
|
-
const upper = times.map((atMs, ti) => (lower[ti] ?? 0) + valueAt(s, atMs));
|
|
558
|
-
const top = times.map((atMs, ti) => `${(fractionOf(atMs) * width).toFixed(1)} ${yOf(upper[ti] ?? 0).toFixed(1)}`);
|
|
559
|
-
const bottom = times
|
|
560
|
-
.map((atMs, ti) => `${(fractionOf(atMs) * width).toFixed(1)} ${yOf(lower[ti] ?? 0).toFixed(1)}`)
|
|
561
|
-
.reverse();
|
|
562
|
-
const bucketTop = upper[0] ?? 0;
|
|
563
|
-
const bucketHeight = bucketTop - (lower[0] ?? 0);
|
|
564
|
-
for (let ti = 0; ti < times.length; ti++)
|
|
565
|
-
lower[ti] = upper[ti] ?? 0;
|
|
566
|
-
return {
|
|
567
|
-
key: s.key,
|
|
568
|
-
color: chartColor(s.key, si, tones, colors),
|
|
569
|
-
edge: `M ${top.join(" L ")}`,
|
|
570
|
-
d: `M ${top.join(" L ")} L ${bottom.join(" L ")} Z`,
|
|
571
|
-
dot: isSingleBucket && bucketHeight > 0 ? clampDotY(yOf(bucketTop), height) : undefined,
|
|
572
|
-
};
|
|
573
|
-
});
|
|
574
|
-
const lineColorOffset = series.length;
|
|
575
|
-
const linePaths = lines.map((line, li) => ({
|
|
576
|
-
key: line.key,
|
|
577
|
-
label: line.label,
|
|
578
|
-
dashed: line.dashed === true,
|
|
579
|
-
color: chartColor(line.key, lineColorOffset + li, tones, colors),
|
|
580
|
-
d: linePathSegments(line, (atMs) => fractionOf(atMs) * width, yOf),
|
|
581
|
-
}));
|
|
582
|
-
const coloredMarkers = sortMarkers(markers ?? []).filter((marker) => marker.color !== undefined);
|
|
583
|
-
const todayX = (todayFraction ?? 1) * width;
|
|
584
|
-
const bandGradientId = (index) => `${clipId}-band-${index}`;
|
|
585
|
-
const renderBands = (fillOpacity, edgeOpacity) => bands.map((band, bi) => band.dot === undefined ? (_jsxs("g", { children: [_jsx("path", { d: band.d, fill: `url(#${bandGradientId(bi)})`, fillOpacity: fillOpacity, stroke: "none" }), _jsx("path", { "data-testid": `chart-band-edge-${band.key}`, d: band.edge, fill: "none", stroke: band.color, strokeOpacity: edgeOpacity, strokeWidth: 1.5, vectorEffect: "non-scaling-stroke" })] }, band.key)) : (_jsx("circle", { "data-testid": "stacked-area-single-point", cx: width / 2, cy: band.dot, r: SINGLE_POINT_RADIUS, fill: band.color, fillOpacity: fillOpacity }, band.key)));
|
|
586
|
-
const tooltipFraction = tooltipAtMs === undefined ? 0 : fractionOf(tooltipAtMs);
|
|
587
|
-
const tooltipRows = tooltipAtMs === undefined
|
|
588
|
-
? []
|
|
589
|
-
: [
|
|
590
|
-
...series.map((s, i) => ({
|
|
591
|
-
key: s.key,
|
|
592
|
-
label: s.label,
|
|
593
|
-
color: chartColor(s.key, i, tones, colors),
|
|
594
|
-
swatch: "area",
|
|
595
|
-
value: valueAt(s, tooltipAtMs),
|
|
596
|
-
})),
|
|
597
|
-
...lines.flatMap((line, li) => {
|
|
598
|
-
const value = line.points.find((p) => p.atMs === tooltipAtMs)?.value;
|
|
599
|
-
return value === null || value === undefined
|
|
600
|
-
? []
|
|
601
|
-
: [
|
|
602
|
-
{
|
|
603
|
-
key: line.key,
|
|
604
|
-
label: line.label,
|
|
605
|
-
color: linePaths[li]?.color ?? "var(--color-foreground)",
|
|
606
|
-
swatch: line.dashed === true ? "dashed-line" : "line",
|
|
607
|
-
value,
|
|
608
|
-
},
|
|
609
|
-
];
|
|
610
|
-
}),
|
|
611
|
-
];
|
|
612
|
-
const tooltipOnLeft = tooltipFraction > 0.5;
|
|
613
|
-
const tooltipEdge = `${percent(tooltipFraction)} ${tooltipOnLeft ? "-" : "+"} ${TOOLTIP_OFFSET_PX}px`;
|
|
614
|
-
const tooltipLeft = tooltipOnLeft
|
|
615
|
-
? `max(0px, calc(${tooltipEdge} - ${TOOLTIP_WIDTH_REM}rem))`
|
|
616
|
-
: `max(0px, min(calc(${tooltipEdge}), calc(100% - ${TOOLTIP_WIDTH_REM}rem)))`;
|
|
617
|
-
const bucketAtMsUnderPointer = (event) => {
|
|
618
|
-
const rect = event.currentTarget.getBoundingClientRect();
|
|
619
|
-
return (times[bucketIndexAtFraction(times, visibleWindow, rect.width === 0 ? 0 : (event.clientX - rect.left) / rect.width)] ?? null);
|
|
620
|
-
};
|
|
621
|
-
const tooltipOverlay = (_jsx("div", { ref: tooltipOverlayRef, "data-testid": "chart-tooltip-overlay", className: cn("absolute inset-x-0 top-0", STACKED_AREA_PLOT_HEIGHT_CLASS), onPointerMove: (event) => {
|
|
622
|
-
if (event.pointerType === "mouse")
|
|
623
|
-
setSelectedTooltipAtMs(bucketAtMsUnderPointer(event));
|
|
624
|
-
}, onPointerLeave: (event) => {
|
|
625
|
-
if (event.pointerType === "mouse")
|
|
626
|
-
setSelectedTooltipAtMs(null);
|
|
627
|
-
}, onPointerDown: (event) => {
|
|
628
|
-
if (event.pointerType === "mouse")
|
|
629
|
-
return;
|
|
630
|
-
touchStartRef.current = { x: event.clientX, y: event.clientY };
|
|
631
|
-
}, onPointerCancel: () => {
|
|
632
|
-
touchStartRef.current = null;
|
|
633
|
-
}, onPointerUp: (event) => {
|
|
634
|
-
const start = touchStartRef.current;
|
|
635
|
-
touchStartRef.current = null;
|
|
636
|
-
if (event.pointerType === "mouse" || start === null)
|
|
637
|
-
return;
|
|
638
|
-
const moved = Math.hypot(event.clientX - start.x, event.clientY - start.y);
|
|
639
|
-
if (moved >= TOOLTIP_TAP_MAX_MOVEMENT_PX)
|
|
640
|
-
return;
|
|
641
|
-
const next = bucketAtMsUnderPointer(event);
|
|
642
|
-
setSelectedTooltipAtMs((current) => (current === next ? null : next));
|
|
643
|
-
}, children: tooltipAtMs !== undefined && (_jsxs(_Fragment, { children: [_jsx("div", { "aria-hidden": "true", className: "pointer-events-none absolute inset-y-0 w-px bg-foreground/40", style: { left: percent(tooltipFraction) } }), _jsxs("div", { "data-testid": "chart-tooltip", "data-side": tooltipOnLeft ? "left" : "right", className: "pointer-events-none absolute top-2 rounded-md border bg-popover p-2 text-xs text-popover-foreground shadow-sm", style: { left: tooltipLeft, width: `min(${TOOLTIP_WIDTH_REM}rem, 100%)` }, children: [_jsx("div", { className: "mb-1 font-medium", children: formatBucket(tooltipAtMs) }), tooltipRows.map((row) => (_jsxs("div", { className: "flex items-center gap-1.5", children: [row.swatch === "area" ? (_jsx("span", { "aria-hidden": "true", className: "size-2 shrink-0 rounded-[2px]", style: { backgroundColor: row.color } })) : (_jsx("span", { "aria-hidden": "true", className: "w-3 shrink-0 border-t-2", style: {
|
|
644
|
-
borderColor: row.color,
|
|
645
|
-
borderTopStyle: row.swatch === "dashed-line" ? "dashed" : "solid",
|
|
646
|
-
} })), _jsx("span", { className: "min-w-0 truncate text-muted-foreground", children: row.label }), _jsx("span", { className: "ml-auto tabular-nums", children: formatAxisValue(row.value) })] }, row.key)))] })] })) }));
|
|
647
|
-
const plot = (_jsxs(_Fragment, { children: [todayFraction !== undefined && (_jsx("span", { "data-testid": "chart-today-label", className: "absolute -top-4 -translate-x-1/2 text-[11px] text-foreground", style: { left: percent(todayFraction) }, children: todayLabel })), _jsxs("svg", { viewBox: `0 0 ${width} ${height}`, preserveAspectRatio: "none", className: cn("block w-full", STACKED_AREA_PLOT_HEIGHT_CLASS), role: "img", "aria-label": ariaLabel, children: [_jsx("title", { children: ariaLabel }), _jsxs("defs", { children: [bands.map((band, bi) => (_jsxs("linearGradient", { id: bandGradientId(bi), x1: "0", x2: "0", y1: "0", y2: "1", children: [_jsx("stop", { offset: "0%", stopColor: band.color, stopOpacity: 1 }), _jsx("stop", { offset: "100%", stopColor: band.color, stopOpacity: 0.35 })] }, band.key))), _jsx("clipPath", { id: `${clipId}-past`, children: _jsx("rect", { x: 0, y: 0, width: todayX, height: height }) }), _jsx("clipPath", { id: `${clipId}-future`, children: _jsx("rect", { x: todayX, y: 0, width: width - todayX, height: height }) })] }), yTickValues.map((tickValue) => (_jsx("line", { "data-testid": "chart-grid-line", x1: 0, x2: width, y1: yOf(tickValue), y2: yOf(tickValue), stroke: "var(--color-border)", strokeWidth: 1, strokeDasharray: "3 3", vectorEffect: "non-scaling-stroke" }, tickValue))), _jsx("g", { clipPath: `url(#${clipId}-past)`, children: renderBands(0.55, 0.9) }), todayFraction !== undefined && (_jsx("g", { "data-testid": "chart-forecast-region", clipPath: `url(#${clipId}-future)`, children: renderBands(0.25, 0.5) })), coloredMarkers.map((marker) => {
|
|
648
|
-
const x = fractionOf(marker.atMs) * width;
|
|
649
|
-
return (_jsx("line", { "data-testid": "chart-marker-guide", x1: x, x2: x, y1: 0, y2: height, stroke: marker.color, strokeWidth: 1, strokeDasharray: "2 3", vectorEffect: "non-scaling-stroke" }, marker.key));
|
|
650
|
-
}), linePaths.map((line) => (_jsx("path", { "data-testid": `chart-line-${line.key}`, d: line.d, fill: "none", stroke: line.color, strokeWidth: 2, strokeDasharray: line.dashed ? "6 4" : undefined, vectorEffect: "non-scaling-stroke" }, line.key))), todayFraction !== undefined && (_jsx("line", { "data-testid": "chart-today-line", x1: todayX, x2: todayX, y1: 0, y2: height, stroke: "var(--color-foreground)", strokeWidth: 1, strokeDasharray: "3 3", vectorEffect: "non-scaling-stroke" }))] }), scrollable ? (_jsx("div", { "data-testid": "chart-bucket-labels", className: "relative mt-1 h-4 text-[11px] text-muted-foreground", children: times.map((atMs, i) => (_jsx("span", { className: cn("absolute whitespace-nowrap", bucketLabelTransform(i, times.length)), style: { left: percent(fractionOf(atMs)) }, children: formatBucket(atMs) }, atMs))) })) : (_jsxs("div", { className: "relative mt-1 h-4 text-[11px] text-muted-foreground", children: [_jsx("span", { className: "absolute left-0 whitespace-nowrap", children: formatBucket(windowStartMs) }), _jsx("span", { className: "absolute right-0 whitespace-nowrap", children: formatBucket(windowEndMs) }), AXIS_TIERS.map((tier, tierIndex) => (_jsx("div", { className: cn("absolute inset-0", tier.visibilityClass), children: (tierTicks[tierIndex] ?? []).map((tickMs) => (_jsx("span", { "data-testid": "chart-axis-tick", className: "absolute -translate-x-1/2 whitespace-nowrap", style: { left: percent(fractionOf(tickMs)) }, children: formatBucket(tickMs) }, tickMs))) }, tier.maxLabels)))] })), tooltipOverlay, markerLabels &&
|
|
651
|
-
groupMarkerLabels(markers ?? []).map((group) => {
|
|
652
|
-
const fraction = fractionOf(group.atMs);
|
|
653
|
-
const flipped = fraction > MARKER_LABEL_FLIP_FRACTION;
|
|
654
|
-
return (_jsx("div", { className: cn("pointer-events-none absolute top-0 hidden @2xl:block", flipped ? "-translate-x-full pr-1" : "pl-1"), style: { left: percent(fraction) }, children: _jsx("span", { "data-testid": "chart-marker-label", className: "block max-h-44 rotate-180 overflow-hidden text-ellipsis whitespace-nowrap text-[11px] leading-none [writing-mode:vertical-rl] @2xl:max-h-60", style: { color: group.color }, children: group.text }) }, group.atMs));
|
|
655
|
-
}), markers !== undefined && markers.length > 0 && (_jsx(MarkerPins, { markers: markers, windowStartMs: windowStartMs, windowEndMs: windowEndMs, numbered: markerLegend === "list" || markerLabels, formatMarkerTime: formatMarkerTimeInWindow, ...(markerLabels && { className: "@2xl:hidden" }) }))] }));
|
|
656
|
-
const ownRangeSwitch = !isControlled && ranges !== undefined ? (_jsx("div", { className: "flex justify-end", children: _jsx(ModeSwitch, { value: selection?.kind === "range" ? selection.value : null, options: ranges.options.map((option) => ({
|
|
657
|
-
value: option.value,
|
|
658
|
-
label: option.label,
|
|
659
|
-
})), onChange: (value) => changeSelection({ kind: "range", value }), ariaLabel: ariaLabel, variant: "pill", testId: testId !== undefined ? `${testId}-range` : undefined }) })) : undefined;
|
|
660
|
-
const brushBar = brush && allTimes.length >= 2 ? (_jsx(StackedAreaBrush, { times: allTimes, totals: allTimes.map((atMs) => allSeries.reduce((sum, s) => sum + valueAt(s, atMs), 0)), window: visibleWindow, labels: brushLabels, onChange: (next) => changeSelection({ kind: "brush", startMs: next.startMs, endMs: next.endMs }), testId: `${testId ?? "stacked-area"}-brush` })) : undefined;
|
|
661
|
-
return (_jsxs("div", { "data-testid": testId, className: "@container flex flex-col gap-3", children: [ownRangeSwitch, _jsxs("div", { className: "flex gap-2", children: [_jsx(YTicks, { values: yTickValues, formatValue: formatAxisValue, className: cn("shrink-0 pb-0", STACKED_AREA_PLOT_HEIGHT_CLASS, scrollable && "mt-4") }), scrollable ? (_jsx("div", { ref: scrollRef, "data-testid": "chart-scroll-container", className: "grow overflow-x-auto pt-4", children: _jsx("div", { "data-testid": "chart-scroll-content", className: "relative", style: {
|
|
662
|
-
minWidth: "100%",
|
|
663
|
-
width: `${times.length * SCROLLABLE_BUCKET_WIDTH_PX}px`,
|
|
664
|
-
}, children: plot }) })) : (_jsx("div", { className: "relative grow", children: plot }))] }), brushBar, _jsx(ChartLegend, { formatValue: formatValue, items: [
|
|
665
|
-
...series.map((s, i) => ({
|
|
666
|
-
key: s.key,
|
|
667
|
-
label: s.label,
|
|
668
|
-
color: chartColor(s.key, i, tones, colors),
|
|
669
|
-
...(showLegendTotals && { total: seriesTotal(s) }),
|
|
670
|
-
})),
|
|
671
|
-
...linePaths.map((line) => ({
|
|
672
|
-
key: `line:${line.key}`,
|
|
673
|
-
testIdKey: `line-${line.key}`,
|
|
674
|
-
label: line.label,
|
|
675
|
-
color: line.color,
|
|
676
|
-
swatch: line.dashed ? "dashed-line" : "line",
|
|
677
|
-
})),
|
|
678
|
-
...(markerLegend === "legend"
|
|
679
|
-
? markerLegendItems(markers ?? []).map((item) => ({
|
|
680
|
-
...item,
|
|
681
|
-
swatch: "dashed-line",
|
|
682
|
-
}))
|
|
683
|
-
: []),
|
|
684
|
-
] }), markers !== undefined && markers.length > 0 && (markerLegend === "list" || markerLabels) && (_jsx(MarkerLegend, { markers: markers, formatMarkerTime: formatMarkerTimeInWindow, ...(markerLabels && { className: "@2xl:hidden" }) }))] }));
|
|
685
|
-
}
|
|
205
|
+
export { chartToneColor } from "./chart-data.js";
|
|
206
|
+
export { assignPinRows } from "./chart-markers.js";
|
|
207
|
+
export { StackedAreaChart } from "./stacked-area-chart.js";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// Must match the static `max(600px,37.5vw)` in sidePanelClass — Tailwind's
|
|
2
|
+
// JIT can't read DEFAULT_WIDTH_MIN_PX/DEFAULT_WIDTH_VIEWPORT_RATIO at
|
|
3
|
+
// runtime, so the two are kept in sync by hand.
|
|
4
|
+
// Same cap as WIDTH_CLASS: an explicit `width` must not outgrow the viewport.
|
|
5
|
+
export const MAX_VIEWPORT_WIDTH = "85vw";
|
|
6
|
+
const WIDTH_CLASS = "w-[max(600px,37.5vw)] max-w-[85vw] sm:max-w-[max(600px,37.5vw)]";
|
|
7
|
+
// "floating": 32px margin + 32px radius so the panel reads as detached from
|
|
8
|
+
// the viewport edge, unlike the sheet primitive's flush-edge default.
|
|
9
|
+
// "flush" docks it back to that flush-edge default instead, borrowing the
|
|
10
|
+
// vendored Sheet's per-side border (only the edge facing the app content).
|
|
11
|
+
// Below the narrow-viewport breakpoint neither treatment fits — the JS hook
|
|
12
|
+
// decides, not a `sm:` Tailwind prefix, since the resize width below already
|
|
13
|
+
// comes from JS and two decision sources is the bug this avoids.
|
|
14
|
+
export function sidePanelClass(side, narrow, variant, belowHeader) {
|
|
15
|
+
if (narrow)
|
|
16
|
+
return "inset-0 h-full w-full max-w-none rounded-none border-0 overflow-hidden";
|
|
17
|
+
if (variant === "flush") {
|
|
18
|
+
switch (side) {
|
|
19
|
+
// h-full would pin height to 100vh despite the shifted top; h-auto lets inset-y-0 size it.
|
|
20
|
+
case "left":
|
|
21
|
+
return `inset-y-0 left-0 ${belowHeader ? "h-auto" : "h-full"} ${WIDTH_CLASS} border-r shadow-2xl overflow-hidden`;
|
|
22
|
+
case "top":
|
|
23
|
+
return `inset-x-0 ${belowHeader ? "top-(--shell-header-height)" : "top-0"} h-auto max-h-[80vh] border-b shadow-2xl overflow-hidden`;
|
|
24
|
+
case "bottom":
|
|
25
|
+
return "inset-x-0 bottom-0 h-auto max-h-[80vh] border-t shadow-2xl overflow-hidden";
|
|
26
|
+
default:
|
|
27
|
+
return `inset-y-0 right-0 ${belowHeader ? "h-auto" : "h-full"} ${WIDTH_CLASS} border-l shadow-2xl overflow-hidden`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
switch (side) {
|
|
31
|
+
case "left":
|
|
32
|
+
return `inset-y-8 left-8 h-auto ${WIDTH_CLASS} rounded-[2rem] border shadow-2xl overflow-hidden`;
|
|
33
|
+
case "top":
|
|
34
|
+
return "inset-x-8 top-8 h-auto max-h-[80vh] rounded-[2rem] border shadow-2xl overflow-hidden";
|
|
35
|
+
case "bottom":
|
|
36
|
+
return "inset-x-8 bottom-8 h-auto max-h-[80vh] rounded-[2rem] border shadow-2xl overflow-hidden";
|
|
37
|
+
default:
|
|
38
|
+
return `inset-y-8 right-8 h-auto ${WIDTH_CLASS} rounded-[2rem] border shadow-2xl overflow-hidden`;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { useTranslation } from "../../index.ts";
|
|
3
|
+
import type { DrawerResizeHandle, DrawerResizeState } from "./use-drawer-resize.ts";
|
|
4
|
+
export type DrawerResizeControlsModel = {
|
|
5
|
+
readonly t: ReturnType<typeof useTranslation>;
|
|
6
|
+
readonly side: "left" | "right" | "top" | "bottom";
|
|
7
|
+
readonly resize: DrawerResizeState;
|
|
8
|
+
readonly handle: DrawerResizeHandle;
|
|
9
|
+
};
|
|
10
|
+
export declare function renderMaximizeButton(model: DrawerResizeControlsModel): ReactNode;
|
|
11
|
+
export declare function renderResizeHandle(model: DrawerResizeControlsModel): ReactNode;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { Maximize2Icon, Minimize2Icon } from "lucide-react";
|
|
3
|
+
import { cn } from "../lib/cn.js";
|
|
4
|
+
export function renderMaximizeButton(model) {
|
|
5
|
+
const { t, resize } = model;
|
|
6
|
+
const { maximized, setMaximized } = resize;
|
|
7
|
+
return (_jsx("button", { type: "button", onClick: () => setMaximized((m) => !m), "aria-pressed": maximized, "aria-label": maximized ? t("kumiko.widget.drawer.restore") : t("kumiko.widget.drawer.maximize"), className: "absolute top-4 right-14 z-10 rounded-xs p-1 text-muted-foreground opacity-70 transition-opacity hover:opacity-100 hover:bg-secondary focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-hidden", children: maximized ? _jsx(Minimize2Icon, { className: "size-4" }) : _jsx(Maximize2Icon, { className: "size-4" }) }));
|
|
8
|
+
}
|
|
9
|
+
export function renderResizeHandle(model) {
|
|
10
|
+
const { t, side, resize, handle } = model;
|
|
11
|
+
return (_jsx("div", { role: "separator", "aria-orientation": "vertical", "aria-label": t("kumiko.widget.drawer.resize"), "aria-valuenow": resize.effectiveWidthPx, "aria-valuemin": resize.minWidthPx, "aria-valuemax": resize.effectiveMaxWidthPx(), tabIndex: 0, ...handle.handleDrag, onKeyDown: handle.onHandleKeyDown, className: cn("absolute inset-y-0 z-10 w-1 cursor-col-resize touch-none after:absolute after:inset-y-0 after:left-1/2 after:w-3 after:-translate-x-1/2 hover:bg-border", side === "right" ? "left-0 -translate-x-1/2" : "right-0 translate-x-1/2") }));
|
|
12
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { CSSProperties } from "react";
|
|
2
|
+
export declare const MIN_WIDTH_PX = 320;
|
|
3
|
+
export declare const MAX_WIDTH_PX = 1000;
|
|
4
|
+
export declare function defaultWidthFromViewport(): number;
|
|
5
|
+
export declare function buildOverlayStyle(backdrop: {
|
|
6
|
+
readonly blurPx?: number;
|
|
7
|
+
readonly dimPercent?: number;
|
|
8
|
+
} | undefined): CSSProperties;
|
|
9
|
+
type SheetStyleInput = {
|
|
10
|
+
readonly side: "left" | "right" | "top" | "bottom";
|
|
11
|
+
readonly narrow: boolean;
|
|
12
|
+
readonly canResize: boolean;
|
|
13
|
+
readonly width: number | string | undefined;
|
|
14
|
+
readonly effectiveWidthPx: number;
|
|
15
|
+
readonly variant: "floating" | "flush";
|
|
16
|
+
readonly belowHeader: boolean;
|
|
17
|
+
};
|
|
18
|
+
export declare function buildSheetStyle(input: SheetStyleInput): CSSProperties | undefined;
|
|
19
|
+
export {};
|