@liiift-studio/sanity-visitor-insights 0.50.0 → 0.52.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/README.md +1 -1
- package/dist/index.d.mts +2 -3
- package/dist/index.d.ts +2 -3
- package/dist/index.js +153 -46
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +141 -34
- package/dist/index.mjs.map +1 -1
- package/dist/{ranges-CeB8esIA.d.mts → ranges-DBbZ0tg7.d.mts} +30 -1
- package/dist/{ranges-CeB8esIA.d.ts → ranges-DBbZ0tg7.d.ts} +30 -1
- package/dist/server.d.mts +4 -4
- package/dist/server.d.ts +4 -4
- package/dist/server.js +60 -6
- package/dist/server.js.map +1 -1
- package/dist/server.mjs +60 -6
- package/dist/server.mjs.map +1 -1
- package/dist/testing.d.mts +1 -1
- package/dist/testing.d.ts +1 -1
- package/dist/{vercel-CGDmz9Bt.d.mts → vercel-pqyL1I1o.d.mts} +18 -1
- package/dist/{vercel-CGDmz9Bt.d.ts → vercel-pqyL1I1o.d.ts} +18 -1
- package/package.json +1 -1
- package/src/reportData.ts +30 -0
- package/src/server/ga4.ts +44 -6
- package/src/server/reports/journey.ts +59 -1
- package/src/server/reports/reports.test.ts +6 -5
- package/src/studio/Figure.tsx +61 -4
- package/src/studio/panels.test.tsx +92 -2
- package/src/studio/panels.tsx +136 -1
package/README.md
CHANGED
|
@@ -15,7 +15,7 @@ only as a conversion anchor and a count; no revenue, and no customer data.
|
|
|
15
15
|
|---|---|
|
|
16
16
|
| **Measurement health** | How much of reality does each source actually see? |
|
|
17
17
|
| **Acquisition** | Where do visitors come from, and how many are design-industry referrals? |
|
|
18
|
-
| **Journey** | How far do visitors get,
|
|
18
|
+
| **Journey** | How far do visitors get, where do they stop, and is it different on mobile? |
|
|
19
19
|
| **Typeface interest** | Which families get viewed, tested and bought? |
|
|
20
20
|
| **Diagnostics** | What to fix before trusting any of the above? |
|
|
21
21
|
|
package/dist/index.d.mts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as sanity from 'sanity';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue, A as AcquisitionData, c as MeasurementHealthData, D as DiagnosticReport, J as JourneyData, T as TypefaceInterestData } from './ranges-
|
|
4
|
-
export { C as CaptureBasis, d as CaptureEstimate, e as CaptureModel, f as CheckStatus, g as Coverage, h as CrossSourceDay, i as DailyPoint, j as DateRange, k as DiagnosticCheck, E as EmailCampaign, l as EventCutover, m as JourneyOutcome, n as JourneyStep, L as LandingPage,
|
|
3
|
+
import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue, A as AcquisitionData, c as MeasurementHealthData, D as DiagnosticReport, J as JourneyData, T as TypefaceInterestData } from './ranges-DBbZ0tg7.mjs';
|
|
4
|
+
export { C as CaptureBasis, d as CaptureEstimate, e as CaptureModel, f as CheckStatus, g as Coverage, h as CrossSourceDay, i as DailyPoint, j as DateRange, k as DiagnosticCheck, E as EmailCampaign, l as EventCutover, m as JourneyOutcome, n as JourneySegment, o as JourneyStep, L as LandingPage, p as LicenceTierRow, P as PREEXISTING, q as REPORT_NAMES, r as ReportError, S as SiteAnalyticsConfig, s as SourceName, t as SourceRow, u as SourceStatus, v as TimelineEvent, w as TypefaceInterestRow, U as UnavailableReason, x as coverageForRange, y as estimated, z as isReportName, B as ok, F as partial, G as previousRange, H as resolveRange, I as unavailable, K as validateSiteConfig, N as valueOrNull } from './ranges-DBbZ0tg7.mjs';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Data hook for the Studio panels.
|
|
@@ -606,7 +606,6 @@ declare function AcquisitionPanel({ data, previous }: {
|
|
|
606
606
|
data: AcquisitionData;
|
|
607
607
|
previous?: AcquisitionData;
|
|
608
608
|
}): React.ReactElement;
|
|
609
|
-
/** Journey — a funnel, drawn as a tracked sequence or as independent totals, whichever the report used. */
|
|
610
609
|
declare function JourneyPanel({ data }: {
|
|
611
610
|
data: JourneyData;
|
|
612
611
|
}): React.ReactElement;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as sanity from 'sanity';
|
|
2
2
|
import React from 'react';
|
|
3
|
-
import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue, A as AcquisitionData, c as MeasurementHealthData, D as DiagnosticReport, J as JourneyData, T as TypefaceInterestData } from './ranges-
|
|
4
|
-
export { C as CaptureBasis, d as CaptureEstimate, e as CaptureModel, f as CheckStatus, g as Coverage, h as CrossSourceDay, i as DailyPoint, j as DateRange, k as DiagnosticCheck, E as EmailCampaign, l as EventCutover, m as JourneyOutcome, n as JourneyStep, L as LandingPage,
|
|
3
|
+
import { R as ReportEnvelope, a as ReportName, b as RangeKey, M as MetricValue, A as AcquisitionData, c as MeasurementHealthData, D as DiagnosticReport, J as JourneyData, T as TypefaceInterestData } from './ranges-DBbZ0tg7.js';
|
|
4
|
+
export { C as CaptureBasis, d as CaptureEstimate, e as CaptureModel, f as CheckStatus, g as Coverage, h as CrossSourceDay, i as DailyPoint, j as DateRange, k as DiagnosticCheck, E as EmailCampaign, l as EventCutover, m as JourneyOutcome, n as JourneySegment, o as JourneyStep, L as LandingPage, p as LicenceTierRow, P as PREEXISTING, q as REPORT_NAMES, r as ReportError, S as SiteAnalyticsConfig, s as SourceName, t as SourceRow, u as SourceStatus, v as TimelineEvent, w as TypefaceInterestRow, U as UnavailableReason, x as coverageForRange, y as estimated, z as isReportName, B as ok, F as partial, G as previousRange, H as resolveRange, I as unavailable, K as validateSiteConfig, N as valueOrNull } from './ranges-DBbZ0tg7.js';
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Data hook for the Studio panels.
|
|
@@ -606,7 +606,6 @@ declare function AcquisitionPanel({ data, previous }: {
|
|
|
606
606
|
data: AcquisitionData;
|
|
607
607
|
previous?: AcquisitionData;
|
|
608
608
|
}): React.ReactElement;
|
|
609
|
-
/** Journey — a funnel, drawn as a tracked sequence or as independent totals, whichever the report used. */
|
|
610
609
|
declare function JourneyPanel({ data }: {
|
|
611
610
|
data: JourneyData;
|
|
612
611
|
}): React.ReactElement;
|
package/dist/index.js
CHANGED
|
@@ -75,7 +75,7 @@ var import_sanity2 = require("sanity");
|
|
|
75
75
|
var import_icons = require("@liiift-studio/sanity-ui-compat/icons");
|
|
76
76
|
|
|
77
77
|
// src/studio/VisitorInsightsTool.tsx
|
|
78
|
-
var
|
|
78
|
+
var import_react5 = __toESM(require("react"));
|
|
79
79
|
var import_sanity_ui_compat4 = require("@liiift-studio/sanity-ui-compat");
|
|
80
80
|
|
|
81
81
|
// src/studio/useReport.ts
|
|
@@ -266,6 +266,30 @@ var SEND_WINDOW_DAYS = 3;
|
|
|
266
266
|
// src/studio/Figure.tsx
|
|
267
267
|
var import_react2 = __toESM(require("react"));
|
|
268
268
|
var import_sanity_ui_compat = require("@liiift-studio/sanity-ui-compat");
|
|
269
|
+
|
|
270
|
+
// src/studio/palette.ts
|
|
271
|
+
var SERIES = {
|
|
272
|
+
/** The complete, server-side count. The reference every other line is read against. */
|
|
273
|
+
vercel: "#4C8FD0",
|
|
274
|
+
/** GA4's pageviews — the like-for-like counterpart to Vercel's. */
|
|
275
|
+
ga4Pageviews: "#DD6B3F",
|
|
276
|
+
/** GA4's sessions. Warm, like its sibling above: same instrument, different unit. */
|
|
277
|
+
ga4Sessions: "#BC861F",
|
|
278
|
+
/** Orders, from the foundry's own records. Exact. */
|
|
279
|
+
orders: "#2E9E6B",
|
|
280
|
+
/** Revenue, same source, different unit. */
|
|
281
|
+
revenue: "#BF5D9B"
|
|
282
|
+
};
|
|
283
|
+
var REGION_ALPHA = 0.18;
|
|
284
|
+
function seriesFill(key, alpha = REGION_ALPHA) {
|
|
285
|
+
const hex = SERIES[key];
|
|
286
|
+
const r = parseInt(hex.slice(1, 3), 16);
|
|
287
|
+
const g = parseInt(hex.slice(3, 5), 16);
|
|
288
|
+
const b = parseInt(hex.slice(5, 7), 16);
|
|
289
|
+
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
// src/studio/Figure.tsx
|
|
269
293
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
270
294
|
var REASON_TEXT = {
|
|
271
295
|
not_instrumented: "Not tracked on this site",
|
|
@@ -524,7 +548,10 @@ function FunnelChart({ stages, measurement }) {
|
|
|
524
548
|
const active = activeKey === stage.key;
|
|
525
549
|
const delta = previous ? previous.value - stage.value : 0;
|
|
526
550
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("li", { style: funnelItem, children: [
|
|
527
|
-
previous && /* @__PURE__ */ (0, import_jsx_runtime.
|
|
551
|
+
previous && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: funnelGap, "aria-hidden": "true", children: [
|
|
552
|
+
measurement === "sequence" && delta > 0 && entry > 0 && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: lostTrack, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { ...lostFill, width: `${Math.max(1, Math.min(1, delta / entry) * 100)}%` } }) }),
|
|
553
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: gapLabel(delta, measurement) })
|
|
554
|
+
] }),
|
|
528
555
|
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
529
556
|
"div",
|
|
530
557
|
{
|
|
@@ -564,9 +591,30 @@ var funnelList = { listStyle: "none", margin: 0, padding: 0 };
|
|
|
564
591
|
var funnelItem = { display: "grid", gap: 4 };
|
|
565
592
|
var funnelGap = {
|
|
566
593
|
display: "flex",
|
|
594
|
+
alignItems: "center",
|
|
567
595
|
justifyContent: "flex-end",
|
|
596
|
+
gap: 8,
|
|
568
597
|
padding: "4px 2px"
|
|
569
598
|
};
|
|
599
|
+
var lostTrack = {
|
|
600
|
+
width: "38%",
|
|
601
|
+
maxWidth: 200,
|
|
602
|
+
height: 6,
|
|
603
|
+
borderRadius: 2,
|
|
604
|
+
overflow: "hidden",
|
|
605
|
+
background: "currentColor",
|
|
606
|
+
opacity: 0.1,
|
|
607
|
+
// Right to left: the bar grows back toward the funnel it came out of, which reads as leaving
|
|
608
|
+
// rather than as another quantity accumulating alongside.
|
|
609
|
+
display: "flex",
|
|
610
|
+
justifyContent: "flex-end"
|
|
611
|
+
};
|
|
612
|
+
var lostFill = {
|
|
613
|
+
height: "100%",
|
|
614
|
+
borderRadius: 2,
|
|
615
|
+
background: SERIES.ga4Pageviews,
|
|
616
|
+
opacity: 0.75
|
|
617
|
+
};
|
|
570
618
|
function funnelStage(active) {
|
|
571
619
|
return {
|
|
572
620
|
display: "grid",
|
|
@@ -581,10 +629,11 @@ function funnelStage(active) {
|
|
|
581
629
|
var barFill = {
|
|
582
630
|
height: "100%",
|
|
583
631
|
borderRadius: 2,
|
|
584
|
-
//
|
|
585
|
-
//
|
|
586
|
-
|
|
587
|
-
|
|
632
|
+
// The reference blue, as everywhere else the tool draws "people who were here". Previously
|
|
633
|
+
// currentColor at 0.55, which made the funnel a stack of grey bars whose only variable was
|
|
634
|
+
// length — legible, and giving the eye nothing to hold on to across five rungs.
|
|
635
|
+
background: SERIES.vercel,
|
|
636
|
+
opacity: 0.85
|
|
588
637
|
};
|
|
589
638
|
var barTrack = {
|
|
590
639
|
height: 8,
|
|
@@ -1018,6 +1067,7 @@ var visuallyHidden = {
|
|
|
1018
1067
|
var import_sanity_ui_compat5 = require("@liiift-studio/sanity-ui-compat");
|
|
1019
1068
|
|
|
1020
1069
|
// src/studio/panels.tsx
|
|
1070
|
+
var import_react4 = __toESM(require("react"));
|
|
1021
1071
|
var import_sanity_ui_compat3 = require("@liiift-studio/sanity-ui-compat");
|
|
1022
1072
|
|
|
1023
1073
|
// src/studio/CrossSourceTimeline.tsx
|
|
@@ -1026,30 +1076,6 @@ var import_sanity_ui_compat2 = require("@liiift-studio/sanity-ui-compat");
|
|
|
1026
1076
|
var import_d3_scale = require("d3-scale");
|
|
1027
1077
|
var import_d3_shape = require("d3-shape");
|
|
1028
1078
|
var import_d3_array = require("d3-array");
|
|
1029
|
-
|
|
1030
|
-
// src/studio/palette.ts
|
|
1031
|
-
var SERIES = {
|
|
1032
|
-
/** The complete, server-side count. The reference every other line is read against. */
|
|
1033
|
-
vercel: "#4C8FD0",
|
|
1034
|
-
/** GA4's pageviews — the like-for-like counterpart to Vercel's. */
|
|
1035
|
-
ga4Pageviews: "#DD6B3F",
|
|
1036
|
-
/** GA4's sessions. Warm, like its sibling above: same instrument, different unit. */
|
|
1037
|
-
ga4Sessions: "#BC861F",
|
|
1038
|
-
/** Orders, from the foundry's own records. Exact. */
|
|
1039
|
-
orders: "#2E9E6B",
|
|
1040
|
-
/** Revenue, same source, different unit. */
|
|
1041
|
-
revenue: "#BF5D9B"
|
|
1042
|
-
};
|
|
1043
|
-
var REGION_ALPHA = 0.18;
|
|
1044
|
-
function seriesFill(key, alpha = REGION_ALPHA) {
|
|
1045
|
-
const hex = SERIES[key];
|
|
1046
|
-
const r = parseInt(hex.slice(1, 3), 16);
|
|
1047
|
-
const g = parseInt(hex.slice(3, 5), 16);
|
|
1048
|
-
const b = parseInt(hex.slice(5, 7), 16);
|
|
1049
|
-
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
|
1050
|
-
}
|
|
1051
|
-
|
|
1052
|
-
// src/studio/CrossSourceTimeline.tsx
|
|
1053
1079
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
1054
1080
|
function colorFor(source, unit, explicit) {
|
|
1055
1081
|
if (explicit) return explicit;
|
|
@@ -1778,6 +1804,24 @@ var figureRow = {
|
|
|
1778
1804
|
gap: 10,
|
|
1779
1805
|
flexWrap: "wrap"
|
|
1780
1806
|
};
|
|
1807
|
+
var segmentRow = { display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center" };
|
|
1808
|
+
function segmentButton(selected) {
|
|
1809
|
+
return {
|
|
1810
|
+
appearance: "none",
|
|
1811
|
+
background: selected ? "var(--card-bg-color, rgba(128,128,128,0.12))" : "transparent",
|
|
1812
|
+
border: `1px solid ${selected ? "currentColor" : "var(--card-border-color, rgba(128,128,128,0.3))"}`,
|
|
1813
|
+
borderRadius: 3,
|
|
1814
|
+
color: "inherit",
|
|
1815
|
+
font: "inherit",
|
|
1816
|
+
fontSize: "0.85em",
|
|
1817
|
+
padding: "4px 10px",
|
|
1818
|
+
cursor: "pointer",
|
|
1819
|
+
display: "flex",
|
|
1820
|
+
alignItems: "baseline",
|
|
1821
|
+
gap: 7
|
|
1822
|
+
};
|
|
1823
|
+
}
|
|
1824
|
+
var segmentRate = { opacity: 0.65, fontVariantNumeric: "tabular-nums" };
|
|
1781
1825
|
var sourceLink = {
|
|
1782
1826
|
color: "inherit",
|
|
1783
1827
|
textDecoration: "underline",
|
|
@@ -2430,8 +2474,33 @@ function AcquisitionPanel({ data, previous }) {
|
|
|
2430
2474
|
data.rowsWithheld && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(NoticeList, { notices: ["GA4 withheld some low-traffic rows for privacy, so this list is shorter than reality."] })
|
|
2431
2475
|
] });
|
|
2432
2476
|
}
|
|
2477
|
+
function firstStepRate(steps) {
|
|
2478
|
+
const second = steps[1];
|
|
2479
|
+
if (!second) return null;
|
|
2480
|
+
const rate = second.conversionFromPrevious;
|
|
2481
|
+
return typeof rate === "number" && Number.isFinite(rate) ? rate : null;
|
|
2482
|
+
}
|
|
2483
|
+
function spread(segments) {
|
|
2484
|
+
const MIN_USERS = 200;
|
|
2485
|
+
const RATIO = 3;
|
|
2486
|
+
const rated = segments.map((segment) => ({
|
|
2487
|
+
label: segment.label,
|
|
2488
|
+
rate: firstStepRate(segment.steps),
|
|
2489
|
+
users: metricSortValue(segment.steps[0]?.count) ?? 0
|
|
2490
|
+
})).filter((row) => row.rate !== null && row.rate > 0 && row.users >= MIN_USERS);
|
|
2491
|
+
if (rated.length < 2) return null;
|
|
2492
|
+
const sorted = [...rated].sort((a, b) => b.rate - a.rate);
|
|
2493
|
+
const best = sorted[0];
|
|
2494
|
+
const worst = sorted[sorted.length - 1];
|
|
2495
|
+
const ratio = best.rate / worst.rate;
|
|
2496
|
+
if (ratio < RATIO) return null;
|
|
2497
|
+
return `${worst.label} reaches the second step at ${formatPercent(worst.rate, 2)} against ${best.label}'s ${formatPercent(best.rate, 2)} \u2014 about ${Math.round(ratio)} times worse, on ${formatCount(worst.users)} visitors. The combined funnel above averages the two and describes neither.`;
|
|
2498
|
+
}
|
|
2433
2499
|
function JourneyPanel({ data }) {
|
|
2434
|
-
const
|
|
2500
|
+
const [segmentKey, setSegmentKey] = import_react4.default.useState(null);
|
|
2501
|
+
const segments = data.segments ?? [];
|
|
2502
|
+
const segment = segmentKey === null ? null : segments.find((row) => row.key === segmentKey) ?? null;
|
|
2503
|
+
const allSteps = segment ? segment.steps : data.steps ?? [];
|
|
2435
2504
|
const shown = allSteps.filter((step) => step.count.status !== "unavailable");
|
|
2436
2505
|
const hiddenSteps = allSteps.filter((step) => step.count.status === "unavailable");
|
|
2437
2506
|
const stages = shown.flatMap(
|
|
@@ -2443,6 +2512,44 @@ function JourneyPanel({ data }) {
|
|
|
2443
2512
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, weight: "medium", children: tracked ? "Tracked funnel" : "Independent per-step totals" }),
|
|
2444
2513
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: tracked, children: data.approximationNote })
|
|
2445
2514
|
] }) }),
|
|
2515
|
+
segments.length > 1 && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
|
|
2516
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: segmentRow, role: "radiogroup", "aria-label": `Funnel by ${data.segmentDimension ?? "segment"}`, children: [
|
|
2517
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2518
|
+
"button",
|
|
2519
|
+
{
|
|
2520
|
+
type: "button",
|
|
2521
|
+
role: "radio",
|
|
2522
|
+
"aria-checked": segmentKey === null,
|
|
2523
|
+
style: segmentButton(segmentKey === null),
|
|
2524
|
+
onClick: () => setSegmentKey(null),
|
|
2525
|
+
children: "Everyone"
|
|
2526
|
+
}
|
|
2527
|
+
),
|
|
2528
|
+
segments.map((row) => {
|
|
2529
|
+
const rate = firstStepRate(row.steps);
|
|
2530
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
2531
|
+
"button",
|
|
2532
|
+
{
|
|
2533
|
+
type: "button",
|
|
2534
|
+
role: "radio",
|
|
2535
|
+
"aria-checked": segmentKey === row.key,
|
|
2536
|
+
style: segmentButton(segmentKey === row.key),
|
|
2537
|
+
onClick: () => setSegmentKey(row.key),
|
|
2538
|
+
children: [
|
|
2539
|
+
row.label,
|
|
2540
|
+
rate !== null && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: segmentRate, children: formatPercent(rate, 2) })
|
|
2541
|
+
]
|
|
2542
|
+
},
|
|
2543
|
+
row.key
|
|
2544
|
+
);
|
|
2545
|
+
})
|
|
2546
|
+
] }),
|
|
2547
|
+
spread(segments) && /* Said in words, once, where the funnel is. The whole reason to split a funnel
|
|
2548
|
+
is that the halves differ; when they differ by a multiple rather than a
|
|
2549
|
+
margin, the undivided funnel above describes nobody and the reader should
|
|
2550
|
+
be told rather than left to press the buttons and notice. */
|
|
2551
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: spread(segments) })
|
|
2552
|
+
] }),
|
|
2446
2553
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FunnelChart, { stages, measurement: data.measurement ?? "independent-totals" }),
|
|
2447
2554
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: splitGrid, children: [
|
|
2448
2555
|
(data.outcomes?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -2853,13 +2960,13 @@ function RangeSelector({
|
|
|
2853
2960
|
onChange,
|
|
2854
2961
|
onCustomChange
|
|
2855
2962
|
}) {
|
|
2856
|
-
const refs = (0,
|
|
2857
|
-
const [pickerOpen, setPickerOpen] = (0,
|
|
2858
|
-
const [draft, setDraft] = (0,
|
|
2859
|
-
|
|
2963
|
+
const refs = (0, import_react5.useRef)([]);
|
|
2964
|
+
const [pickerOpen, setPickerOpen] = (0, import_react5.useState)(value === "custom");
|
|
2965
|
+
const [draft, setDraft] = (0, import_react5.useState)(custom);
|
|
2966
|
+
import_react5.default.useEffect(() => {
|
|
2860
2967
|
setDraft(custom);
|
|
2861
2968
|
}, [custom.start, custom.end]);
|
|
2862
|
-
const onKeyDown = (0,
|
|
2969
|
+
const onKeyDown = (0, import_react5.useCallback)(
|
|
2863
2970
|
(event) => {
|
|
2864
2971
|
const currentIndex = RANGES.findIndex((r) => r.key === value);
|
|
2865
2972
|
let nextIndex = null;
|
|
@@ -2979,8 +3086,8 @@ function SourceStatusRow({ sources }) {
|
|
|
2979
3086
|
] }) });
|
|
2980
3087
|
}
|
|
2981
3088
|
function PanelTabs({ value, onChange }) {
|
|
2982
|
-
const refs = (0,
|
|
2983
|
-
const onKeyDown = (0,
|
|
3089
|
+
const refs = (0, import_react5.useRef)([]);
|
|
3090
|
+
const onKeyDown = (0, import_react5.useCallback)(
|
|
2984
3091
|
(event) => {
|
|
2985
3092
|
const currentIndex = PANELS.findIndex((p) => p.id === value);
|
|
2986
3093
|
let nextIndex = null;
|
|
@@ -3184,7 +3291,7 @@ function isoDaysAgo(days) {
|
|
|
3184
3291
|
return new Date(utcMidnight - days * 864e5).toISOString().slice(0, 10);
|
|
3185
3292
|
}
|
|
3186
3293
|
var COMPARED_TABS = ["overview", "acquisition"];
|
|
3187
|
-
var PanelBoundary = class extends
|
|
3294
|
+
var PanelBoundary = class extends import_react5.default.Component {
|
|
3188
3295
|
constructor(props) {
|
|
3189
3296
|
super(props);
|
|
3190
3297
|
this.state = { error: null, shownFor: props.resetKey };
|
|
@@ -3223,16 +3330,16 @@ var PanelBoundary = class extends import_react4.default.Component {
|
|
|
3223
3330
|
function VisitorInsightsTool(props) {
|
|
3224
3331
|
const apiBaseUrl = props.tool?.options?.apiBaseUrl ?? props.apiBaseUrl ?? "";
|
|
3225
3332
|
const siteLabel = props.tool?.options?.siteLabel ?? props.siteLabel ?? "";
|
|
3226
|
-
const [linked] = (0,
|
|
3333
|
+
const [linked] = (0, import_react5.useState)(() => typeof window === "undefined" ? {} : decodeView(window.location.hash));
|
|
3227
3334
|
const linkedRange = RANGES.some((r) => r.key === linked.range) ? linked.range : null;
|
|
3228
3335
|
const linkedTab = PANELS.some((p) => p.id === linked.tab) ? linked.tab : null;
|
|
3229
|
-
const [range, setRange] = (0,
|
|
3230
|
-
const [rangeBeforeBrush, setRangeBeforeBrush] = (0,
|
|
3231
|
-
const [custom, setCustom] = (0,
|
|
3336
|
+
const [range, setRange] = (0, import_react5.useState)(linkedRange ?? "month");
|
|
3337
|
+
const [rangeBeforeBrush, setRangeBeforeBrush] = (0, import_react5.useState)(linkedRange ?? "month");
|
|
3338
|
+
const [custom, setCustom] = (0, import_react5.useState)(() => linked.from && linked.to ? { start: linked.from, end: linked.to } : { start: isoDaysAgo(30), end: isoDaysAgo(0) });
|
|
3232
3339
|
const atPresent = custom.end >= isoDaysAgo(0);
|
|
3233
|
-
const [compare, setCompare] = (0,
|
|
3234
|
-
const [activePanel, setActivePanel] = (0,
|
|
3235
|
-
|
|
3340
|
+
const [compare, setCompare] = (0, import_react5.useState)("previous-period");
|
|
3341
|
+
const [activePanel, setActivePanel] = (0, import_react5.useState)(linkedTab ?? "overview");
|
|
3342
|
+
import_react5.default.useEffect(() => {
|
|
3236
3343
|
if (typeof window === "undefined") return;
|
|
3237
3344
|
const next = mergeIntoHash(window.location.hash, {
|
|
3238
3345
|
tab: activePanel,
|