@liiift-studio/sanity-visitor-insights 0.69.0 → 0.70.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -290,6 +290,15 @@ function withAlpha(hex, alpha) {
290
290
  return `rgba(${r}, ${g}, ${b}, ${alpha})`;
291
291
  }
292
292
  var COMPARISON = "#8368D4";
293
+ var COMPARISON_TEXT = `var(--vi-comparison, ${COMPARISON})`;
294
+ var COMPARISON_ON_LIGHT = "#6A4FC4";
295
+ var COMPARISON_ON_DARK = "#A594E8";
296
+ var COMPARISON_STYLE = `
297
+ :root { --vi-comparison: ${COMPARISON}; }
298
+ @supports (color: light-dark(#000, #fff)) {
299
+ :root { --vi-comparison: light-dark(${COMPARISON_ON_LIGHT}, ${COMPARISON_ON_DARK}); }
300
+ }
301
+ `.trim();
293
302
  var MARKS = {
294
303
  "chart.line": { key: "vercel", alpha: 1, role: "data", note: "A source series in the timeline" },
295
304
  "chart.lossyLine": { key: "ga4Pageviews", alpha: 1, role: "data", note: "The lossier source, dashed" },
@@ -455,11 +464,11 @@ function deltaStyle() {
455
464
  fontFamily: "inherit",
456
465
  fontSize: "0.8em",
457
466
  fontWeight: 500,
458
- color: COMPARISON,
467
+ color: COMPARISON_TEXT,
459
468
  whiteSpace: "nowrap"
460
469
  };
461
470
  }
462
- var baselineStyle = { color: COMPARISON, fontWeight: 400 };
471
+ var baselineStyle = { color: COMPARISON_TEXT, fontWeight: 400 };
463
472
  var HOVER_GUTTER = 14;
464
473
  var HOVER_CARD_MAX = 260;
