@camstack/ui-library 1.2.329 → 1.2.331
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/composites/charts/BarSeries.d.ts +4 -1
- package/dist/composites/charts/Sparkline.d.ts +6 -1
- package/dist/composites/charts/TimeSeriesChart.d.ts +4 -1
- package/dist/composites/charts/chart-tooltip-host.d.ts +27 -0
- package/dist/composites/charts/chart-tooltip-model.d.ts +174 -0
- package/dist/composites/charts/time-series-model.d.ts +3 -0
- package/dist/composites/charts/uplot-host.d.ts +9 -1
- package/dist/composites/index.d.ts +1 -0
- package/dist/composites/view-switch.d.ts +24 -0
- package/dist/index.cjs +864 -30
- package/dist/index.js +865 -32
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -22518,6 +22518,641 @@ function bucketIndexAt(buckets, bucketMs, t) {
|
|
|
22518
22518
|
}
|
|
22519
22519
|
return null;
|
|
22520
22520
|
}
|
|
22521
|
+
var UNKNOWN$1 = "—";
|
|
22522
|
+
var NO_INFERENCE = new Set([
|
|
22523
|
+
"information",
|
|
22524
|
+
"duration",
|
|
22525
|
+
"duration-ms",
|
|
22526
|
+
"none",
|
|
22527
|
+
"device",
|
|
22528
|
+
"count",
|
|
22529
|
+
"index",
|
|
22530
|
+
"ratio",
|
|
22531
|
+
"humidity",
|
|
22532
|
+
"percentage"
|
|
22533
|
+
]);
|
|
22534
|
+
var HIDDEN_INTERACTION = {
|
|
22535
|
+
selection: {
|
|
22536
|
+
visible: false,
|
|
22537
|
+
dataX: null,
|
|
22538
|
+
anchorX: 0,
|
|
22539
|
+
anchorY: 0,
|
|
22540
|
+
pinned: false
|
|
22541
|
+
},
|
|
22542
|
+
gesture: null,
|
|
22543
|
+
lockHorizontalScroll: false
|
|
22544
|
+
};
|
|
22545
|
+
var TOOLTIP_SIDES = [
|
|
22546
|
+
{
|
|
22547
|
+
placed: "right",
|
|
22548
|
+
vertical: "above"
|
|
22549
|
+
},
|
|
22550
|
+
{
|
|
22551
|
+
placed: "left",
|
|
22552
|
+
vertical: "above"
|
|
22553
|
+
},
|
|
22554
|
+
{
|
|
22555
|
+
placed: "right",
|
|
22556
|
+
vertical: "below"
|
|
22557
|
+
},
|
|
22558
|
+
{
|
|
22559
|
+
placed: "left",
|
|
22560
|
+
vertical: "below"
|
|
22561
|
+
}
|
|
22562
|
+
];
|
|
22563
|
+
/** Index of the sample closest to `x`. A tie keeps the earlier sample. */
|
|
22564
|
+
function nearestPointIndex(xs, x) {
|
|
22565
|
+
if (xs.length === 0 || !Number.isFinite(x)) return null;
|
|
22566
|
+
let lo = 0;
|
|
22567
|
+
let hi = xs.length - 1;
|
|
22568
|
+
while (lo < hi) {
|
|
22569
|
+
const mid = Math.floor((lo + hi) / 2);
|
|
22570
|
+
if ((xs[mid] ?? 0) < x) lo = mid + 1;
|
|
22571
|
+
else hi = mid;
|
|
22572
|
+
}
|
|
22573
|
+
const right = lo;
|
|
22574
|
+
const left = Math.max(0, right - 1);
|
|
22575
|
+
return Math.abs((xs[left] ?? 0) - x) <= Math.abs((xs[right] ?? 0) - x) ? left : right;
|
|
22576
|
+
}
|
|
22577
|
+
/** Data x for a pointer inside the plot. `null` when it is outside and clamping is off. */
|
|
22578
|
+
function dataXFromPointer(input) {
|
|
22579
|
+
if (!(input.plotWidth > 0) || !Number.isFinite(input.localX)) return null;
|
|
22580
|
+
const rel = input.localX - input.plotLeft;
|
|
22581
|
+
if (!input.clamp && (rel < 0 || rel > input.plotWidth)) return null;
|
|
22582
|
+
const clamped = Math.min(input.plotWidth, Math.max(0, rel));
|
|
22583
|
+
return input.xMin + clamped / input.plotWidth * (input.xMax - input.xMin);
|
|
22584
|
+
}
|
|
22585
|
+
/** Bucket that contains `dataX`, or the nearest one when `dataX` falls in a gap. */
|
|
22586
|
+
function bucketSelectionIndex(buckets, bucketMs, dataX) {
|
|
22587
|
+
if (buckets.length === 0 || !Number.isFinite(dataX)) return null;
|
|
22588
|
+
let best = 0;
|
|
22589
|
+
let bestDist = Number.POSITIVE_INFINITY;
|
|
22590
|
+
for (let i = 0; i < buckets.length; i++) {
|
|
22591
|
+
const start = buckets[i]?.t;
|
|
22592
|
+
if (start === void 0) continue;
|
|
22593
|
+
const end = start + bucketMs;
|
|
22594
|
+
if (dataX >= start && dataX < end) return i;
|
|
22595
|
+
const dist = dataX < start ? start - dataX : dataX - end;
|
|
22596
|
+
if (dist < bestDist) {
|
|
22597
|
+
bestDist = dist;
|
|
22598
|
+
best = i;
|
|
22599
|
+
}
|
|
22600
|
+
}
|
|
22601
|
+
return best;
|
|
22602
|
+
}
|
|
22603
|
+
/** Next keyboard stop. A null current starts at the first or last stop. */
|
|
22604
|
+
function stepStop(stops, current, direction) {
|
|
22605
|
+
if (stops.length === 0) return null;
|
|
22606
|
+
const ordered = [...stops].sort((a, b) => a - b);
|
|
22607
|
+
if (current === null || !Number.isFinite(current)) return direction > 0 ? ordered[0] ?? null : ordered[ordered.length - 1] ?? null;
|
|
22608
|
+
let idx = 0;
|
|
22609
|
+
let best = Number.POSITIVE_INFINITY;
|
|
22610
|
+
for (let i = 0; i < ordered.length; i++) {
|
|
22611
|
+
const dist = Math.abs((ordered[i] ?? 0) - current);
|
|
22612
|
+
if (dist < best) {
|
|
22613
|
+
best = dist;
|
|
22614
|
+
idx = i;
|
|
22615
|
+
}
|
|
22616
|
+
}
|
|
22617
|
+
const next = idx + direction;
|
|
22618
|
+
if (next < 0 || next >= ordered.length) return ordered[idx] ?? null;
|
|
22619
|
+
return ordered[next] ?? null;
|
|
22620
|
+
}
|
|
22621
|
+
function shown(dataX, anchorX, anchorY, pinned) {
|
|
22622
|
+
return {
|
|
22623
|
+
visible: true,
|
|
22624
|
+
dataX,
|
|
22625
|
+
anchorX,
|
|
22626
|
+
anchorY,
|
|
22627
|
+
pinned
|
|
22628
|
+
};
|
|
22629
|
+
}
|
|
22630
|
+
function resting(selection, gesture, lock) {
|
|
22631
|
+
return {
|
|
22632
|
+
selection,
|
|
22633
|
+
gesture,
|
|
22634
|
+
lockHorizontalScroll: lock
|
|
22635
|
+
};
|
|
22636
|
+
}
|
|
22637
|
+
function lockAxis(gesture, x, y) {
|
|
22638
|
+
if (gesture.axis !== "pending") return gesture.axis;
|
|
22639
|
+
const dx = Math.abs(x - gesture.originX);
|
|
22640
|
+
const dy = Math.abs(y - gesture.originY);
|
|
22641
|
+
if (dx < 10 && dy < 10) return "pending";
|
|
22642
|
+
return dx >= dy ? "x" : "y";
|
|
22643
|
+
}
|
|
22644
|
+
function onDown(state, event) {
|
|
22645
|
+
if (event.kind === "mouse") return state;
|
|
22646
|
+
return resting(state.selection, {
|
|
22647
|
+
kind: event.kind,
|
|
22648
|
+
originX: event.x,
|
|
22649
|
+
originY: event.y,
|
|
22650
|
+
axis: "pending"
|
|
22651
|
+
}, false);
|
|
22652
|
+
}
|
|
22653
|
+
function onMove(state, event) {
|
|
22654
|
+
if (event.kind === "mouse") {
|
|
22655
|
+
if (event.dataX === null) return state.selection.pinned ? state : HIDDEN_INTERACTION;
|
|
22656
|
+
return resting(shown(event.dataX, event.x, event.y, false), null, false);
|
|
22657
|
+
}
|
|
22658
|
+
const gesture = state.gesture;
|
|
22659
|
+
if (gesture === null) return state;
|
|
22660
|
+
const axis = lockAxis(gesture, event.x, event.y);
|
|
22661
|
+
const nextGesture = {
|
|
22662
|
+
...gesture,
|
|
22663
|
+
axis
|
|
22664
|
+
};
|
|
22665
|
+
if (axis !== "x" || event.dataX === null) return resting(state.selection, nextGesture, false);
|
|
22666
|
+
return resting(shown(event.dataX, event.x, event.y, true), nextGesture, true);
|
|
22667
|
+
}
|
|
22668
|
+
function onUp(state, event) {
|
|
22669
|
+
if (event.kind === "mouse" || state.gesture === null) return state;
|
|
22670
|
+
if (state.gesture.axis === "y" || state.gesture.axis === "x") return resting(state.selection, null, false);
|
|
22671
|
+
if (state.selection.visible && state.selection.pinned) return HIDDEN_INTERACTION;
|
|
22672
|
+
if (event.dataX === null) return HIDDEN_INTERACTION;
|
|
22673
|
+
return resting(shown(event.dataX, event.x, event.y, true), null, false);
|
|
22674
|
+
}
|
|
22675
|
+
/** Pointer, keyboard and dismiss rules for one chart. */
|
|
22676
|
+
function reduceChartInteraction(state, event) {
|
|
22677
|
+
switch (event.type) {
|
|
22678
|
+
case "hide":
|
|
22679
|
+
case "outside": return HIDDEN_INTERACTION;
|
|
22680
|
+
case "leave": return state.selection.pinned ? resting(state.selection, null, false) : HIDDEN_INTERACTION;
|
|
22681
|
+
case "hover": return resting(shown(event.dataX, event.anchorX, event.anchorY, false), null, false);
|
|
22682
|
+
case "key": return resting(shown(event.dataX, event.anchorX, event.anchorY, true), null, false);
|
|
22683
|
+
case "down": return onDown(state, event);
|
|
22684
|
+
case "move": return onMove(state, event);
|
|
22685
|
+
case "up": return onUp(state, event);
|
|
22686
|
+
default: return event;
|
|
22687
|
+
}
|
|
22688
|
+
}
|
|
22689
|
+
function quantityFromUnit(unit) {
|
|
22690
|
+
if (unit === null || unit === void 0 || unit.length === 0) return void 0;
|
|
22691
|
+
let found;
|
|
22692
|
+
for (const key of Object.keys(_camstack_types.QUANTITY_BASE)) {
|
|
22693
|
+
if (!(0, _camstack_types.isReadingQuantity)(key) || NO_INFERENCE.has(key)) continue;
|
|
22694
|
+
if (_camstack_types.QUANTITY_BASE[key] !== unit) continue;
|
|
22695
|
+
if (found !== void 0) return void 0;
|
|
22696
|
+
found = key;
|
|
22697
|
+
}
|
|
22698
|
+
return found;
|
|
22699
|
+
}
|
|
22700
|
+
function withUnit(text, unit, unknown) {
|
|
22701
|
+
if (text.length === 0) return unknown;
|
|
22702
|
+
if (unit.length === 0) return text;
|
|
22703
|
+
if (unit === "%") return `${text}%`;
|
|
22704
|
+
return `${text} ${unit}`;
|
|
22705
|
+
}
|
|
22706
|
+
/** One sample, with its unit. Missing samples are an em dash, never zero. */
|
|
22707
|
+
function formatTooltipValue(input) {
|
|
22708
|
+
const unknown = input.unknownLabel ?? UNKNOWN$1;
|
|
22709
|
+
if (input.value === null || !Number.isFinite(input.value)) return unknown;
|
|
22710
|
+
const quantity = input.quantity === "device" ? void 0 : input.quantity ?? quantityFromUnit(input.unit);
|
|
22711
|
+
if (quantity === void 0) return formatChartValue(input.value, input.unit ?? null);
|
|
22712
|
+
const rendered = (0, _camstack_types.formatReading)({
|
|
22713
|
+
value: input.value,
|
|
22714
|
+
quantity,
|
|
22715
|
+
preference: input.preference,
|
|
22716
|
+
precision: input.precision
|
|
22717
|
+
});
|
|
22718
|
+
return withUnit(rendered.text, rendered.unit, unknown);
|
|
22719
|
+
}
|
|
22720
|
+
function liveText(heading, lines) {
|
|
22721
|
+
if (lines.length === 0) return heading;
|
|
22722
|
+
return `${heading}, ${lines.map((line) => `${line.label} ${line.value}`).join(", ")}`;
|
|
22723
|
+
}
|
|
22724
|
+
function asBody(heading, lines) {
|
|
22725
|
+
return {
|
|
22726
|
+
heading,
|
|
22727
|
+
lines,
|
|
22728
|
+
liveText: liveText(heading, lines)
|
|
22729
|
+
};
|
|
22730
|
+
}
|
|
22731
|
+
/** Time plus one line per visible series. */
|
|
22732
|
+
function lineTooltipBody(input) {
|
|
22733
|
+
const lines = input.series.filter((sample) => sample.visible !== false).map((sample) => ({
|
|
22734
|
+
id: sample.id,
|
|
22735
|
+
label: sample.label,
|
|
22736
|
+
value: formatTooltipValue({
|
|
22737
|
+
value: sample.value,
|
|
22738
|
+
quantity: sample.quantity,
|
|
22739
|
+
unit: sample.unit,
|
|
22740
|
+
precision: sample.precision,
|
|
22741
|
+
preference: input.preference
|
|
22742
|
+
})
|
|
22743
|
+
}));
|
|
22744
|
+
return asBody(input.timeLabel, lines);
|
|
22745
|
+
}
|
|
22746
|
+
/** The state in force at `t`, and how long that span lasts. */
|
|
22747
|
+
function stateSpanAt(segments, t) {
|
|
22748
|
+
for (const segment of segments) if (t >= segment.from && t < segment.to) return {
|
|
22749
|
+
state: segment.state,
|
|
22750
|
+
spanMs: Math.max(0, segment.to - segment.from)
|
|
22751
|
+
};
|
|
22752
|
+
return null;
|
|
22753
|
+
}
|
|
22754
|
+
function stateTooltipValue(stateLabel, spanMs) {
|
|
22755
|
+
if (stateLabel === null || stateLabel.length === 0) return UNKNOWN$1;
|
|
22756
|
+
if (spanMs === null || !(spanMs > 0)) return stateLabel;
|
|
22757
|
+
return `${stateLabel} · ${formatSpan(spanMs)}`;
|
|
22758
|
+
}
|
|
22759
|
+
/** One row per state series: the label and the span under the pointer. */
|
|
22760
|
+
function stateTooltipBody(input) {
|
|
22761
|
+
const lines = input.rows.map((row) => ({
|
|
22762
|
+
id: row.id,
|
|
22763
|
+
label: row.label,
|
|
22764
|
+
value: stateTooltipValue(row.stateLabel, row.spanMs)
|
|
22765
|
+
}));
|
|
22766
|
+
return asBody(input.timeLabel, lines);
|
|
22767
|
+
}
|
|
22768
|
+
/** `start – end` for one bar bucket, at the window's time precision. */
|
|
22769
|
+
function barRangeLabel(start, bucketMs, spanMs) {
|
|
22770
|
+
return `${formatCursorTime(start, spanMs)} – ${formatCursorTime(start + bucketMs, spanMs)}`;
|
|
22771
|
+
}
|
|
22772
|
+
/** The bucket's range, and each layer's value with its unit. */
|
|
22773
|
+
function barTooltipBody(input) {
|
|
22774
|
+
const lines = input.layers.map((layer) => ({
|
|
22775
|
+
id: layer.id,
|
|
22776
|
+
label: layer.label,
|
|
22777
|
+
value: formatTooltipValue({
|
|
22778
|
+
value: layer.value,
|
|
22779
|
+
quantity: layer.quantity,
|
|
22780
|
+
unit: layer.unit,
|
|
22781
|
+
precision: layer.precision,
|
|
22782
|
+
preference: input.preference
|
|
22783
|
+
})
|
|
22784
|
+
}));
|
|
22785
|
+
return asBody(input.rangeLabel, lines);
|
|
22786
|
+
}
|
|
22787
|
+
function sideOrigin(side, input, gap) {
|
|
22788
|
+
return {
|
|
22789
|
+
left: side.placed === "right" ? input.anchorX + gap : input.anchorX - gap - input.tooltipWidth,
|
|
22790
|
+
top: side.vertical === "above" ? input.anchorY - gap - input.tooltipHeight : input.anchorY + gap
|
|
22791
|
+
};
|
|
22792
|
+
}
|
|
22793
|
+
function insideBounds(left, top, input) {
|
|
22794
|
+
const pad = 4;
|
|
22795
|
+
return left >= pad && top >= pad && left + input.tooltipWidth <= input.boundsWidth - pad && top + input.tooltipHeight <= input.boundsHeight - pad;
|
|
22796
|
+
}
|
|
22797
|
+
function coversPoint(left, top, input) {
|
|
22798
|
+
return input.anchorX >= left && input.anchorX <= left + input.tooltipWidth && input.anchorY >= top && input.anchorY <= top + input.tooltipHeight;
|
|
22799
|
+
}
|
|
22800
|
+
function clampOrigin(left, top, input) {
|
|
22801
|
+
const pad = 4;
|
|
22802
|
+
const maxLeft = Math.max(pad, input.boundsWidth - pad - input.tooltipWidth);
|
|
22803
|
+
const maxTop = Math.max(pad, input.boundsHeight - pad - input.tooltipHeight);
|
|
22804
|
+
return {
|
|
22805
|
+
left: Math.min(Math.max(left, pad), maxLeft),
|
|
22806
|
+
top: Math.min(Math.max(top, pad), maxTop)
|
|
22807
|
+
};
|
|
22808
|
+
}
|
|
22809
|
+
/**
|
|
22810
|
+
* Place the tooltip beside the point, inside the chart. The first side that
|
|
22811
|
+
* fits and does not cover the point wins; the edges flip before the corners.
|
|
22812
|
+
*/
|
|
22813
|
+
function placeChartTooltip(input) {
|
|
22814
|
+
const gap = input.gap ?? 12;
|
|
22815
|
+
let fallback = null;
|
|
22816
|
+
for (const side of TOOLTIP_SIDES) {
|
|
22817
|
+
const origin = sideOrigin(side, input, gap);
|
|
22818
|
+
const clamped = clampOrigin(origin.left, origin.top, input);
|
|
22819
|
+
const placement = {
|
|
22820
|
+
...side,
|
|
22821
|
+
...clamped
|
|
22822
|
+
};
|
|
22823
|
+
if (fallback === null && !coversPoint(clamped.left, clamped.top, input)) fallback = placement;
|
|
22824
|
+
if (insideBounds(origin.left, origin.top, input) && !coversPoint(origin.left, origin.top, input)) return {
|
|
22825
|
+
...side,
|
|
22826
|
+
...origin
|
|
22827
|
+
};
|
|
22828
|
+
}
|
|
22829
|
+
if (fallback !== null) return fallback;
|
|
22830
|
+
const first = TOOLTIP_SIDES[0] ?? {
|
|
22831
|
+
placed: "right",
|
|
22832
|
+
vertical: "above"
|
|
22833
|
+
};
|
|
22834
|
+
const clamped = clampOrigin(sideOrigin(first, input, gap).left, sideOrigin(first, input, gap).top, input);
|
|
22835
|
+
return {
|
|
22836
|
+
...first,
|
|
22837
|
+
...clamped
|
|
22838
|
+
};
|
|
22839
|
+
}
|
|
22840
|
+
//#endregion
|
|
22841
|
+
//#region src/composites/charts/chart-tooltip-host.tsx
|
|
22842
|
+
/**
|
|
22843
|
+
* One tooltip for every chart. The plot stays in `UPlotHost`; this shell
|
|
22844
|
+
* tracks the pointer, a touch scrub and the arrow keys, and draws the
|
|
22845
|
+
* readout inside the chart.
|
|
22846
|
+
*/
|
|
22847
|
+
function pointerKind(pointerType) {
|
|
22848
|
+
return pointerType === "mouse" ? "mouse" : "touch";
|
|
22849
|
+
}
|
|
22850
|
+
function pointerSample(clientX, clientY, shell, plot, spec, clamp) {
|
|
22851
|
+
const box = shell.getBoundingClientRect();
|
|
22852
|
+
const anchorX = clientX - box.left;
|
|
22853
|
+
const anchorY = clientY - box.top;
|
|
22854
|
+
if (plot !== null) {
|
|
22855
|
+
const over = plot.over.getBoundingClientRect();
|
|
22856
|
+
const left = clientX - over.left;
|
|
22857
|
+
const top = clientY - over.top;
|
|
22858
|
+
const minX = plot.bbox.left;
|
|
22859
|
+
const maxX = plot.bbox.left + plot.bbox.width;
|
|
22860
|
+
const inside = left >= minX && left <= maxX && top >= plot.bbox.top && top <= plot.bbox.top + plot.bbox.height;
|
|
22861
|
+
if (!inside && !clamp) return {
|
|
22862
|
+
dataX: null,
|
|
22863
|
+
anchorX,
|
|
22864
|
+
anchorY,
|
|
22865
|
+
inside: false
|
|
22866
|
+
};
|
|
22867
|
+
return {
|
|
22868
|
+
dataX: plot.posToVal(Math.min(maxX, Math.max(minX, left)), "x"),
|
|
22869
|
+
anchorX,
|
|
22870
|
+
anchorY,
|
|
22871
|
+
inside
|
|
22872
|
+
};
|
|
22873
|
+
}
|
|
22874
|
+
const dataX = dataXFromPointer({
|
|
22875
|
+
localX: anchorX,
|
|
22876
|
+
plotLeft: 0,
|
|
22877
|
+
plotWidth: box.width,
|
|
22878
|
+
xMin: spec.xMin,
|
|
22879
|
+
xMax: spec.xMax,
|
|
22880
|
+
clamp
|
|
22881
|
+
});
|
|
22882
|
+
return {
|
|
22883
|
+
dataX,
|
|
22884
|
+
anchorX,
|
|
22885
|
+
anchorY,
|
|
22886
|
+
inside: dataX !== null
|
|
22887
|
+
};
|
|
22888
|
+
}
|
|
22889
|
+
function anchorForDataX(dataX, shell, plot, spec) {
|
|
22890
|
+
if (plot !== null && shell !== null) {
|
|
22891
|
+
const over = plot.over.getBoundingClientRect();
|
|
22892
|
+
const box = shell.getBoundingClientRect();
|
|
22893
|
+
return {
|
|
22894
|
+
x: over.left - box.left + plot.valToPos(dataX, "x"),
|
|
22895
|
+
y: over.top - box.top + plot.bbox.top + plot.bbox.height / 2
|
|
22896
|
+
};
|
|
22897
|
+
}
|
|
22898
|
+
const width = shell?.getBoundingClientRect().width ?? 0;
|
|
22899
|
+
const height = shell?.getBoundingClientRect().height ?? 0;
|
|
22900
|
+
const span = spec.xMax - spec.xMin;
|
|
22901
|
+
const frac = span === 0 ? 0 : (dataX - spec.xMin) / span;
|
|
22902
|
+
return {
|
|
22903
|
+
x: Math.min(1, Math.max(0, frac)) * width,
|
|
22904
|
+
y: height / 2
|
|
22905
|
+
};
|
|
22906
|
+
}
|
|
22907
|
+
function movePlotCursor(plot, dataX) {
|
|
22908
|
+
if (plot === null) return;
|
|
22909
|
+
if (dataX === null) {
|
|
22910
|
+
plot.setCursor({
|
|
22911
|
+
left: -10,
|
|
22912
|
+
top: -10
|
|
22913
|
+
});
|
|
22914
|
+
return;
|
|
22915
|
+
}
|
|
22916
|
+
plot.setCursor({
|
|
22917
|
+
left: plot.valToPos(dataX, "x"),
|
|
22918
|
+
top: plot.bbox.top + plot.bbox.height / 2
|
|
22919
|
+
});
|
|
22920
|
+
}
|
|
22921
|
+
function ChartTooltipCard({ view }) {
|
|
22922
|
+
const ref = (0, react$1.useRef)(null);
|
|
22923
|
+
(0, react$1.useLayoutEffect)(() => {
|
|
22924
|
+
const el = ref.current;
|
|
22925
|
+
if (el === null) return;
|
|
22926
|
+
const placed = placeChartTooltip({
|
|
22927
|
+
anchorX: view.anchorX,
|
|
22928
|
+
anchorY: view.anchorY,
|
|
22929
|
+
tooltipWidth: el.offsetWidth,
|
|
22930
|
+
tooltipHeight: el.offsetHeight,
|
|
22931
|
+
boundsWidth: view.boundsWidth,
|
|
22932
|
+
boundsHeight: view.boundsHeight
|
|
22933
|
+
});
|
|
22934
|
+
el.style.left = `${String(placed.left)}px`;
|
|
22935
|
+
el.style.top = `${String(placed.top)}px`;
|
|
22936
|
+
}, [view]);
|
|
22937
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
22938
|
+
ref,
|
|
22939
|
+
role: "tooltip",
|
|
22940
|
+
"data-chart-tooltip": "",
|
|
22941
|
+
className: "pointer-events-none absolute z-20 max-w-64 rounded-md border border-border bg-background-elevated px-2 py-1 text-foreground shadow-md",
|
|
22942
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
|
|
22943
|
+
className: "m-0 text-[10px] tabular-nums text-foreground-subtle",
|
|
22944
|
+
children: view.body.heading
|
|
22945
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("ul", {
|
|
22946
|
+
className: "m-0 list-none p-0",
|
|
22947
|
+
children: view.body.lines.map((line) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("li", {
|
|
22948
|
+
className: "flex items-baseline justify-between gap-3 text-xs",
|
|
22949
|
+
children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
22950
|
+
className: "min-w-0 truncate text-foreground-muted",
|
|
22951
|
+
children: line.label
|
|
22952
|
+
}), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
22953
|
+
className: "shrink-0 tabular-nums",
|
|
22954
|
+
children: line.value
|
|
22955
|
+
})]
|
|
22956
|
+
}, line.id))
|
|
22957
|
+
})]
|
|
22958
|
+
});
|
|
22959
|
+
}
|
|
22960
|
+
function ChartTooltipShell({ spec, plotRef, kind, label, inline = false, children }) {
|
|
22961
|
+
const shellRef = (0, react$1.useRef)(null);
|
|
22962
|
+
const specRef = (0, react$1.useRef)(spec);
|
|
22963
|
+
specRef.current = spec;
|
|
22964
|
+
const interactionRef = (0, react$1.useRef)(HIDDEN_INTERACTION);
|
|
22965
|
+
const dataXRef = (0, react$1.useRef)(null);
|
|
22966
|
+
const [view, setView] = (0, react$1.useState)(null);
|
|
22967
|
+
const pinned = view !== null && interactionRef.current.selection.pinned;
|
|
22968
|
+
const commit = (0, react$1.useCallback)((next) => {
|
|
22969
|
+
interactionRef.current = next;
|
|
22970
|
+
const current = specRef.current;
|
|
22971
|
+
const shell = shellRef.current;
|
|
22972
|
+
const dataX = next.selection.visible ? next.selection.dataX : null;
|
|
22973
|
+
const body = current !== null && dataX !== null ? current.resolve(dataX) : null;
|
|
22974
|
+
if (body === null || dataX === null) {
|
|
22975
|
+
setView(null);
|
|
22976
|
+
if (dataXRef.current !== null) {
|
|
22977
|
+
dataXRef.current = null;
|
|
22978
|
+
current?.onDataX?.(null);
|
|
22979
|
+
}
|
|
22980
|
+
return;
|
|
22981
|
+
}
|
|
22982
|
+
setView({
|
|
22983
|
+
body,
|
|
22984
|
+
anchorX: next.selection.anchorX,
|
|
22985
|
+
anchorY: next.selection.anchorY,
|
|
22986
|
+
boundsWidth: shell?.getBoundingClientRect().width ?? 0,
|
|
22987
|
+
boundsHeight: shell?.getBoundingClientRect().height ?? 0
|
|
22988
|
+
});
|
|
22989
|
+
if (dataXRef.current !== dataX) {
|
|
22990
|
+
dataXRef.current = dataX;
|
|
22991
|
+
current?.onDataX?.(dataX);
|
|
22992
|
+
}
|
|
22993
|
+
}, []);
|
|
22994
|
+
(0, react$1.useEffect)(() => {
|
|
22995
|
+
const shell = shellRef.current;
|
|
22996
|
+
if (shell === null) return;
|
|
22997
|
+
const sample = (event, clamp) => {
|
|
22998
|
+
const current = specRef.current;
|
|
22999
|
+
if (current === null) return null;
|
|
23000
|
+
return pointerSample(event.clientX, event.clientY, shell, plotRef.current, current, clamp);
|
|
23001
|
+
};
|
|
23002
|
+
const onDown = (event) => {
|
|
23003
|
+
if (pointerKind(event.pointerType) === "mouse") return;
|
|
23004
|
+
const at = sample(event, false);
|
|
23005
|
+
if (at === null) return;
|
|
23006
|
+
commit(reduceChartInteraction(interactionRef.current, {
|
|
23007
|
+
type: "down",
|
|
23008
|
+
kind: "touch",
|
|
23009
|
+
x: at.anchorX,
|
|
23010
|
+
y: at.anchorY,
|
|
23011
|
+
dataX: at.dataX
|
|
23012
|
+
}));
|
|
23013
|
+
};
|
|
23014
|
+
const onMove = (event) => {
|
|
23015
|
+
const kind = pointerKind(event.pointerType);
|
|
23016
|
+
const scrubbing = interactionRef.current.gesture?.axis === "x";
|
|
23017
|
+
const at = sample(event, kind !== "mouse" && scrubbing);
|
|
23018
|
+
if (at === null) return;
|
|
23019
|
+
if (kind === "mouse") {
|
|
23020
|
+
if (!at.inside || at.dataX === null) {
|
|
23021
|
+
commit(reduceChartInteraction(interactionRef.current, { type: "leave" }));
|
|
23022
|
+
if (!interactionRef.current.selection.visible) movePlotCursor(plotRef.current, null);
|
|
23023
|
+
return;
|
|
23024
|
+
}
|
|
23025
|
+
commit(reduceChartInteraction(interactionRef.current, {
|
|
23026
|
+
type: "hover",
|
|
23027
|
+
dataX: at.dataX,
|
|
23028
|
+
anchorX: at.anchorX,
|
|
23029
|
+
anchorY: at.anchorY
|
|
23030
|
+
}));
|
|
23031
|
+
movePlotCursor(plotRef.current, at.dataX);
|
|
23032
|
+
return;
|
|
23033
|
+
}
|
|
23034
|
+
const next = reduceChartInteraction(interactionRef.current, {
|
|
23035
|
+
type: "move",
|
|
23036
|
+
kind: "touch",
|
|
23037
|
+
x: at.anchorX,
|
|
23038
|
+
y: at.anchorY,
|
|
23039
|
+
dataX: at.dataX
|
|
23040
|
+
});
|
|
23041
|
+
if (next.lockHorizontalScroll) {
|
|
23042
|
+
event.preventDefault();
|
|
23043
|
+
if (event.pointerId !== 0 && !shell.hasPointerCapture(event.pointerId)) shell.setPointerCapture(event.pointerId);
|
|
23044
|
+
}
|
|
23045
|
+
commit(next);
|
|
23046
|
+
if (next.selection.dataX !== null && next.selection.visible) movePlotCursor(plotRef.current, next.selection.dataX);
|
|
23047
|
+
};
|
|
23048
|
+
const onUp = (event) => {
|
|
23049
|
+
if (pointerKind(event.pointerType) === "mouse") return;
|
|
23050
|
+
const at = sample(event, interactionRef.current.gesture?.axis === "x");
|
|
23051
|
+
if (at === null) return;
|
|
23052
|
+
const next = reduceChartInteraction(interactionRef.current, {
|
|
23053
|
+
type: "up",
|
|
23054
|
+
kind: "touch",
|
|
23055
|
+
x: at.anchorX,
|
|
23056
|
+
y: at.anchorY,
|
|
23057
|
+
dataX: at.dataX
|
|
23058
|
+
});
|
|
23059
|
+
commit(next);
|
|
23060
|
+
movePlotCursor(plotRef.current, next.selection.visible ? next.selection.dataX : null);
|
|
23061
|
+
};
|
|
23062
|
+
const onLeave = (event) => {
|
|
23063
|
+
if (pointerKind(event.pointerType) !== "mouse") return;
|
|
23064
|
+
const next = reduceChartInteraction(interactionRef.current, { type: "leave" });
|
|
23065
|
+
commit(next);
|
|
23066
|
+
if (!next.selection.visible) movePlotCursor(plotRef.current, null);
|
|
23067
|
+
};
|
|
23068
|
+
shell.addEventListener("pointerdown", onDown);
|
|
23069
|
+
shell.addEventListener("pointermove", onMove);
|
|
23070
|
+
shell.addEventListener("pointerup", onUp);
|
|
23071
|
+
shell.addEventListener("pointercancel", onUp);
|
|
23072
|
+
shell.addEventListener("pointerleave", onLeave);
|
|
23073
|
+
return () => {
|
|
23074
|
+
shell.removeEventListener("pointerdown", onDown);
|
|
23075
|
+
shell.removeEventListener("pointermove", onMove);
|
|
23076
|
+
shell.removeEventListener("pointerup", onUp);
|
|
23077
|
+
shell.removeEventListener("pointercancel", onUp);
|
|
23078
|
+
shell.removeEventListener("pointerleave", onLeave);
|
|
23079
|
+
};
|
|
23080
|
+
}, [commit, plotRef]);
|
|
23081
|
+
(0, react$1.useEffect)(() => {
|
|
23082
|
+
if (!pinned) return;
|
|
23083
|
+
const onDoc = (event) => {
|
|
23084
|
+
const shell = shellRef.current;
|
|
23085
|
+
if (shell !== null && event.target instanceof Node && shell.contains(event.target)) return;
|
|
23086
|
+
commit(reduceChartInteraction(interactionRef.current, { type: "outside" }));
|
|
23087
|
+
movePlotCursor(plotRef.current, null);
|
|
23088
|
+
};
|
|
23089
|
+
document.addEventListener("pointerdown", onDoc, true);
|
|
23090
|
+
return () => document.removeEventListener("pointerdown", onDoc, true);
|
|
23091
|
+
}, [
|
|
23092
|
+
commit,
|
|
23093
|
+
pinned,
|
|
23094
|
+
plotRef
|
|
23095
|
+
]);
|
|
23096
|
+
const onKeyDown = (event) => {
|
|
23097
|
+
const current = specRef.current;
|
|
23098
|
+
if (current === null) return;
|
|
23099
|
+
if (event.key === "Escape") {
|
|
23100
|
+
event.preventDefault();
|
|
23101
|
+
commit(reduceChartInteraction(interactionRef.current, { type: "hide" }));
|
|
23102
|
+
movePlotCursor(plotRef.current, null);
|
|
23103
|
+
return;
|
|
23104
|
+
}
|
|
23105
|
+
if (event.key !== "ArrowLeft" && event.key !== "ArrowRight") return;
|
|
23106
|
+
event.preventDefault();
|
|
23107
|
+
const from = interactionRef.current.selection.visible ? interactionRef.current.selection.dataX : null;
|
|
23108
|
+
const dataX = stepStop(current.stops, from, event.key === "ArrowRight" ? 1 : -1);
|
|
23109
|
+
if (dataX === null) return;
|
|
23110
|
+
const anchor = anchorForDataX(dataX, shellRef.current, plotRef.current, current);
|
|
23111
|
+
commit(reduceChartInteraction(interactionRef.current, {
|
|
23112
|
+
type: "key",
|
|
23113
|
+
dataX,
|
|
23114
|
+
anchorX: anchor.x,
|
|
23115
|
+
anchorY: anchor.y
|
|
23116
|
+
}));
|
|
23117
|
+
movePlotCursor(plotRef.current, dataX);
|
|
23118
|
+
};
|
|
23119
|
+
const setShell = (el) => {
|
|
23120
|
+
shellRef.current = el;
|
|
23121
|
+
};
|
|
23122
|
+
const className = cn("relative min-w-0 touch-pan-y outline-none focus-visible:ring-2 focus-visible:ring-primary", inline ? "inline-block w-full" : "w-full");
|
|
23123
|
+
const tooltip = view !== null ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChartTooltipCard, { view }) : null;
|
|
23124
|
+
const live = view !== null && typeof document !== "undefined" ? (0, react_dom.createPortal)(/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
23125
|
+
className: "sr-only",
|
|
23126
|
+
"aria-live": "polite",
|
|
23127
|
+
"data-chart-tooltip-live": "",
|
|
23128
|
+
children: view.body.liveText
|
|
23129
|
+
}), document.body) : null;
|
|
23130
|
+
const shared = {
|
|
23131
|
+
className,
|
|
23132
|
+
"data-chart-shell": "",
|
|
23133
|
+
"data-chart-kind": kind,
|
|
23134
|
+
tabIndex: spec === null ? void 0 : 0,
|
|
23135
|
+
"aria-label": label,
|
|
23136
|
+
onKeyDown
|
|
23137
|
+
};
|
|
23138
|
+
return inline ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
|
|
23139
|
+
ref: setShell,
|
|
23140
|
+
...shared,
|
|
23141
|
+
children: [
|
|
23142
|
+
children,
|
|
23143
|
+
tooltip,
|
|
23144
|
+
live
|
|
23145
|
+
]
|
|
23146
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
23147
|
+
ref: setShell,
|
|
23148
|
+
...shared,
|
|
23149
|
+
children: [
|
|
23150
|
+
children,
|
|
23151
|
+
tooltip,
|
|
23152
|
+
live
|
|
23153
|
+
]
|
|
23154
|
+
});
|
|
23155
|
+
}
|
|
22521
23156
|
//#endregion
|
|
22522
23157
|
//#region src/composites/charts/uplot-host.tsx
|
|
22523
23158
|
/**
|
|
@@ -22548,7 +23183,7 @@ function loadUPlot() {
|
|
|
22548
23183
|
if (ctorPromise === null) ctorPromise = canDrawCanvas() ? Promise.resolve().then(() => require("./uPlot.esm-BbxBUnav.cjs")).then((m) => m.default).catch(() => null) : Promise.resolve(null);
|
|
22549
23184
|
return ctorPromise;
|
|
22550
23185
|
}
|
|
22551
|
-
function UPlotHost({ optionsKey, buildOptions, data, height, onInstance, className, inline = false }) {
|
|
23186
|
+
function UPlotHost({ optionsKey, buildOptions, data, height, onInstance, className, inline = false, tooltip = null, kind, label }) {
|
|
22552
23187
|
const hostRef = (0, react$1.useRef)(null);
|
|
22553
23188
|
const plotRef = (0, react$1.useRef)(null);
|
|
22554
23189
|
const [ctor, setCtor] = (0, react$1.useState)(null);
|
|
@@ -22580,6 +23215,8 @@ function UPlotHost({ optionsKey, buildOptions, data, height, onInstance, classNa
|
|
|
22580
23215
|
width,
|
|
22581
23216
|
height
|
|
22582
23217
|
}, latest.current.data, el);
|
|
23218
|
+
u.over.style.touchAction = "pan-y";
|
|
23219
|
+
u.root.style.touchAction = "pan-y";
|
|
22583
23220
|
plotRef.current = u;
|
|
22584
23221
|
latest.current.onInstance?.(u);
|
|
22585
23222
|
return () => {
|
|
@@ -22621,14 +23258,21 @@ function UPlotHost({ optionsKey, buildOptions, data, height, onInstance, classNa
|
|
|
22621
23258
|
const setHost = (el) => {
|
|
22622
23259
|
hostRef.current = el;
|
|
22623
23260
|
};
|
|
22624
|
-
return
|
|
22625
|
-
|
|
22626
|
-
|
|
22627
|
-
|
|
22628
|
-
|
|
22629
|
-
|
|
22630
|
-
|
|
22631
|
-
|
|
23261
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(ChartTooltipShell, {
|
|
23262
|
+
spec: tooltip,
|
|
23263
|
+
plotRef,
|
|
23264
|
+
kind,
|
|
23265
|
+
label,
|
|
23266
|
+
inline,
|
|
23267
|
+
children: inline ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
|
|
23268
|
+
ref: setHost,
|
|
23269
|
+
className: className ?? "block w-full min-w-0",
|
|
23270
|
+
"data-chart-plot": ""
|
|
23271
|
+
}) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
23272
|
+
ref: setHost,
|
|
23273
|
+
className: className ?? "w-full min-w-0",
|
|
23274
|
+
"data-chart-plot": ""
|
|
23275
|
+
})
|
|
22632
23276
|
});
|
|
22633
23277
|
}
|
|
22634
23278
|
//#endregion
|
|
@@ -22643,7 +23287,7 @@ function UPlotHost({ optionsKey, buildOptions, data, height, onInstance, classNa
|
|
|
22643
23287
|
* lives inside another control; there a bucket can be selected (`onSelect`)
|
|
22644
23288
|
* and every bucket is also a real button for keyboard and screen readers.
|
|
22645
23289
|
*/
|
|
22646
|
-
function BarSeries({ title, layers, buckets, bucketMs, range, unit = null, formatValue, height, stacked, showTotal = false, syncKey, pending = false, emptyMessage, actions, footer, windowToggle, compact = false, minBarFraction = 0, selectedIndex = null, onSelect, bucketLabel, bucketTestId, className }) {
|
|
23290
|
+
function BarSeries({ title, layers, buckets, bucketMs, range, unit = null, formatValue, displayPreference, height, stacked, showTotal = false, syncKey, pending = false, emptyMessage, actions, footer, windowToggle, compact = false, minBarFraction = 0, selectedIndex = null, onSelect, bucketLabel, bucketTestId, className }) {
|
|
22647
23291
|
const t = useUiText();
|
|
22648
23292
|
const plotHeight = height ?? (compact ? 24 : 100);
|
|
22649
23293
|
const isStacked = stacked ?? layers.length > 1;
|
|
@@ -22655,6 +23299,37 @@ function BarSeries({ title, layers, buckets, bucketMs, range, unit = null, forma
|
|
|
22655
23299
|
]);
|
|
22656
23300
|
const totals = (0, react$1.useMemo)(() => bucketTotals(buckets, layers.length), [buckets, layers.length]);
|
|
22657
23301
|
const [cursorIdx, setCursorIdx] = (0, react$1.useState)(null);
|
|
23302
|
+
const tooltip = (0, react$1.useMemo)(() => ({
|
|
23303
|
+
xMin: range.from,
|
|
23304
|
+
xMax: range.to,
|
|
23305
|
+
stops: buckets.map((bucket) => bucket.t),
|
|
23306
|
+
resolve: (dataX) => {
|
|
23307
|
+
const index = bucketSelectionIndex(buckets, bucketMs, dataX);
|
|
23308
|
+
if (index === null) return null;
|
|
23309
|
+
const bucket = buckets[index];
|
|
23310
|
+
if (bucket === void 0) return null;
|
|
23311
|
+
return barTooltipBody({
|
|
23312
|
+
rangeLabel: barRangeLabel(bucket.t, bucketMs, range.to - range.from),
|
|
23313
|
+
preference: displayPreference,
|
|
23314
|
+
layers: layers.map((layer, li) => ({
|
|
23315
|
+
id: layer.id,
|
|
23316
|
+
label: layer.label,
|
|
23317
|
+
value: bucket.values[li] ?? null,
|
|
23318
|
+
unit
|
|
23319
|
+
}))
|
|
23320
|
+
});
|
|
23321
|
+
},
|
|
23322
|
+
onDataX: (dataX) => {
|
|
23323
|
+
setCursorIdx(dataX === null ? null : bucketSelectionIndex(buckets, bucketMs, dataX));
|
|
23324
|
+
}
|
|
23325
|
+
}), [
|
|
23326
|
+
buckets,
|
|
23327
|
+
bucketMs,
|
|
23328
|
+
range,
|
|
23329
|
+
layers,
|
|
23330
|
+
unit,
|
|
23331
|
+
displayPreference
|
|
23332
|
+
]);
|
|
22658
23333
|
const live = (0, react$1.useRef)({
|
|
22659
23334
|
buckets,
|
|
22660
23335
|
tops,
|
|
@@ -22817,7 +23492,10 @@ function BarSeries({ title, layers, buckets, bucketMs, range, unit = null, forma
|
|
|
22817
23492
|
optionsKey,
|
|
22818
23493
|
buildOptions,
|
|
22819
23494
|
data,
|
|
22820
|
-
height: plotHeight
|
|
23495
|
+
height: plotHeight,
|
|
23496
|
+
tooltip,
|
|
23497
|
+
kind: "bars",
|
|
23498
|
+
label: title
|
|
22821
23499
|
});
|
|
22822
23500
|
if (compact) return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
|
|
22823
23501
|
className: cn("relative min-w-0", className),
|
|
@@ -30636,8 +31314,21 @@ function AddonActionSelectField({ field, value, values, onChange, disabled, onAc
|
|
|
30636
31314
|
const [options, setOptions] = (0, react$1.useState)(null);
|
|
30637
31315
|
const params = resolveActionParams(field, values);
|
|
30638
31316
|
const missingParam = Object.keys(field.paramsFromForm ?? {}).some((k) => params[k] === void 0);
|
|
31317
|
+
const valueKey = field.mapOption.value;
|
|
31318
|
+
const labelKey = field.mapOption.label;
|
|
31319
|
+
const descKey = field.mapOption.description;
|
|
31320
|
+
const refreshKey = (field.refreshOn ?? Object.values(field.paramsFromForm ?? {})).map((k) => `${k}=${JSON.stringify(values[k])}`).join("|");
|
|
31321
|
+
const onActionRef = (0, react$1.useRef)(onAction);
|
|
31322
|
+
onActionRef.current = onAction;
|
|
31323
|
+
const onChangeRef = (0, react$1.useRef)(onChange);
|
|
31324
|
+
onChangeRef.current = onChange;
|
|
31325
|
+
const paramsRef = (0, react$1.useRef)(params);
|
|
31326
|
+
paramsRef.current = params;
|
|
31327
|
+
const valueRef = (0, react$1.useRef)(value);
|
|
31328
|
+
valueRef.current = value;
|
|
30639
31329
|
(0, react$1.useEffect)(() => {
|
|
30640
|
-
|
|
31330
|
+
const run = onActionRef.current;
|
|
31331
|
+
if (!run || missingParam) {
|
|
30641
31332
|
setOptions(null);
|
|
30642
31333
|
setError(null);
|
|
30643
31334
|
return;
|
|
@@ -30645,7 +31336,8 @@ function AddonActionSelectField({ field, value, values, onChange, disabled, onAc
|
|
|
30645
31336
|
let cancelled = false;
|
|
30646
31337
|
setLoading(true);
|
|
30647
31338
|
setError(null);
|
|
30648
|
-
|
|
31339
|
+
const requested = paramsRef.current;
|
|
31340
|
+
run(field.action, field.key, requested).then((res) => {
|
|
30649
31341
|
if (cancelled) return;
|
|
30650
31342
|
const result = res;
|
|
30651
31343
|
let list = [];
|
|
@@ -30658,9 +31350,10 @@ function AddonActionSelectField({ field, value, values, onChange, disabled, onAc
|
|
|
30658
31350
|
}
|
|
30659
31351
|
}
|
|
30660
31352
|
setOptions(list);
|
|
30661
|
-
|
|
31353
|
+
const current = valueRef.current;
|
|
31354
|
+
if (field.hideWhenFewerThan !== void 0 && list.length === 1 && (typeof current !== "string" || current.length === 0)) {
|
|
30662
31355
|
const only = list[0]?.[valueKey];
|
|
30663
|
-
if (typeof only === "string" && only.length > 0)
|
|
31356
|
+
if (typeof only === "string" && only.length > 0) onChangeRef.current(only);
|
|
30664
31357
|
}
|
|
30665
31358
|
}).catch((err) => {
|
|
30666
31359
|
if (cancelled) return;
|
|
@@ -30673,14 +31366,14 @@ function AddonActionSelectField({ field, value, values, onChange, disabled, onAc
|
|
|
30673
31366
|
cancelled = true;
|
|
30674
31367
|
};
|
|
30675
31368
|
}, [
|
|
30676
|
-
|
|
30677
|
-
|
|
31369
|
+
refreshKey,
|
|
31370
|
+
missingParam,
|
|
31371
|
+
onAction !== void 0,
|
|
30678
31372
|
field.action,
|
|
30679
|
-
field.key
|
|
31373
|
+
field.key,
|
|
31374
|
+
field.hideWhenFewerThan,
|
|
31375
|
+
valueKey
|
|
30680
31376
|
]);
|
|
30681
|
-
const valueKey = field.mapOption.value;
|
|
30682
|
-
const labelKey = field.mapOption.label;
|
|
30683
|
-
const descKey = field.mapOption.description;
|
|
30684
31377
|
if (!onAction && field.hideWhenFewerThan !== void 0) return null;
|
|
30685
31378
|
if (field.hideWhenFewerThan !== void 0 && options !== null && options.length < field.hideWhenFewerThan) return null;
|
|
30686
31379
|
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(FieldWrapper, {
|
|
@@ -66241,11 +66934,34 @@ function RetrainReviewBadge({ kind, approximate = false, className, ...props })
|
|
|
66241
66934
|
});
|
|
66242
66935
|
}
|
|
66243
66936
|
//#endregion
|
|
66937
|
+
//#region src/composites/view-switch.tsx
|
|
66938
|
+
function ViewSwitch({ options, value, onChange, ariaLabel, className, testId }) {
|
|
66939
|
+
if (options.length < 2) return null;
|
|
66940
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
|
|
66941
|
+
role: "radiogroup",
|
|
66942
|
+
"aria-label": ariaLabel,
|
|
66943
|
+
"data-testid": testId,
|
|
66944
|
+
className: cn("inline-flex shrink-0 items-center gap-0.5 rounded-md border border-border bg-background p-0.5", className),
|
|
66945
|
+
children: options.map((option) => {
|
|
66946
|
+
const active = value === option.id;
|
|
66947
|
+
const Icon = option.icon;
|
|
66948
|
+
return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("button", {
|
|
66949
|
+
type: "button",
|
|
66950
|
+
role: "radio",
|
|
66951
|
+
"aria-checked": active,
|
|
66952
|
+
"data-testid": testId === void 0 ? void 0 : `${testId}-${option.id}`,
|
|
66953
|
+
onClick: () => onChange(option.id),
|
|
66954
|
+
className: cn("inline-flex h-7 items-center gap-1 whitespace-nowrap rounded px-2 text-xs font-medium transition-colors", active ? "bg-primary/10 text-primary" : "text-foreground-subtle hover:bg-surface-hover hover:text-foreground"),
|
|
66955
|
+
children: [Icon !== void 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
|
|
66956
|
+
className: "h-3.5 w-3.5",
|
|
66957
|
+
"aria-hidden": true
|
|
66958
|
+
}) : null, option.label]
|
|
66959
|
+
}, option.id);
|
|
66960
|
+
})
|
|
66961
|
+
});
|
|
66962
|
+
}
|
|
66963
|
+
//#endregion
|
|
66244
66964
|
//#region src/composites/charts/time-series-model.ts
|
|
66245
|
-
/**
|
|
66246
|
-
* The pure half of `TimeSeriesChart`: which series go on which value axis,
|
|
66247
|
-
* the aligned data uPlot draws, and what the readout says at an index.
|
|
66248
|
-
*/
|
|
66249
66965
|
/** The unit key a series lives on (`''` for no / unknown unit). */
|
|
66250
66966
|
function unitKey(unit) {
|
|
66251
66967
|
return unit ?? "";
|
|
@@ -66313,7 +67029,39 @@ function latestValues(model) {
|
|
|
66313
67029
|
* for the cross-device History page, where the reader chose the series.
|
|
66314
67030
|
*/
|
|
66315
67031
|
var DEFAULT_HEIGHT = 120;
|
|
66316
|
-
function
|
|
67032
|
+
function seriesSpan(range, xs) {
|
|
67033
|
+
if (range !== void 0) return range.to - range.from;
|
|
67034
|
+
if (xs.length < 2) return 0;
|
|
67035
|
+
return (xs[xs.length - 1] ?? 0) - (xs[0] ?? 0);
|
|
67036
|
+
}
|
|
67037
|
+
function seriesTooltip(input) {
|
|
67038
|
+
const span = seriesSpan(input.range, input.model.xs);
|
|
67039
|
+
return {
|
|
67040
|
+
xMin: input.range?.from ?? input.model.xs[0] ?? 0,
|
|
67041
|
+
xMax: input.range?.to ?? input.model.xs.at(-1) ?? 0,
|
|
67042
|
+
stops: input.model.xs,
|
|
67043
|
+
resolve: (dataX) => {
|
|
67044
|
+
const index = nearestPointIndex(input.model.xs, dataX);
|
|
67045
|
+
if (index === null) return null;
|
|
67046
|
+
const at = input.model.xs[index];
|
|
67047
|
+
if (at === void 0) return null;
|
|
67048
|
+
const values = readoutAt(input.model, index);
|
|
67049
|
+
return lineTooltipBody({
|
|
67050
|
+
timeLabel: formatCursorTime(at, span),
|
|
67051
|
+
preference: input.preference,
|
|
67052
|
+
series: input.series.map((sample, i) => ({
|
|
67053
|
+
id: sample.id,
|
|
67054
|
+
label: sample.label,
|
|
67055
|
+
value: values[i] ?? null,
|
|
67056
|
+
quantity: sample.quantity,
|
|
67057
|
+
unit: sample.unit ?? null
|
|
67058
|
+
}))
|
|
67059
|
+
});
|
|
67060
|
+
},
|
|
67061
|
+
onDataX: input.onDataX
|
|
67062
|
+
};
|
|
67063
|
+
}
|
|
67064
|
+
function TimeSeriesChart({ title, series, range, height = DEFAULT_HEIGHT, stacked = false, area = false, syncKey, formatValue, displayPreference, axisFor, emptyMessage, pending = false, actions, footer, windowToggle, className }) {
|
|
66317
67065
|
const t = useUiText();
|
|
66318
67066
|
const model = (0, react$1.useMemo)(() => buildTimeSeriesModel(series, stacked), [series, stacked]);
|
|
66319
67067
|
const [cursorIdx, setCursorIdx] = (0, react$1.useState)(null);
|
|
@@ -66456,6 +67204,20 @@ function TimeSeriesChart({ title, series, range, height = DEFAULT_HEIGHT, stacke
|
|
|
66456
67204
|
fmt,
|
|
66457
67205
|
specFor
|
|
66458
67206
|
]);
|
|
67207
|
+
const tooltip = (0, react$1.useMemo)(() => seriesTooltip({
|
|
67208
|
+
model,
|
|
67209
|
+
series,
|
|
67210
|
+
range,
|
|
67211
|
+
preference: displayPreference,
|
|
67212
|
+
onDataX: (dataX) => {
|
|
67213
|
+
setCursorIdx(dataX === null ? null : nearestPointIndex(model.xs, dataX));
|
|
67214
|
+
}
|
|
67215
|
+
}), [
|
|
67216
|
+
model,
|
|
67217
|
+
series,
|
|
67218
|
+
range,
|
|
67219
|
+
displayPreference
|
|
67220
|
+
]);
|
|
66459
67221
|
const shown = readoutAt(model, cursorIdx);
|
|
66460
67222
|
const legend = series.map((s, i) => {
|
|
66461
67223
|
const style = chartSeriesStyle(s.colorIndex);
|
|
@@ -66517,7 +67279,10 @@ function TimeSeriesChart({ title, series, range, height = DEFAULT_HEIGHT, stacke
|
|
|
66517
67279
|
optionsKey,
|
|
66518
67280
|
buildOptions,
|
|
66519
67281
|
data,
|
|
66520
|
-
height
|
|
67282
|
+
height,
|
|
67283
|
+
tooltip,
|
|
67284
|
+
kind: "lines",
|
|
67285
|
+
label: title
|
|
66521
67286
|
})
|
|
66522
67287
|
});
|
|
66523
67288
|
}
|
|
@@ -66536,6 +67301,33 @@ function TimeSeriesChart({ title, series, range, height = DEFAULT_HEIGHT, stacke
|
|
|
66536
67301
|
/** Pixel height of one row band — matches the `h-5` label cells. */
|
|
66537
67302
|
var ROW_PX = 20;
|
|
66538
67303
|
var AXIS_PX = 22;
|
|
67304
|
+
function stateStops(rows, from, to) {
|
|
67305
|
+
const found = new Set([from]);
|
|
67306
|
+
for (const row of rows) for (const segment of row.segments) if (segment.from >= from && segment.from < to) found.add(segment.from);
|
|
67307
|
+
return [...found].sort((a, b) => a - b);
|
|
67308
|
+
}
|
|
67309
|
+
function stateTooltip(rows, range) {
|
|
67310
|
+
const span = range.to - range.from;
|
|
67311
|
+
return {
|
|
67312
|
+
xMin: range.from,
|
|
67313
|
+
xMax: range.to,
|
|
67314
|
+
stops: stateStops(rows, range.from, range.to),
|
|
67315
|
+
resolve: (dataX) => stateTooltipBody({
|
|
67316
|
+
timeLabel: formatCursorTime(dataX, span),
|
|
67317
|
+
rows: rows.map((row) => {
|
|
67318
|
+
const hit = stateSpanAt(row.segments, dataX);
|
|
67319
|
+
const state = hit?.state ?? null;
|
|
67320
|
+
const stateLabel = state === null ? null : row.stateLabel?.(state) ?? state;
|
|
67321
|
+
return {
|
|
67322
|
+
id: row.id,
|
|
67323
|
+
label: row.label,
|
|
67324
|
+
stateLabel,
|
|
67325
|
+
spanMs: state === null ? null : hit?.spanMs ?? null
|
|
67326
|
+
};
|
|
67327
|
+
})
|
|
67328
|
+
})
|
|
67329
|
+
};
|
|
67330
|
+
}
|
|
66539
67331
|
/** The colour role of a state on a row: a categorical slot, or the recessive "off". */
|
|
66540
67332
|
function stateStyle(row, state) {
|
|
66541
67333
|
const base = row.colorIndex ?? 0;
|
|
@@ -66610,6 +67402,10 @@ function StateTimeline({ title, rows, range, syncKey, pending = false, emptyMess
|
|
|
66610
67402
|
}]
|
|
66611
67403
|
}
|
|
66612
67404
|
}), [optionsKey]);
|
|
67405
|
+
const tooltip = (0, react$1.useMemo)(() => ({
|
|
67406
|
+
...stateTooltip(rows, range),
|
|
67407
|
+
onDataX: (dataX) => setCursorT(dataX)
|
|
67408
|
+
}), [rows, range]);
|
|
66613
67409
|
const durations = (0, react$1.useMemo)(() => rows.map((r) => r.totals ?? timeInState(r.segments)), [rows]);
|
|
66614
67410
|
const summary = rows.map((row, i) => {
|
|
66615
67411
|
const parts = row.states.map((s) => {
|
|
@@ -66663,7 +67459,10 @@ function StateTimeline({ title, rows, range, syncKey, pending = false, emptyMess
|
|
|
66663
67459
|
optionsKey,
|
|
66664
67460
|
buildOptions,
|
|
66665
67461
|
data,
|
|
66666
|
-
height: rows.length * ROW_PX + AXIS_PX
|
|
67462
|
+
height: rows.length * ROW_PX + AXIS_PX,
|
|
67463
|
+
tooltip,
|
|
67464
|
+
kind: "states",
|
|
67465
|
+
label: title
|
|
66667
67466
|
})
|
|
66668
67467
|
})]
|
|
66669
67468
|
}),
|
|
@@ -66708,7 +67507,38 @@ function StateTimeline({ title, rows, range, syncKey, pending = false, emptyMess
|
|
|
66708
67507
|
* row (a training job's mAP per epoch). The x is whatever the caller orders
|
|
66709
67508
|
* by (an epoch, a time); the value next to it names what the line is.
|
|
66710
67509
|
*/
|
|
66711
|
-
function Sparkline({ points, ariaLabel, token = "chart-series-a", height = 32, widthClassName = "w-40", zeroBased = true, className }) {
|
|
67510
|
+
function Sparkline({ points, ariaLabel, token = "chart-series-a", height = 32, widthClassName = "w-40", zeroBased = true, unit = null, quantity, displayPreference, className }) {
|
|
67511
|
+
const tooltip = (0, react$1.useMemo)(() => {
|
|
67512
|
+
const xs = points.map((point) => point.x);
|
|
67513
|
+
return {
|
|
67514
|
+
xMin: xs[0] ?? 0,
|
|
67515
|
+
xMax: xs.at(-1) ?? 0,
|
|
67516
|
+
stops: xs,
|
|
67517
|
+
resolve: (dataX) => {
|
|
67518
|
+
const index = nearestPointIndex(xs, dataX);
|
|
67519
|
+
if (index === null) return null;
|
|
67520
|
+
const point = points[index];
|
|
67521
|
+
if (point === void 0) return null;
|
|
67522
|
+
return lineTooltipBody({
|
|
67523
|
+
timeLabel: String(point.x),
|
|
67524
|
+
preference: displayPreference,
|
|
67525
|
+
series: [{
|
|
67526
|
+
id: "value",
|
|
67527
|
+
label: ariaLabel,
|
|
67528
|
+
value: point.v,
|
|
67529
|
+
unit,
|
|
67530
|
+
quantity
|
|
67531
|
+
}]
|
|
67532
|
+
});
|
|
67533
|
+
}
|
|
67534
|
+
};
|
|
67535
|
+
}, [
|
|
67536
|
+
points,
|
|
67537
|
+
ariaLabel,
|
|
67538
|
+
unit,
|
|
67539
|
+
quantity,
|
|
67540
|
+
displayPreference
|
|
67541
|
+
]);
|
|
66712
67542
|
const data = (0, react$1.useMemo)(() => [points.map((p) => p.x), points.map((p) => p.v)], [points]);
|
|
66713
67543
|
const buildOptions = (0, react$1.useCallback)((_w, lib) => ({
|
|
66714
67544
|
legend: { show: false },
|
|
@@ -66744,7 +67574,10 @@ function Sparkline({ points, ariaLabel, token = "chart-series-a", height = 32, w
|
|
|
66744
67574
|
buildOptions,
|
|
66745
67575
|
data,
|
|
66746
67576
|
height,
|
|
66747
|
-
inline: true
|
|
67577
|
+
inline: true,
|
|
67578
|
+
tooltip,
|
|
67579
|
+
kind: "spark",
|
|
67580
|
+
label: ariaLabel
|
|
66748
67581
|
})
|
|
66749
67582
|
});
|
|
66750
67583
|
}
|
|
@@ -69420,6 +70253,7 @@ exports.ValveHeroCard = ValveHeroCard;
|
|
|
69420
70253
|
exports.ValveInlineControl = ValveInlineControl;
|
|
69421
70254
|
exports.VersionBadge = VersionBadge;
|
|
69422
70255
|
exports.Vibrate = Vibrate;
|
|
70256
|
+
exports.ViewSwitch = ViewSwitch;
|
|
69423
70257
|
exports.VodPlaybackProvider = VodPlaybackProvider;
|
|
69424
70258
|
exports.Volume2 = Volume2;
|
|
69425
70259
|
exports.VolumeX = VolumeX;
|