@liiift-studio/sanity-visitor-insights 0.58.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 +79 -76
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +67 -64
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/studio/Figure.tsx +2 -2
- package/src/studio/panels.test.tsx +28 -5
- package/src/studio/panels.tsx +121 -71
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
|
|
@@ -1166,7 +1166,6 @@ var visuallyHidden = {
|
|
|
1166
1166
|
var import_sanity_ui_compat5 = require("@liiift-studio/sanity-ui-compat");
|
|
1167
1167
|
|
|
1168
1168
|
// src/studio/panels.tsx
|
|
1169
|
-
var import_react4 = __toESM(require("react"));
|
|
1170
1169
|
var import_sanity_ui_compat3 = require("@liiift-studio/sanity-ui-compat");
|
|
1171
1170
|
|
|
1172
1171
|
// src/studio/CrossSourceTimeline.tsx
|
|
@@ -1907,24 +1906,24 @@ var figureRow = {
|
|
|
1907
1906
|
gap: 10,
|
|
1908
1907
|
flexWrap: "wrap"
|
|
1909
1908
|
};
|
|
1910
|
-
var
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
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
|
+
};
|
|
1928
1927
|
var sourceLink = {
|
|
1929
1928
|
color: "inherit",
|
|
1930
1929
|
textDecoration: "underline",
|
|
@@ -2625,11 +2624,51 @@ function rankLine(series, pick) {
|
|
|
2625
2624
|
const ranked = weeklyRank(series.map((d) => ({ date: d.date, value: pick(d) })));
|
|
2626
2625
|
return ranked ? describeRank(ranked) : null;
|
|
2627
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
|
+
}
|
|
2628
2669
|
function JourneyPanel({ data }) {
|
|
2629
|
-
const [segmentKey, setSegmentKey] = import_react4.default.useState(null);
|
|
2630
2670
|
const segments = data.segments ?? [];
|
|
2631
|
-
const
|
|
2632
|
-
const allSteps = segment ? segment.steps : data.steps ?? [];
|
|
2671
|
+
const allSteps = data.steps ?? [];
|
|
2633
2672
|
const shown = allSteps.filter((step) => step.count.status !== "unavailable");
|
|
2634
2673
|
const hiddenSteps = allSteps.filter((step) => step.count.status === "unavailable");
|
|
2635
2674
|
const stages = shown.flatMap(
|
|
@@ -2641,44 +2680,8 @@ function JourneyPanel({ data }) {
|
|
|
2641
2680
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, weight: "medium", children: tracked ? "Tracked funnel" : "Independent per-step totals" }),
|
|
2642
2681
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, muted: tracked, children: data.approximationNote })
|
|
2643
2682
|
] }) }),
|
|
2644
|
-
|
|
2645
|
-
|
|
2646
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
2647
|
-
"button",
|
|
2648
|
-
{
|
|
2649
|
-
type: "button",
|
|
2650
|
-
role: "radio",
|
|
2651
|
-
"aria-checked": segmentKey === null,
|
|
2652
|
-
style: segmentButton(segmentKey === null),
|
|
2653
|
-
onClick: () => setSegmentKey(null),
|
|
2654
|
-
children: "Everyone"
|
|
2655
|
-
}
|
|
2656
|
-
),
|
|
2657
|
-
segments.map((row) => {
|
|
2658
|
-
const rate = firstStepRate(row.steps);
|
|
2659
|
-
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
2660
|
-
"button",
|
|
2661
|
-
{
|
|
2662
|
-
type: "button",
|
|
2663
|
-
role: "radio",
|
|
2664
|
-
"aria-checked": segmentKey === row.key,
|
|
2665
|
-
style: segmentButton(segmentKey === row.key),
|
|
2666
|
-
onClick: () => setSegmentKey(row.key),
|
|
2667
|
-
children: [
|
|
2668
|
-
row.label,
|
|
2669
|
-
rate !== null && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { style: segmentRate, children: formatPercent(rate, 2) })
|
|
2670
|
-
]
|
|
2671
|
-
},
|
|
2672
|
-
row.key
|
|
2673
|
-
);
|
|
2674
|
-
})
|
|
2675
|
-
] }),
|
|
2676
|
-
spread(segments) && /* Said in words, once, where the funnel is. The whole reason to split a funnel
|
|
2677
|
-
is that the halves differ; when they differ by a multiple rather than a
|
|
2678
|
-
margin, the undivided funnel above describes nobody and the reader should
|
|
2679
|
-
be told rather than left to press the buttons and notice. */
|
|
2680
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_sanity_ui_compat3.Text, { size: 1, children: spread(segments) })
|
|
2681
|
-
] }),
|
|
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) }),
|
|
2682
2685
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(FunnelChart, { stages, measurement: data.measurement ?? "independent-totals" }),
|
|
2683
2686
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { style: splitGrid, children: [
|
|
2684
2687
|
(data.outcomes?.length ?? 0) > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -3092,13 +3095,13 @@ function RangeSelector({
|
|
|
3092
3095
|
onChange,
|
|
3093
3096
|
onCustomChange
|
|
3094
3097
|
}) {
|
|
3095
|
-
const refs = (0,
|
|
3096
|
-
const [pickerOpen, setPickerOpen] = (0,
|
|
3097
|
-
const [draft, setDraft] = (0,
|
|
3098
|
-
|
|
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(() => {
|
|
3099
3102
|
setDraft(custom);
|
|
3100
3103
|
}, [custom.start, custom.end]);
|
|
3101
|
-
const onKeyDown = (0,
|
|
3104
|
+
const onKeyDown = (0, import_react4.useCallback)(
|
|
3102
3105
|
(event) => {
|
|
3103
3106
|
const currentIndex = RANGES.findIndex((r) => r.key === value);
|
|
3104
3107
|
let nextIndex = null;
|
|
@@ -3218,8 +3221,8 @@ function SourceStatusRow({ sources }) {
|
|
|
3218
3221
|
] }) });
|
|
3219
3222
|
}
|
|
3220
3223
|
function PanelTabs({ value, onChange }) {
|
|
3221
|
-
const refs = (0,
|
|
3222
|
-
const onKeyDown = (0,
|
|
3224
|
+
const refs = (0, import_react4.useRef)([]);
|
|
3225
|
+
const onKeyDown = (0, import_react4.useCallback)(
|
|
3223
3226
|
(event) => {
|
|
3224
3227
|
const currentIndex = PANELS.findIndex((p) => p.id === value);
|
|
3225
3228
|
let nextIndex = null;
|
|
@@ -3420,7 +3423,7 @@ function isoDaysAgo(days) {
|
|
|
3420
3423
|
return new Date(utcMidnight - days * 864e5).toISOString().slice(0, 10);
|
|
3421
3424
|
}
|
|
3422
3425
|
var COMPARED_TABS = ["overview", "acquisition"];
|
|
3423
|
-
var PanelBoundary = class extends
|
|
3426
|
+
var PanelBoundary = class extends import_react4.default.Component {
|
|
3424
3427
|
constructor(props) {
|
|
3425
3428
|
super(props);
|
|
3426
3429
|
this.state = { error: null, shownFor: props.resetKey };
|
|
@@ -3459,16 +3462,16 @@ var PanelBoundary = class extends import_react5.default.Component {
|
|
|
3459
3462
|
function VisitorInsightsTool(props) {
|
|
3460
3463
|
const apiBaseUrl = props.tool?.options?.apiBaseUrl ?? props.apiBaseUrl ?? "";
|
|
3461
3464
|
const siteLabel = props.tool?.options?.siteLabel ?? props.siteLabel ?? "";
|
|
3462
|
-
const [linked] = (0,
|
|
3465
|
+
const [linked] = (0, import_react4.useState)(() => typeof window === "undefined" ? {} : decodeView(window.location.hash));
|
|
3463
3466
|
const linkedRange = RANGES.some((r) => r.key === linked.range) ? linked.range : null;
|
|
3464
3467
|
const linkedTab = PANELS.some((p) => p.id === linked.tab) ? linked.tab : null;
|
|
3465
|
-
const [range, setRange] = (0,
|
|
3466
|
-
const [rangeBeforeBrush, setRangeBeforeBrush] = (0,
|
|
3467
|
-
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) });
|
|
3468
3471
|
const atPresent = custom.end >= isoDaysAgo(0);
|
|
3469
|
-
const [compare, setCompare] = (0,
|
|
3470
|
-
const [activePanel, setActivePanel] = (0,
|
|
3471
|
-
|
|
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(() => {
|
|
3472
3475
|
if (typeof window === "undefined") return;
|
|
3473
3476
|
const next = mergeIntoHash(window.location.hash, {
|
|
3474
3477
|
tab: activePanel,
|