465
474
  function HoverCard({
@@ -701,7 +710,7 @@ function FunnelChart({ stages, measurement }) {
701
710
  const active = activeKey === stage.key;
702
711
  const delta = previous ? previous.value - stage.value : 0;
703
712
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: funnelItem, children: [
704
- previous && gapLabel(delta, measurement, entry) !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: funnelGapStyle(measurement === "sequence"), children: [
713
+ previous && gapLabel(delta, measurement, entry) !== "" && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: funnelGapStyle(measurement === "sequence" && delta > 0 && entry > 0), children: [
705
714
  measurement === "sequence" && delta > 0 && entry > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-hidden": "true", style: { ...barTrack, height: 6, flex: 1 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
706
715
  "div",
707
716
  {
@@ -1002,6 +1011,7 @@ function SurvivalLines({
1002
1011
  minDenominator = MIN_RATE_DENOMINATOR
1003
1012
  }) {
1004
1013
  const [active, setActive] = import_react2.default.useState(null);
1014
+ const [steppedByKey, setSteppedByKey] = import_react2.default.useState(false);
1005
1015
  if (segments.length < 2 || stepLabels.length < 2) return null;
1006
1016
  const width = 320;
1007
1017
  const height = 132;
@@ -1112,18 +1122,23 @@ function SurvivalLines({
1112
1122
  "button",
1113
1123
  {
1114
1124
  type: "button",
1115
- style: survivalTick(i === active),
1125
+ style: survivalTick(i === active, i, stepLabels.length),
1116
1126
  "aria-pressed": i === active,
1117
- onMouseEnter: () => setActive(i),
1127
+ onMouseEnter: () => {
1128
+ setSteppedByKey(false);
1129
+ setActive(i);
1130
+ },
1118
1131
  onMouseLeave: () => setActive(null),
1119
- onFocus: () => setActive(i),
1132
+ onFocus: () => {
1133
+ setSteppedByKey(true);
1134
+ setActive(i);
1135
+ },
1120
1136
  onBlur: () => setActive(null),
1121
- onClick: () => setActive(i === active ? null : i),
1122
1137
  children: label
1123
1138
  },
1124
1139
  label
1125
1140
  )) }),
1126
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: survivalLegend, "aria-live": "polite", children: lines.map(({ segment, shares, colour }) => {
1141
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: survivalLegend, "aria-live": steppedByKey ? "polite" : "off", children: lines.map(({ segment, shares, colour }) => {
1127
1142
  const share = active === null ? null : shares[active];
1128
1143
  return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Text, { size: 0, children: [
1129
1144
  /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { style: { ...hoverSwatch, background: colour, display: "inline-block", marginRight: 6 } }),
@@ -1457,13 +1472,18 @@ var shiftNow = {
1457
1472
  var survivalLegend = { display: "flex", gap: 14, flexWrap: "wrap" };
1458
1473
  var survivalFrame = { paddingLeft: 34, position: "relative" };
1459
1474
  var survivalAxis = {
1460
- display: "flex",
1461
- justifyContent: "space-between",
1462
- gap: 4,
1463
- paddingLeft: 34
1475
+ position: "relative",
1476
+ // Reserves the row's height, since the ticks inside it are positioned.
1477
+ height: 30,
1478
+ marginLeft: 34
1464
1479
  };
1465
- function survivalTick(activeTick) {
1480
+ function survivalTick(activeTick, index, count) {
1481
+ const at = count > 1 ? index / (count - 1) * 100 : 50;
1466
1482
  return {
1483
+ position: "absolute",
1484
+ left: `${at}%`,
1485
+ // The ends align to the plot edge; everything between is centred on its vertex.
1486
+ transform: index === 0 ? "none" : index === count - 1 ? "translateX(-100%)" : "translateX(-50%)",
1467
1487
  appearance: "none",
1468
1488
  background: "transparent",
1469
1489
  border: "none",
@@ -1473,11 +1493,10 @@ function survivalTick(activeTick) {
1473
1493
  font: "inherit",
1474
1494
  fontSize: "0.72em",
1475
1495
  fontWeight: activeTick ? 600 : 400,
1476
- padding: "6px 2px",
1496
+ padding: "6px 4px",
1477
1497
  minHeight: 24,
1478
1498
  cursor: "pointer",
1479
- textAlign: "center",
1480
- flex: "1 1 0"
1499
+ whiteSpace: "nowrap"
1481
1500
  };
1482
1501
  }
1483
1502
  function survivalYLabel(level) {
@@ -2206,7 +2225,7 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
2206
2225
  }
2207
2226
  )
2208
2227
  ] }),
2209
- series.some((row) => row.comparison) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2228
+ series.some((row) => (row.comparison?.length ?? 0) > 1) && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2210
2229
  LegendKey,
2211
2230
  {
2212
2231
  swatch: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: legendDash(mark("chart.ghost")) }),
@@ -2216,7 +2235,7 @@ function CrossSourceTimeline({ series, markers = [], currency, onBrush }) {
2216
2235
  markers.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
2217
2236
  LegendKey,
2218
2237
  {
2219
- swatch: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: legendRule(mark("chart.grid")) }),
2238
+ swatch: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { style: legendMarker }),
2220
2239
  label: "Dashed rules with a dot mark campaign sends"
2221
2240
  }
2222
2241
  ),
@@ -2251,14 +2270,15 @@ function legendBlock(fill, edge) {
2251
2270
  borderTop: `1.5px dashed ${edge}`
2252
2271
  };
2253
2272
  }
2254
- function legendRule(colour) {
2255
- return {
2256
- display: "inline-block",
2257
- width: 0,
2258
- height: 12,
2259
- borderLeft: `1px dashed ${colour}`
2260
- };
2261
- }
2273
+ var legendMarker = {
2274
+ display: "inline-block",
2275
+ position: "relative",
2276
+ width: 0,
2277
+ height: 12,
2278
+ borderLeft: "1px dashed currentColor",
2279
+ opacity: 0.7,
2280
+ boxShadow: "-2px -6px 0 0 currentColor, -1px -6px 0 0 currentColor"
2281
+ };
2262
2282
  function shortfallKey(series) {
2263
2283
  const row = series.find((r) => r.shortfall);
2264
2284
  return colorFor(row?.shortfall?.source ?? "GA4", row?.unit ?? "count", row?.shortfall?.color);
@@ -2394,7 +2414,7 @@ function OverviewPanel({ data, previous, onBrush }) {
2394
2414
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: metricOr(data.vercelPageviews, OLDER_ROUTE), label: "Pageviews" }),
2395
2415
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Delta, { current: metricSortValue(data.vercelPageviews), previous: metricSortValue(previous?.vercelPageviews) })
2396
2416
  ] }),
2397
- data.vercelPageviews?.status === "ok" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: "Pageviews, from Vercel\u2019s own counter." })
2417
+ data.vercelPageviews?.status !== "unavailable" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: "Pageviews, from Vercel\u2019s own counter." })
2398
2418
  ] }) }),
2399
2419
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { padding: 3, radius: 2, tone: "transparent", border: true, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
2400
2420
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Label, { size: 1, muted: true, children: "Mailing list, total" }),
@@ -2413,7 +2433,7 @@ function OverviewPanel({ data, previous, onBrush }) {
2413
2433
  ] });
