@liiift-studio/sanity-visitor-insights 0.57.0 → 0.59.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 +164 -107
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +152 -95
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/studio/Figure.tsx +85 -2
- package/src/studio/palette.ts +1 -0
- package/src/studio/panels.test.tsx +89 -6
- package/src/studio/panels.tsx +149 -75
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_react4 = __toESM(require("react"));
|
|
79
79
|
var import_sanity_ui_compat4 = require("@liiift-studio/sanity-ui-compat");
|
|
80
80
|
|
|
81
81
|
// src/studio/useReport.ts
|
|
@@ -306,7 +306,8 @@ var MARKS = {
|
|
|
306
306
|
"chart.grid": { key: "neutral", alpha: 0.18, role: "furniture", note: "Grid rules" },
|
|
307
307
|
"bar.fill": { key: "vercel", alpha: 1, role: "data", note: "Every proportion, comparison and funnel bar" },
|
|
308
308
|
"bar.track": { key: "neutral", alpha: 0.12, role: "furniture", note: "The rail a bar sits in" },
|
|
309
|
-
"bar.lost": { key: "revenue", alpha: 1, role: "data", note: "People who did not continue past a funnel rung" }
|
|
309
|
+
"bar.lost": { key: "revenue", alpha: 1, role: "data", note: "People who did not continue past a funnel rung" },
|
|
310
|
+
"bar.seen": { key: "ga4Pageviews", alpha: 1, role: "data", note: "The share of a complete count that the lossy source saw" }
|
|
310
311
|
};
|
|
311
312
|
function mark(name) {
|
|
312
313
|
const m = MARKS[name];
|
|
@@ -315,6 +316,33 @@ function mark(name) {
|
|
|
315
316
|
return seriesFill(m.key, m.alpha);
|
|
316
317
|
}
|
|
317
318
|
|
|
319
|
+
// src/types.ts
|
|
320
|
+
function estimated(value, low, high, basis) {
|
|
321
|
+
return { status: "estimated", value, low, high, basis };
|
|
322
|
+
}
|
|
323
|
+
function ok(value) {
|
|
324
|
+
return { status: "ok", value };
|
|
325
|
+
}
|
|
326
|
+
function unavailable(reason, detail) {
|
|
327
|
+
return { status: "unavailable", reason, detail };
|
|
328
|
+
}
|
|
329
|
+
function partial(value, coveredFrom, note) {
|
|
330
|
+
return { status: "partial", value, coveredFrom, note };
|
|
331
|
+
}
|
|
332
|
+
function valueOrNull(metric) {
|
|
333
|
+
return metric.status === "unavailable" ? null : metric.value;
|
|
334
|
+
}
|
|
335
|
+
var REPORT_NAMES = [
|
|
336
|
+
"measurement-health",
|
|
337
|
+
"acquisition",
|
|
338
|
+
"journey",
|
|
339
|
+
"typeface-interest",
|
|
340
|
+
"diagnostics"
|
|
341
|
+
];
|
|
342
|
+
function isReportName(value) {
|
|
343
|
+
return typeof value === "string" && REPORT_NAMES.includes(value);
|
|
344
|
+
}
|
|
345
|
+
|
|
318
346
|
// src/studio/Figure.tsx
|
|
319
347
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
320
348
|
var REASON_TEXT = {
|
|
@@ -773,6 +801,47 @@ function SectionTitle({
|
|
|
773
801
|
}) {
|
|
774
802
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: sectionHeader, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Heading, { size: tone === "primary" ? 1 : 0, style: tone === "primary" ? primaryTitle : secondaryTitle, children: title }) });
|
|
775
803
|
}
|
|
804
|
+
function isContainment(whole, part) {
|
|
805
|
+
const w = valueOrNull(whole);
|
|
806
|
+
const p = valueOrNull(part);
|
|
807
|
+
return w !== null && p !== null && w > 0 && p <= w;
|
|
808
|
+
}
|
|
809
|
+
function ContainmentBar({
|
|
810
|
+
wholeLabel,
|
|
811
|
+
whole,
|
|
812
|
+
partLabel,
|
|
813
|
+
part,
|
|
814
|
+
missingLabel
|
|
815
|
+
}) {
|
|
816
|
+
const wholeValue = valueOrNull(whole);
|
|
817
|
+
const partValue = valueOrNull(part);
|
|
818
|
+
if (!isContainment(whole, part)) return null;
|
|
819
|
+
if (wholeValue === null || partValue === null) return null;
|
|
820
|
+
const share = partValue / wholeValue;
|
|
821
|
+
const missing = wholeValue - partValue;
|
|
822
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Stack, { space: 2, children: [
|
|
823
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: barHeader, children: [
|
|
824
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 1, children: wholeLabel }),
|
|
825
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_sanity_ui_compat.Text, { size: 1, weight: "medium", children: formatCount(wholeValue) })
|
|
826
|
+
] }),
|
|
827
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { "aria-hidden": "true", style: { ...barTrack, background: mark("bar.fill"), height: 14 }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { style: { height: "100%", width: `${share * 100}%`, background: mark("bar.seen"), borderRadius: 2 } }) }),
|
|
828
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { style: barHeader, children: [
|
|
829
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: [
|
|
830
|
+
partLabel,
|
|
831
|
+
": ",
|
|
832
|
+
formatCount(partValue),
|
|
833
|
+
" (",
|
|
834
|
+
formatPercent(share, 0),
|
|
835
|
+
")"
|
|
836
|
+
] }),
|
|
837
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_sanity_ui_compat.Text, { size: 0, muted: true, children: [
|
|
838
|
+
formatCount(missing),
|
|
839
|
+
" ",
|
|
840
|
+
missingLabel
|
|
841
|
+
] })
|
|
842
|
+
] })
|
|
843
|
+
] });
|
|
844
|
+
}
|
|
776
845
|
function SortableTable({
|
|
777
846
|
caption,
|
|
778
847
|
columns,
|
|
@@ -1097,7 +1166,6 @@ var visuallyHidden = {
|
|
|
1097
1166
|
var import_sanity_ui_compat5 = require("@liiift-studio/sanity-ui-compat");
|
|
1098
1167
|
|
|
1099
1168
|
// src/studio/panels.tsx
|
|
1100
|
-
var import_react4 = __toESM(require("react"));
|
|
1101
1169
|
var import_sanity_ui_compat3 = require("@liiift-studio/sanity-ui-compat");
|
|
1102
1170
|
|
|
1103
1171
|
// src/studio/CrossSourceTimeline.tsx
|
|
@@ -1812,33 +1880,6 @@ function describeRank(rank) {
|
|
|
1812
1880
|
return `${ordinal(rank.rank)} best of the last ${rank.of} weeks`;
|
|
1813
1881
|
}
|
|
1814
1882
|
|
|
1815
|
-
// src/types.ts
|
|
1816
|
-
function estimated(value, low, high, basis) {
|
|
1817
|
-
return { status: "estimated", value, low, high, basis };
|
|
1818
|
-
}
|
|
1819
|
-
function ok(value) {
|
|
1820
|
-
return { status: "ok", value };
|
|
1821
|
-
}
|
|
1822
|
-
function unavailable(reason, detail) {
|
|
1823
|
-
return { status: "unavailable", reason, detail };
|
|
1824
|
-
}
|
|
1825
|
-
function partial(value, coveredFrom, note) {
|
|
1826
|
-
return { status: "partial", value, coveredFrom, note };
|
|
1827
|
-
}
|
|
1828
|
-
function valueOrNull(metric) {
|
|
1829
|
-
return metric.status === "unavailable" ? null : metric.value;
|
|
1830
|
-
}
|
|
1831
|
-
var REPORT_NAMES = [
|
|
1832
|
-
"measurement-health",
|
|
1833
|
-
"acquisition",
|
|
1834
|
-
"journey",
|
|
1835
|
-
"typeface-interest",
|
|
1836
|
-
"diagnostics"
|
|
1837
|
-
];
|
|
1838
|
-
function isReportName(value) {
|
|
1839
|
-
return typeof value === "string" && REPORT_NAMES.includes(value);
|
|
1840
|
-
}
|
|
1841
|
-
|
|
1842
1883
|
// src/studio/panels.tsx
|
|
1843
1884
|
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
1844
1885
|
function metricSortValue(metric) {
|
|
@@ -1865,24 +1906,24 @@ var figureRow = {
|
|
|
1865
1906
|
gap: 10,
|
|
1866
1907
|
flexWrap: "wrap"
|
|
1867
1908
|
};
|
|
1868
|
-
var
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
1873
|
-
|
|
1874
|
-
|
|
1875
|
-
|
|
1876
|
-
|
|
1877
|
-
|
|
1878
|
-
|
|
1879
|
-
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
1884
|
-
|
|
1885
|
-
|
|
1909
|
+
var segmentHeadCell = {
|
|
1910
|
+
textAlign: "left",
|
|
1911
|
+
fontSize: "0.78em",
|
|
1912
|
+
letterSpacing: "0.06em",
|
|
1913
|
+
textTransform: "uppercase",
|
|
1914
|
+
fontWeight: 500,
|
|
1915
|
+
opacity: 0.7,
|
|
1916
|
+
padding: "8px 12px",
|
|
1917
|
+
borderBottom: "1px solid var(--card-border-color, rgba(128,128,128,0.3))",
|
|
1918
|
+
whiteSpace: "nowrap"
|
|
1919
|
+
};
|
|
1920
|
+
var segmentRowCell = {
|
|
1921
|
+
textAlign: "left",
|
|
1922
|
+
fontWeight: 400,
|
|
1923
|
+
padding: "8px 12px",
|
|
1924
|
+
borderBottom: "1px solid var(--card-border-color, rgba(128,128,128,0.18))",
|
|
1925
|
+
verticalAlign: "baseline"
|
|
1926
|
+
};
|
|
1886
1927
|
var sourceLink = {
|
|
1887
1928
|
color: "inherit",
|
|
1888
1929
|
textDecoration: "underline",
|
|
@@ -2269,12 +2310,24 @@ function Verdict({ data, previous }) {
|
|
|
2269
2310
|
}
|
|
2270
2311
|
function DataHealthPanel({ data, diagnostics }) {
|
|
2271
2312
|
const pageviewMax = maxOf([data.ga4Pageviews, data.vercelPageviews]);
|
|
2313
|
+
const contained = isContainment(data.vercelPageviews, data.ga4Pageviews);
|
|
2272
2314
|
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 4, children: [
|
|
2273
2315
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
|
|
2274
2316
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SectionTitle, { title: "Pageviews, source against source" }),
|
|
2275
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: "
|
|
2276
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2277
|
-
|
|
2317
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: "Both counting pageviews. Google Analytics is blockable, so seeing fewer is normal." }),
|
|
2318
|
+
contained ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2319
|
+
ContainmentBar,
|
|
2320
|
+
{
|
|
2321
|
+
wholeLabel: "Pageviews Vercel counted",
|
|
2322
|
+
whole: data.vercelPageviews,
|
|
2323
|
+
partLabel: "Seen by Google Analytics",
|
|
2324
|
+
part: data.ga4Pageviews,
|
|
2325
|
+
missingLabel: "it missed"
|
|
2326
|
+
}
|
|
2327
|
+
) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
2328
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ComparisonBar, { label: "Vercel pageviews", metric: data.vercelPageviews, max: pageviewMax }),
|
|
2329
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(ComparisonBar, { label: "GA4 pageviews", metric: data.ga4Pageviews, max: pageviewMax })
|
|
2330
|
+
] })
|
|
2278
2331
|
] }),
|
|
2279
2332
|
/* @__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: 2, children: [
|
|
2280
2333
|
data.shortfallRatio !== null && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, weight: "semibold", children: data.shortfallRatio >= 0 ? `GA4 saw ${formatPercent(data.shortfallRatio, 1)} fewer pageviews than Vercel` : `GA4 saw ${formatPercent(-data.shortfallRatio, 1)} more pageviews than Vercel` }),
|
|
@@ -2571,11 +2624,51 @@ function rankLine(series, pick) {
|
|
|
2571
2624
|
const ranked = weeklyRank(series.map((d) => ({ date: d.date, value: pick(d) })));
|
|
2572
2625
|
return ranked ? describeRank(ranked) : null;
|
|
2573
2626
|
}
|
|
2627
|
+
function SegmentTable({ segments, dimension }) {
|
|
2628
|
+
if (segments.length < 2) return null;
|
|
2629
|
+
const spine = [...segments].sort((a, b) => b.steps.length - a.steps.length)[0]?.steps ?? [];
|
|
2630
|
+
if (spine.length === 0) return null;
|
|
2631
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 3, children: [
|
|
2632
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: [
|
|
2633
|
+
"Every ",
|
|
2634
|
+
dimension,
|
|
2635
|
+
" at once. A rate needs ",
|
|
2636
|
+
MIN_RATE_DENOMINATOR,
|
|
2637
|
+
" people at the step before it; below that only the count is shown."
|
|
2638
|
+
] }),
|
|
2639
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Card, { radius: 2, tone: "transparent", border: true, style: { overflowX: "auto", width: "100%" }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("table", { style: { width: "100%", borderCollapse: "collapse", minWidth: 360 }, children: [
|
|
2640
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("caption", { style: visuallyHidden, children: [
|
|
2641
|
+
"Funnel steps by ",
|
|
2642
|
+
dimension
|
|
2643
|
+
] }),
|
|
2644
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
|
|
2645
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { scope: "col", style: segmentHeadCell, children: "Step" }),
|
|
2646
|
+
segments.map((segment) => /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { scope: "col", style: { ...segmentHeadCell, textAlign: "right" }, children: segment.label }, segment.key))
|
|
2647
|
+
] }) }),
|
|
2648
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("tbody", { children: spine.map((step, index) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("tr", { children: [
|
|
2649
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("th", { scope: "row", style: segmentRowCell, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: step.label }) }),
|
|
2650
|
+
segments.map((segment) => {
|
|
2651
|
+
const cell = segment.steps.find((s) => s.key === step.key);
|
|
2652
|
+
const count = cell ? metricSortValue(cell.count) : null;
|
|
2653
|
+
const before = index > 0 ? metricSortValue(segment.steps.find((s) => s.key === spine[index - 1].key)?.count) : null;
|
|
2654
|
+
const rate = cell?.conversionFromPrevious ?? null;
|
|
2655
|
+
const showRate = index > 0 && rate !== null && before !== null && before >= MIN_RATE_DENOMINATOR;
|
|
2656
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("td", { style: { ...segmentRowCell, textAlign: "right", fontVariantNumeric: "tabular-nums" }, children: count === null ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: true, children: "\u2014" }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Stack, { space: 1, children: [
|
|
2657
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: formatCount(count) }),
|
|
2658
|
+
showRate && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_sanity_ui_compat3.Text, { size: 0, muted: true, children: [
|
|
2659
|
+
formatPercent(rate, 1),
|
|
2660
|
+
" of ",
|
|
2661
|
+
formatCount(before)
|
|
2662
|
+
] })
|
|
2663
|
+
] }) }, segment.key);
|
|
2664
|
+
})
|
|
2665
|
+
] }, step.key)) })
|
|
2666
|
+
] }) })
|
|
2667
|
+
] });
|
|
2668
|
+
}
|
|
2574
2669
|
function JourneyPanel({ data }) {
|
|
2575
|
-
const [segmentKey, setSegmentKey] = import_react4.default.useState(null);
|
|
2576
2670
|
const segments = data.segments ?? [];
|
|
2577
|
-
const
|
|
2578
|
-
const allSteps = segment ? segment.steps : data.steps ?? [];
|
|
2671
|
+
const allSteps = data.steps ?? [];
|
|
2579
2672
|
const shown = allSteps.filter((step) => step.count.status !== "unavailable");
|
|
2580
2673
|
const hiddenSteps = allSteps.filter((step) => step.count.status === "unavailable");
|
|
2581
2674
|
const stages = shown.flatMap(
|
|
@@ -2587,44 +2680,8 @@ function JourneyPanel({ data }) {
|
|
|
2587
2680
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, weight: "medium", children: tracked ? "Tracked funnel" : "Independent per-step totals" }),
|
|
2588
2681
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: tracked, children: data.approximationNote })
|
|
2589
2682
|
] }) }),
|
|
2590
|
-
|
|
2591
|
-
|
|
2592
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2593
|
-
"button",
|
|
2594
|
-
{
|
|
2595
|
-
type: "button",
|
|
2596
|
-
role: "radio",
|
|
2597
|
-
"aria-checked": segmentKey === null,
|
|
2598
|
-
style: segmentButton(segmentKey === null),
|
|
2599
|
-
onClick: () => setSegmentKey(null),
|
|
2600
|
-
children: "Everyone"
|
|
2601
|
-
}
|
|
2602
|
-
),
|
|
2603
|
-
segments.map((row) => {
|
|
2604
|
-
const rate = firstStepRate(row.steps);
|
|
2605
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
2606
|
-
"button",
|
|
2607
|
-
{
|
|
2608
|
-
type: "button",
|
|
2609
|
-
role: "radio",
|
|
2610
|
-
"aria-checked": segmentKey === row.key,
|
|
2611
|
-
style: segmentButton(segmentKey === row.key),
|
|
2612
|
-
onClick: () => setSegmentKey(row.key),
|
|
2613
|
-
children: [
|
|
2614
|
-
row.label,
|
|
2615
|
-
rate !== null && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: segmentRate, children: formatPercent(rate, 2) })
|
|
2616
|
-
]
|
|
2617
|
-
},
|
|
2618
|
-
row.key
|
|
2619
|
-
);
|
|
2620
|
-
})
|
|
2621
|
-
] }),
|
|
2622
|
-
spread(segments) && /* Said in words, once, where the funnel is. The whole reason to split a funnel
|
|
2623
|
-
is that the halves differ; when they differ by a multiple rather than a
|
|
2624
|
-
margin, the undivided funnel above describes nobody and the reader should
|
|
2625
|
-
be told rather than left to press the buttons and notice. */
|
|
2626
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: spread(segments) })
|
|
2627
|
-
] }),
|
|
2683
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(SegmentTable, { segments, dimension: data.segmentDimension ?? "segment" }),
|
|
2684
|
+
spread(segments) && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: spread(segments) }),
|
|
2628
2685
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FunnelChart, { stages, measurement: data.measurement ?? "independent-totals" }),
|
|
2629
2686
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: splitGrid, children: [
|
|
2630
2687
|
(data.outcomes?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -3038,13 +3095,13 @@ function RangeSelector({
|
|
|
3038
3095
|
onChange,
|
|
3039
3096
|
onCustomChange
|
|
3040
3097
|
}) {
|
|
3041
|
-
const refs = (0,
|
|
3042
|
-
const [pickerOpen, setPickerOpen] = (0,
|
|
3043
|
-
const [draft, setDraft] = (0,
|
|
3044
|
-
|
|
3098
|
+
const refs = (0, import_react4.useRef)([]);
|
|
3099
|
+
const [pickerOpen, setPickerOpen] = (0, import_react4.useState)(value === "custom");
|
|
3100
|
+
const [draft, setDraft] = (0, import_react4.useState)(custom);
|
|
3101
|
+
import_react4.default.useEffect(() => {
|
|
3045
3102
|
setDraft(custom);
|
|
3046
3103
|
}, [custom.start, custom.end]);
|
|
3047
|
-
const onKeyDown = (0,
|
|
3104
|
+
const onKeyDown = (0, import_react4.useCallback)(
|
|
3048
3105
|
(event) => {
|
|
3049
3106
|
const currentIndex = RANGES.findIndex((r) => r.key === value);
|
|
3050
3107
|
let nextIndex = null;
|
|
@@ -3164,8 +3221,8 @@ function SourceStatusRow({ sources }) {
|
|
|
3164
3221
|
] }) });
|
|
3165
3222
|
}
|
|
3166
3223
|
function PanelTabs({ value, onChange }) {
|
|
3167
|
-
const refs = (0,
|
|
3168
|
-
const onKeyDown = (0,
|
|
3224
|
+
const refs = (0, import_react4.useRef)([]);
|
|
3225
|
+
const onKeyDown = (0, import_react4.useCallback)(
|
|
3169
3226
|
(event) => {
|
|
3170
3227
|
const currentIndex = PANELS.findIndex((p) => p.id === value);
|
|
3171
3228
|
let nextIndex = null;
|
|
@@ -3366,7 +3423,7 @@ function isoDaysAgo(days) {
|
|
|
3366
3423
|
return new Date(utcMidnight - days * 864e5).toISOString().slice(0, 10);
|
|
3367
3424
|
}
|
|
3368
3425
|
var COMPARED_TABS = ["overview", "acquisition"];
|
|
3369
|
-
var PanelBoundary = class extends
|
|
3426
|
+
var PanelBoundary = class extends import_react4.default.Component {
|
|
3370
3427
|
constructor(props) {
|
|
3371
3428
|
super(props);
|
|
3372
3429
|
this.state = { error: null, shownFor: props.resetKey };
|
|
@@ -3405,16 +3462,16 @@ var PanelBoundary = class extends import_react5.default.Component {
|
|
|
3405
3462
|
function VisitorInsightsTool(props) {
|
|
3406
3463
|
const apiBaseUrl = props.tool?.options?.apiBaseUrl ?? props.apiBaseUrl ?? "";
|
|
3407
3464
|
const siteLabel = props.tool?.options?.siteLabel ?? props.siteLabel ?? "";
|
|
3408
|
-
const [linked] = (0,
|
|
3465
|
+
const [linked] = (0, import_react4.useState)(() => typeof window === "undefined" ? {} : decodeView(window.location.hash));
|
|
3409
3466
|
const linkedRange = RANGES.some((r) => r.key === linked.range) ? linked.range : null;
|
|
3410
3467
|
const linkedTab = PANELS.some((p) => p.id === linked.tab) ? linked.tab : null;
|
|
3411
|
-
const [range, setRange] = (0,
|
|
3412
|
-
const [rangeBeforeBrush, setRangeBeforeBrush] = (0,
|
|
3413
|
-
const [custom, setCustom] = (0,
|
|
3468
|
+
const [range, setRange] = (0, import_react4.useState)(linkedRange ?? "month");
|
|
3469
|
+
const [rangeBeforeBrush, setRangeBeforeBrush] = (0, import_react4.useState)(linkedRange ?? "month");
|
|
3470
|
+
const [custom, setCustom] = (0, import_react4.useState)(() => linked.from && linked.to ? { start: linked.from, end: linked.to } : { start: isoDaysAgo(30), end: isoDaysAgo(0) });
|
|
3414
3471
|
const atPresent = custom.end >= isoDaysAgo(0);
|
|
3415
|
-
const [compare, setCompare] = (0,
|
|
3416
|
-
const [activePanel, setActivePanel] = (0,
|
|
3417
|
-
|
|
3472
|
+
const [compare, setCompare] = (0, import_react4.useState)("previous-period");
|
|
3473
|
+
const [activePanel, setActivePanel] = (0, import_react4.useState)(linkedTab ?? "overview");
|
|
3474
|
+
import_react4.default.useEffect(() => {
|
|
3418
3475
|
if (typeof window === "undefined") return;
|
|
3419
3476
|
const next = mergeIntoHash(window.location.hash, {
|
|
3420
3477
|
tab: activePanel,
|