2414
2434
  })()
2415
2435
  ] }),
2416
- data.audience?.status === "ok" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: "Everyone subscribed today, not just this period. Mailchimp\u2019s own count, so it is not consent-gated or blockable." })
2436
+ data.audience?.status !== "unavailable" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: "Everyone subscribed today, not just this period. Mailchimp\u2019s own count, so it is not consent-gated or blockable." })
2417
2437
  ] }) })
2418
2438
  ] }),
2419
2439
  /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: cardGrid, children: [
@@ -2448,12 +2468,12 @@ function OverviewPanel({ data, previous, onBrush }) {
2448
2468
  unavailable: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(MetricFigure, { metric: visitorsPerOrder(data), label: "Visitors per order" })
2449
2469
  }
2450
2470
  ),
2451
- data.vercelVisitors?.status === "ok" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: "Vercel\u2019s visitor count. A ceiling \u2014 some are bots or the same person twice." })
2471
+ data.vercelVisitors?.status !== "unavailable" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: "Vercel\u2019s visitor count. A ceiling \u2014 some are bots or the same person twice." })
2452
2472
  ] }) })
2453
2473
  ] }),
2454
2474
  (data.crossSource?.length ?? 0) >= 3 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
2455
2475
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SectionTitle, { title: "Everything, on one time axis" }),
2456
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: "Each row has its own scale. Hover a day to see what each source saw of it." }),
2476
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: "Each row has its own scale, so the rows cannot be compared by height \u2014 only by shape." }),
2457
2477
  /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
2458
2478
  CrossSourceTimeline,
2459
2479
  {
@@ -2760,7 +2780,6 @@ function Verdict({ data, previous }) {
2760
2780
  say("Revenue", data.revenue, previous?.revenue, "money");
2761
2781
  say("Orders", data.orders, previous?.orders, "count");
2762
2782
  say("Traffic", data.vercelPageviews, previous?.vercelPageviews, "count");
2763
- const sends = data.campaigns?.length ?? 0;
2764
2783
  const shortfall = data.shortfallRatio;
2765
2784
  const seeing = shortfall !== null && shortfall !== void 0 ? `GA4 is seeing ${formatPercent(1 - shortfall, 0)} of your traffic` : null;
2766
2785
  const broken = shortfall === null || shortfall === void 0 ? data.capture?.discrepancy ? "measurement disagrees between sources" : null : shortfall > 0.6 ? `${seeing} \u2014 treat its figures as broken` : shortfall > 0.35 ? `${seeing} \u2014 its figures are undercounts, not what happened` : data.capture?.discrepancy ? "measurement disagrees between sources" : null;
@@ -3602,12 +3621,15 @@ function basisButton(selected) {
3602
3621
  background: selected ? withAlpha(COMPARISON, 0.12) : "transparent",
3603
3622
  border: `1px ${selected ? "solid" : "dashed"} ${selected ? COMPARISON : "var(--card-border-color, rgba(128,128,128,0.3))"}`,
3604
3623
  borderRadius: 999,
3605
- color: selected ? COMPARISON : "inherit",
3624
+ color: selected ? COMPARISON_TEXT : "inherit",
3606
3625
  opacity: selected ? 1 : 0.7,
3607
3626
  font: "inherit",
3608
3627
  fontSize: "0.85em",
3609
3628
  fontWeight: selected ? 600 : 400,
3610
3629
  padding: "5px 12px",
3630
+ // Pinned rather than left to emerge from padding and root font size, which is how a target
3631
+ // silently drops under the 24px minimum when someone adjusts either.
3632
+ minHeight: 24,
3611
3633
  cursor: "pointer",
3612
3634
  whiteSpace: "nowrap"
3613
3635
  };
@@ -3846,7 +3868,7 @@ function ReadyReport({
3846
3868
  "These figures are the last ones that loaded. Refreshing them failed: ",
3847
3869
  revalidationError
3848
3870
  ] }) }),
3849
- envelope.comparison && COMPARED_TABS.includes(tabId) && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Text, { size: 0, style: { color: COMPARISON }, children: [
3871
+ envelope.comparison && COMPARED_TABS.includes(tabId) && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Text, { size: 0, style: { color: COMPARISON_TEXT }, children: [
3850
3872
  "Changes are against ",
3851
3873
  envelope.comparison.range.start,
3852
3874
  " to ",
@@ -3889,8 +3911,8 @@ function BasisSelector({
3889
3911
  value,
3890
3912
  onChange
3891
3913
  }) {
3892
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: controlRow, role: "radiogroup", "aria-label": "Comparison basis", children: [
3893
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sanity_ui_compat4.Text, { size: 0, style: { color: COMPARISON }, children: "Compared with" }),
3914
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: controlRow, role: "group", "aria-label": "Comparison basis", children: [
3915
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sanity_ui_compat4.Text, { size: 0, style: { color: COMPARISON_TEXT }, children: "Compared with" }),
3894
3916
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
3895
3917
  "button",
3896
3918
  {
@@ -4037,90 +4059,93 @@ function VisitorInsightsTool(props) {
4037
4059
  // Named for assistive technology even though the name is not printed: a Studio user with
4038
4060
  // several tabs open hears which site's figures these are, without the heading repeating
4039
4061
  // what the Studio navigation already says.
4040
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sanity_ui_compat4.Container, { width: 4, padding: 4, as: "section", "aria-label": siteLabel ? `Visitor insights for ${siteLabel}` : "Visitor insights", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Stack, { space: 5, children: [
4041
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Flex, { align: "flex-start", justify: "space-between", gap: 4, wrap: "wrap", children: [
4042
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sanity_ui_compat4.Heading, { size: 2, children: "Visitor insights" }),
4043
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Stack, { space: 2, children: [
4044
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RangeSelector, { value: range, custom, onChange: setRange, onCustomChange: setCustom }),
4045
- COMPARED_TABS.includes(activePanel) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BasisSelector, { value: compare, onChange: setCompare })
4046
- ] })
4047
- ] }),
4048
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PanelTabs, { value: activePanel, onChange: setActivePanel }),
4049
- range === "custom" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: contextRow, children: [
4050
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Text, { size: 0, muted: true, children: [
4051
- "Showing ",
4052
- custom.start,
4053
- " to ",
4054
- custom.end,
4055
- " (",
4056
- daysBetween(custom.start, custom.end),
4057
- " days).",
4058
- daysBetween(custom.start, custom.end) < 14 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: " Short windows lose GA4\u2019s low-count rows, so the source and typeface lists will be shorter than reality." })
4062
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Container, { width: 4, padding: 4, as: "section", "aria-label": siteLabel ? `Visitor insights for ${siteLabel}` : "Visitor insights", children: [
4063
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("style", { children: COMPARISON_STYLE }),
4064
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Stack, { space: 5, children: [
4065
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Flex, { align: "flex-start", justify: "space-between", gap: 4, wrap: "wrap", children: [
4066
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sanity_ui_compat4.Heading, { size: 2, children: "Visitor insights" }),
4067
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Stack, { space: 2, children: [
4068
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(RangeSelector, { value: range, custom, onChange: setRange, onCustomChange: setCustom }),
4069
+ COMPARED_TABS.includes(activePanel) && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(BasisSelector, { value: compare, onChange: setCompare })
4070
+ ] })
4071
+ ] }),
4072
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PanelTabs, { value: activePanel, onChange: setActivePanel }),
4073
+ range === "custom" && /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { style: contextRow, children: [
4074
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_sanity_ui_compat4.Text, { size: 0, muted: true, children: [
4075
+ "Showing ",
4076
+ custom.start,
4077
+ " to ",
4078
+ custom.end,
4079
+ " (",
4080
+ daysBetween(custom.start, custom.end),
4081
+ " days).",
4082
+ daysBetween(custom.start, custom.end) < 14 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_jsx_runtime4.Fragment, { children: " Short windows lose GA4\u2019s low-count rows, so the source and typeface lists will be shorter than reality." })
4083
+ ] }),
4084
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4085
+ "button",
4086
+ {
4087
+ type: "button",
4088
+ style: inlineClear,
4089
+ "aria-label": "Shift the window back by its own length",
4090
+ onClick: () => {
4091
+ const span = daysBetween(custom.start, custom.end);
4092
+ setCustom({ start: shiftDays(custom.start, -span), end: shiftDays(custom.end, -span) });
4093
+ },
4094
+ children: "\u2190 earlier"
4095
+ }
4096
+ ),
4097
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4098
+ "button",
4099
+ {
4100
+ type: "button",
4101
+ "aria-label": "Shift the window forward by its own length",
4102
+ disabled: atPresent,
4103
+ style: atPresent ? { ...inlineClear, opacity: 0.35, cursor: "not-allowed" } : inlineClear,
4104
+ title: atPresent ? "This window already ends today" : void 0,
4105
+ onClick: () => {
4106
+ const span = daysBetween(custom.start, custom.end);
4107
+ const today = isoDaysAgo(0);
4108
+ const room = daysBetween(custom.end, today) - 1;
4109
+ const allowed = Math.min(span, room);
4110
+ if (allowed <= 0) return;
4111
+ setCustom({ start: shiftDays(custom.start, allowed), end: shiftDays(custom.end, allowed) });
4112
+ },
4113
+ children: "later \u2192"
4114
+ }
4115
+ ),
4116
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("button", { type: "button", style: inlineClear, onClick: () => setRange(rangeBeforeBrush), children: [
4117
+ "Back to the ",
4118
+ RANGES.find((r) => r.key === rangeBeforeBrush)?.label.toLowerCase() ?? "week"
4119
+ ] })
4059
4120
  ] }),
4060
4121
  /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4061
- "button",
4062
- {
4063
- type: "button",
4064
- style: inlineClear,
4065
- "aria-label": "Shift the window back by its own length",
4066
- onClick: () => {
4067
- const span = daysBetween(custom.start, custom.end);
4068
- setCustom({ start: shiftDays(custom.start, -span), end: shiftDays(custom.end, -span) });
4069
- },
4070
- children: "\u2190 earlier"
4071
- }
4072
- ),
4073
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4074
- "button",
4122
+ import_sanity_ui_compat4.Box,
4075
4123
  {
4076
- type: "button",
4077
- "aria-label": "Shift the window forward by its own length",
4078
- disabled: atPresent,
4079
- style: atPresent ? { ...inlineClear, opacity: 0.35, cursor: "not-allowed" } : inlineClear,
4080
- title: atPresent ? "This window already ends today" : void 0,
4081
- onClick: () => {
4082
- const span = daysBetween(custom.start, custom.end);
4083
- const today = isoDaysAgo(0);
4084
- const room = daysBetween(custom.end, today) - 1;
4085
- const allowed = Math.min(span, room);
4086
- if (allowed <= 0) return;
4087
- setCustom({ start: shiftDays(custom.start, allowed), end: shiftDays(custom.end, allowed) });
4088
- },
4089
- children: "later \u2192"
4090
- }
4091
- ),
4092
- /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("button", { type: "button", style: inlineClear, onClick: () => setRange(rangeBeforeBrush), children: [
4093
- "Back to the ",
4094
- RANGES.find((r) => r.key === rangeBeforeBrush)?.label.toLowerCase() ?? "week"
4095
- ] })
4096
- ] }),
4097
- /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4098
- import_sanity_ui_compat4.Box,
4099
- {
4100
- role: "tabpanel",
4101
- id: `panel-${activePanel}`,
4102
- "aria-labelledby": `tab-${activePanel}`,
4103
- tabIndex: 0,
4104
- style: { position: "relative" },
4105
- children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sanity_ui_compat4.Stack, { space: 4, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PanelBoundary, { resetKey: `${activePanel}|${range}|${custom.start}|${custom.end}`, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4106
- ReportPanel,
4107
- {
4108
- tabId: active?.id ?? "overview",
4109
- report: active?.report ?? "measurement-health",
4110
- apiBaseUrl,
4111
- range,
4112
- custom,
4113
- compare,
4114
- onBrush: (start, end) => {
4115
- if (range !== "custom") setRangeBeforeBrush(range);
4116
- setCustom({ start, end });
4117
- setRange("custom");
4124
+ role: "tabpanel",
4125
+ id: `panel-${activePanel}`,
4126
+ "aria-labelledby": `tab-${activePanel}`,
4127
+ tabIndex: 0,
4128
+ style: { position: "relative" },
4129
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_sanity_ui_compat4.Stack, { space: 4, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(PanelBoundary, { resetKey: `${activePanel}|${range}|${custom.start}|${custom.end}`, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
4130
+ ReportPanel,
4131
+ {
4132
+ tabId: active?.id ?? "overview",
4133
+ report: active?.report ?? "measurement-health",
4134
+ apiBaseUrl,
4135
+ range,
4136
+ custom,
4137
+ compare,
4138
+ onBrush: (start, end) => {
4139
+ if (range !== "custom") setRangeBeforeBrush(range);
4140
+ setCustom({ start, end });
4141
+ setRange("custom");
4142
+ }
4118
4143
  }
4119
- }
4120
- ) }) })
4121
- }
4122
- )
4123
- ] }) })
4144
+ ) }) })
4145
+ }
4146
+ )
4147
+ ] })
4148
+ ] })
4124
4149
  );
4125
4150
  }
4126
4151