@reopt-ai/opt-ui 1.14.0 → 1.15.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/CHANGELOG.md +45 -0
- package/COMPONENT_CATALOG.md +222 -69
- package/README.md +26 -0
- package/dist/core/index.cjs +4 -4
- package/dist/core/index.d.cts +2 -2
- package/dist/core/index.d.ts +2 -2
- package/dist/core/index.js +2 -2
- package/dist/docs/02-components/01-core.md +37 -33
- package/dist/docs/02-components/02-visuals.md +1 -1
- package/dist/docs/02-components/03-shells.md +191 -36
- package/dist/docs/02-components/04-surfaces.md +1 -1
- package/dist/docs/02-components/index.md +4 -4
- package/dist/id-registry.cjs +24 -0
- package/dist/id-registry.js +24 -0
- package/dist/id-registry.json +51 -0
- package/dist/index.cjs +445 -381
- package/dist/index.d.cts +16 -13
- package/dist/index.d.ts +16 -13
- package/dist/index.js +303 -245
- package/dist/{key-pad-menu-CqFI6la9.js → key-pad-menu-BCOeYvsH.js} +50 -61
- package/dist/{key-pad-menu-D6pYzaj-.d.cts → key-pad-menu-CaIsTHB2.d.cts} +12 -8
- package/dist/{key-pad-menu-BgtSL3xe.cjs → key-pad-menu-Jaqxhx9P.cjs} +48 -71
- package/dist/{key-pad-menu-Yh7Fpb8m.d.ts → key-pad-menu-odAEycIX.d.ts} +12 -8
- package/dist/meta.cjs +447 -7
- package/dist/meta.js +447 -7
- package/dist/{field-sidebar-mSVCMAZp.js → replay-player-layout-BVUNsUS1.js} +891 -191
- package/dist/{field-sidebar-ePqFX6MN.cjs → replay-player-layout-Cmzptq89.cjs} +931 -189
- package/dist/{field-sidebar-CJ4HxP8D.d.cts → replay-player-layout-DpqdfdQ6.d.ts} +246 -3
- package/dist/{field-sidebar-DnhpcT2A.d.ts → replay-player-layout-DuZW-gFF.d.cts} +246 -3
- package/dist/shells/index.cjs +58 -52
- package/dist/shells/index.d.cts +2 -2
- package/dist/shells/index.d.ts +2 -2
- package/dist/shells/index.js +2 -2
- package/dist/{text-truncate-Bd-0WkSP.js → text-truncate-B-8nJgnu.js} +200 -101
- package/dist/{text-truncate-Bbul-jMD.d.cts → text-truncate-DOxUijpB.d.cts} +2 -0
- package/dist/{text-truncate-Bbul-jMD.d.ts → text-truncate-DOxUijpB.d.ts} +2 -0
- package/dist/{text-truncate-BeYW6XZu.cjs → text-truncate-gQhpMy-Z.cjs} +214 -97
- package/package.json +1 -1
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { $t as
|
|
2
|
+
import { $n as OPT_DOT_YELLOW, $t as Composite, At as ComboboxRoot$1, Bn as OPT_ANIMATE_ENTER_EXIT, Bt as DialogRoot$1, C as FormCheckbox$1, Ct as MenubarRoot$1, Dn as Skeleton, Dt as ComboboxInput$1, En as StatCard, G as FormSelectPopover, Gn as OPT_BADGE_YELLOW, Gt as SelectTrigger$1, H as FormRoot$1, Hn as OPT_BADGE_GREEN, Ht as SelectLabel$1, It as DialogFooter, J as FormSubmit$1, Jn as OPT_DISCLOSURE_TRIGGER, Jt as TabPanel$1, Kn as OPT_BORDER, Kt as Tab$1, Lt as DialogHeader, Mn as Toggle$1, Mt as DialogBody, N as FormInput$1, Nt as DialogDescription$1, O as FormError$1, Ot as ComboboxItem$1, P as FormLabel$1, Qn as OPT_DOT_RED, Qt as DisclosureTrigger$1, R as FormRadio$1, Rn as OPT_ACTIVE_ITEM, Rt as DialogHeading$1, S as formatOptTime, Sn as Button, St as MenubarContainer$1, U as FormSelect, Un as OPT_BADGE_NEUTRAL, Ut as SelectPopover$1, Vn as OPT_BADGE_BLUE, Vt as SelectItem$1, W as FormSelectItem, Wn as OPT_BADGE_RED, Wt as SelectRoot$1, Xt as DisclosureContent$1, Y as FormSwitch$1, Yn as OPT_DIVIDER, Yt as TabsRoot$1, Z as FormTextarea$1, Zn as OPT_DOT_GREEN, Zt as DisclosureRoot$1, _ as DropdownTrigger, _n as Alert, _r as _objectSpread2, _t as MenuItemRadio$1, an as Textarea, ar as OPT_ITEM_BASE, b as formatOptNumber, bn as DescriptionList, br as cn, bt as MenuSeparator$1, c as ResizableHandle, cn as Input, d as Checkbox, dn as ToolbarContainer$1, dr as OPT_TD_MUTED, dt as PopoverTrigger$1, en as CompositeItem, er as OPT_FOCUS, et as useFormStore, fn as ToolbarRoot$1, fr as OPT_TEXT_LABEL, gn as TooltipProvider, gr as OPT_TH, gt as MenuItemCheckbox$1, h as DropdownMenu, hn as TooltipAnchor$1, hr as OPT_TEXT_TERTIARY, ht as MenuItem$1, i as FieldToken, in as Meter$1, k as FormField$1, kt as ComboboxPopover$1, l as ResizablePanel, lr as OPT_TABLE_ROW_ACTIVE, lt as PopoverContent$1, m as DropdownItem, mn as Tooltip$1, mr as OPT_TEXT_SECONDARY, mt as MenuButtonArrow$1, n as Highlight, nn as CompositeRow, nt as DateRangePicker, o as resolveFieldType, or as OPT_SURFACE, p as DropdownContent, pn as ToolbarSeparator$1, pr as OPT_TEXT_PRIMARY, qn as OPT_DISCLOSURE_CONTENT, qt as TabList$1, rr as OPT_INPUT, rt as TagInput, s as Pagination, sn as Switch, sr as OPT_SURFACE_POPOVER, t as TextTruncate, tn as CompositeProvider, tr as OPT_FOCUS_VISIBLE, tt as DEFAULT_PRESETS, u as ResizablePanelGroup, un as ToolbarButton$1, ur as OPT_TD, ut as PopoverRoot$1, v as OPT_UI_DEFAULT_LOCALE, vn as Spinner, vr as _objectWithoutProperties, vt as MenuPopover$1, wt as ComboboxEmpty, xn as EmptyState, xt as MenuTrigger$1, y as formatOptDateTime, yn as Badge, yr as Kbd, yt as MenuRoot$1, z as FormRadioGroup$1, zt as DialogPanel$1 } from "./text-truncate-B-8nJgnu.js";
|
|
3
3
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
import * as React from "react";
|
|
5
5
|
import { Fragment as Fragment$1, createContext, forwardRef, memo, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
6
6
|
import { CommandEmpty, CommandGroup, CommandInput, CommandItem, CommandList, CommandRoot, CommandSeparator, Radio, RadioGroupRoot } from "@reopt-ai/opt-ui-primitives";
|
|
7
|
-
import { ArrowDown, ArrowUp, ArrowUpDown, Braces, ChartLine, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, CircleAlert, CircleCheck, Clock, Columns2, Columns3, Expand, Eye, Filter, Info, Minus, PanelLeftClose, PanelLeftOpen, Play, Plus, RefreshCw, Save, Search, SearchIcon, Square, Table2, TextCursorInput, Trash2, TriangleAlert, Undo2, X, ZoomIn, ZoomOut } from "lucide-react";
|
|
7
|
+
import { ArrowDown, ArrowUp, ArrowUpDown, Braces, ChartLine, ChevronDown, ChevronLeft, ChevronRight, ChevronUp, CircleAlert, CircleCheck, Clock, Columns2, Columns3, Expand, Eye, FastForward, Filter, Info, Maximize2, Minimize2, Minus, PanelLeftClose, PanelLeftOpen, PanelRightClose, PanelRightOpen, Pause, Play, Plus, RefreshCw, Rewind, RotateCcw, Save, Search, SearchIcon, SkipBack, Square, Table2, TextCursorInput, Trash2, TriangleAlert, Undo2, X, ZoomIn, ZoomOut } from "lucide-react";
|
|
8
8
|
import { generatePalette } from "@reopt-ai/opt-palette";
|
|
9
9
|
import { AreaChart, BarChart, LineChart, PieChart, TimeSeriesChart, formatCountUnit, formatDuration, formatInstant, isDateOnly, isExactMinutes, isRelativeToNow, measureTimeWindow, parseRelativeParts, parseTimeExpression, resolveTimeRange, shiftTimeRange, toAbsoluteRange, zoomTimeRange } from "@reopt-ai/opt-charts";
|
|
10
10
|
import { matchSorter } from "match-sorter";
|
|
@@ -277,7 +277,7 @@ function CommandGroup$1(_ref5) {
|
|
|
277
277
|
className: "text-text-tertiary px-2 py-1.5 text-xs font-medium",
|
|
278
278
|
children: heading
|
|
279
279
|
}) : void 0,
|
|
280
|
-
className: cn("py-1", className)
|
|
280
|
+
className: cn("py-1 [&:not(:has([data-slot=command-item]))]:hidden", className)
|
|
281
281
|
}, props), {}, {
|
|
282
282
|
"data-opt-id": "JPK1Y",
|
|
283
283
|
"data-opt-slug": "command.CommandGroup"
|
|
@@ -358,16 +358,20 @@ function CommandPalette({ commands, open, onOpenChange, onSelect, inline = false
|
|
|
358
358
|
"aria-label": resolvedLabels.list,
|
|
359
359
|
children: [/* @__PURE__ */ jsx(CommandEmpty$1, { children: empty !== null && empty !== void 0 ? empty : resolvedLabels.empty }), hasCommands ? Array.from(grouped.entries()).map(([group, items]) => /* @__PURE__ */ jsx(CommandGroup$1, {
|
|
360
360
|
heading: group,
|
|
361
|
-
children: items.map((cmd) =>
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
cmd.
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
361
|
+
children: items.map((cmd) => {
|
|
362
|
+
var _cmd$keywords;
|
|
363
|
+
return /* @__PURE__ */ jsxs(CommandItem$1, {
|
|
364
|
+
value: cmd.id,
|
|
365
|
+
keywords: [
|
|
366
|
+
cmd.label,
|
|
367
|
+
cmd.group,
|
|
368
|
+
...(_cmd$keywords = cmd.keywords) !== null && _cmd$keywords !== void 0 ? _cmd$keywords : [],
|
|
369
|
+
...cmd.shortcut ? [cmd.shortcut] : []
|
|
370
|
+
],
|
|
371
|
+
onSelect: handleSelect,
|
|
372
|
+
children: [/* @__PURE__ */ jsx("span", { children: cmd.label }), cmd.shortcut && /* @__PURE__ */ jsx(CommandShortcut, { children: cmd.shortcut })]
|
|
373
|
+
}, cmd.id);
|
|
374
|
+
})
|
|
371
375
|
}, group)) : null]
|
|
372
376
|
}),
|
|
373
377
|
footer
|
|
@@ -457,9 +461,65 @@ function DashboardGrid({ stats, columns = 3, labels: customLabels }) {
|
|
|
457
461
|
});
|
|
458
462
|
}
|
|
459
463
|
//#endregion
|
|
464
|
+
//#region src/lib/use-async-search.ts
|
|
465
|
+
/** Debounced search with explicit failure and latest-request-only results. */
|
|
466
|
+
function useAsyncSearch({ query, search, enabled, debounceMs }) {
|
|
467
|
+
const [results, setResults] = useState([]);
|
|
468
|
+
const [loading, setLoading] = useState(false);
|
|
469
|
+
const [failed, setFailed] = useState(false);
|
|
470
|
+
const [attempt, setAttempt] = useState(0);
|
|
471
|
+
const generationRef = useRef(0);
|
|
472
|
+
const timerRef = useRef(void 0);
|
|
473
|
+
const invalidate = useCallback(() => {
|
|
474
|
+
generationRef.current += 1;
|
|
475
|
+
clearTimeout(timerRef.current);
|
|
476
|
+
setResults([]);
|
|
477
|
+
setLoading(false);
|
|
478
|
+
setFailed(false);
|
|
479
|
+
}, []);
|
|
480
|
+
useEffect(() => {
|
|
481
|
+
const generation = ++generationRef.current;
|
|
482
|
+
setResults([]);
|
|
483
|
+
setFailed(false);
|
|
484
|
+
setLoading(enabled && Boolean(search));
|
|
485
|
+
if (enabled && search) timerRef.current = setTimeout(async () => {
|
|
486
|
+
try {
|
|
487
|
+
const items = await search(query);
|
|
488
|
+
if (generationRef.current === generation) setResults(items);
|
|
489
|
+
} catch (_unused) {
|
|
490
|
+
if (generationRef.current === generation) setFailed(true);
|
|
491
|
+
} finally {
|
|
492
|
+
if (generationRef.current === generation) setLoading(false);
|
|
493
|
+
}
|
|
494
|
+
}, debounceMs);
|
|
495
|
+
return () => {
|
|
496
|
+
generationRef.current += 1;
|
|
497
|
+
clearTimeout(timerRef.current);
|
|
498
|
+
};
|
|
499
|
+
}, [
|
|
500
|
+
query,
|
|
501
|
+
search,
|
|
502
|
+
enabled,
|
|
503
|
+
debounceMs,
|
|
504
|
+
attempt
|
|
505
|
+
]);
|
|
506
|
+
return {
|
|
507
|
+
results,
|
|
508
|
+
loading,
|
|
509
|
+
failed,
|
|
510
|
+
retry: useCallback(() => {
|
|
511
|
+
invalidate();
|
|
512
|
+
setAttempt((current) => current + 1);
|
|
513
|
+
}, [invalidate]),
|
|
514
|
+
invalidate
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
//#endregion
|
|
460
518
|
//#region src/shells/search-combobox.tsx
|
|
461
519
|
const defaultLabels$33 = {
|
|
462
520
|
noResults: "결과가 없습니다.",
|
|
521
|
+
error: "검색에 실패했습니다. 다시 시도해주세요.",
|
|
522
|
+
retry: "다시 시도",
|
|
463
523
|
loading: "Loading…"
|
|
464
524
|
};
|
|
465
525
|
/** Renders the search combobox component. */
|
|
@@ -467,10 +527,13 @@ function SearchCombobox({ items, placeholder = "검색...", label = "검색", on
|
|
|
467
527
|
const labels = _objectSpread2(_objectSpread2({}, defaultLabels$33), customLabels);
|
|
468
528
|
const inputId = useId();
|
|
469
529
|
const [query, setQuery] = useState("");
|
|
470
|
-
const
|
|
471
|
-
const
|
|
472
|
-
|
|
473
|
-
|
|
530
|
+
const inputRef = useRef(null);
|
|
531
|
+
const { results: asyncResults, loading, failed, retry, invalidate } = useAsyncSearch({
|
|
532
|
+
query,
|
|
533
|
+
search: onSearch,
|
|
534
|
+
enabled: isAsync && Boolean(query),
|
|
535
|
+
debounceMs
|
|
536
|
+
});
|
|
474
537
|
const syncMatches = useMemo(() => {
|
|
475
538
|
if (isAsync) return [];
|
|
476
539
|
if (!query) return items;
|
|
@@ -481,45 +544,15 @@ function SearchCombobox({ items, placeholder = "검색...", label = "검색", on
|
|
|
481
544
|
isAsync
|
|
482
545
|
]);
|
|
483
546
|
const handleInput = (event) => {
|
|
484
|
-
|
|
547
|
+
inputRef.current = event.currentTarget;
|
|
548
|
+
invalidate();
|
|
485
549
|
setQuery(event.currentTarget.value);
|
|
486
550
|
};
|
|
487
551
|
const handleValueChange = (item) => {
|
|
488
|
-
|
|
552
|
+
invalidate();
|
|
489
553
|
setQuery(item);
|
|
490
554
|
onSelect === null || onSelect === void 0 || onSelect(item);
|
|
491
555
|
};
|
|
492
|
-
useEffect(() => {
|
|
493
|
-
const generation = ++searchGenerationRef.current;
|
|
494
|
-
if (timerRef.current) clearTimeout(timerRef.current);
|
|
495
|
-
if (!isAsync || !onSearch) {
|
|
496
|
-
setLoading(false);
|
|
497
|
-
return;
|
|
498
|
-
}
|
|
499
|
-
if (!query) {
|
|
500
|
-
setAsyncResults([]);
|
|
501
|
-
setLoading(false);
|
|
502
|
-
return;
|
|
503
|
-
}
|
|
504
|
-
setLoading(true);
|
|
505
|
-
timerRef.current = setTimeout(async () => {
|
|
506
|
-
try {
|
|
507
|
-
const results = await onSearch(query);
|
|
508
|
-
if (searchGenerationRef.current !== generation) return;
|
|
509
|
-
setAsyncResults(results);
|
|
510
|
-
} finally {
|
|
511
|
-
if (searchGenerationRef.current === generation) setLoading(false);
|
|
512
|
-
}
|
|
513
|
-
}, debounceMs);
|
|
514
|
-
return () => {
|
|
515
|
-
if (timerRef.current) clearTimeout(timerRef.current);
|
|
516
|
-
};
|
|
517
|
-
}, [
|
|
518
|
-
query,
|
|
519
|
-
isAsync,
|
|
520
|
-
onSearch,
|
|
521
|
-
debounceMs
|
|
522
|
-
]);
|
|
523
556
|
const matches = isAsync ? asyncResults : syncMatches;
|
|
524
557
|
return /* @__PURE__ */ jsx("div", {
|
|
525
558
|
className: "gap-element flex flex-col",
|
|
@@ -533,29 +566,42 @@ function SearchCombobox({ items, placeholder = "검색...", label = "검색", on
|
|
|
533
566
|
className: OPT_TEXT_LABEL,
|
|
534
567
|
children: label
|
|
535
568
|
}),
|
|
536
|
-
/* @__PURE__ */ jsx(ComboboxInput$1, {
|
|
569
|
+
/* @__PURE__ */ jsx(ComboboxInput$1, _objectSpread2({
|
|
537
570
|
id: inputId,
|
|
538
571
|
placeholder,
|
|
539
572
|
className: "max-w-xs",
|
|
540
573
|
"aria-busy": loading || void 0,
|
|
541
574
|
onInput: handleInput
|
|
542
|
-
}
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
575
|
+
}, failed ? {
|
|
576
|
+
"aria-expanded": false,
|
|
577
|
+
"aria-controls": void 0
|
|
578
|
+
} : {})),
|
|
579
|
+
/* @__PURE__ */ jsx(ComboboxPopover$1, _objectSpread2(_objectSpread2({ className: "max-w-xs" }, failed ? { role: "presentation" } : {}), {}, { children: isAsync && failed ? /* @__PURE__ */ jsxs("div", {
|
|
580
|
+
role: "alert",
|
|
581
|
+
className: "gap-element flex flex-col px-3 py-2 text-sm",
|
|
582
|
+
children: [/* @__PURE__ */ jsx("span", { children: labels.error }), /* @__PURE__ */ jsx(Button, {
|
|
583
|
+
variant: "secondary",
|
|
584
|
+
size: "sm",
|
|
585
|
+
onClick: () => {
|
|
586
|
+
var _inputRef$current;
|
|
587
|
+
retry();
|
|
588
|
+
(_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 || _inputRef$current.focus();
|
|
589
|
+
},
|
|
590
|
+
children: labels.retry
|
|
591
|
+
})]
|
|
592
|
+
}) : isAsync && loading ? /* @__PURE__ */ jsxs("div", {
|
|
593
|
+
className: "text-text-secondary gap-element flex items-center px-3 py-2 text-sm",
|
|
594
|
+
role: "status",
|
|
595
|
+
"aria-live": "polite",
|
|
596
|
+
"aria-busy": "true",
|
|
597
|
+
children: [/* @__PURE__ */ jsx(Spinner, {
|
|
598
|
+
size: "sm",
|
|
599
|
+
"aria-hidden": "true"
|
|
600
|
+
}), labels.loading]
|
|
601
|
+
}) : matches.length === 0 ? /* @__PURE__ */ jsx(ComboboxEmpty, { children: labels.noResults }) : matches.map((item) => /* @__PURE__ */ jsx(ComboboxItem$1, {
|
|
602
|
+
value: item,
|
|
603
|
+
children: item
|
|
604
|
+
}, item)) }))
|
|
559
605
|
]
|
|
560
606
|
})
|
|
561
607
|
});
|
|
@@ -900,13 +946,16 @@ function FaqAccordion({ items, labels: customLabels }) {
|
|
|
900
946
|
//#region src/shells/settings-form.tsx
|
|
901
947
|
const defaultLabels$26 = {
|
|
902
948
|
formAriaLabel: "설정 폼",
|
|
949
|
+
submitError: "저장에 실패했습니다. 다시 시도해주세요.",
|
|
903
950
|
submitButton: "저장",
|
|
904
951
|
successMessage: "설정이 저장되었습니다."
|
|
905
952
|
};
|
|
906
953
|
/** Renders the settings form component. */
|
|
907
954
|
function SettingsForm({ fields, onSubmit, labels: customLabels }) {
|
|
955
|
+
var _labels$submitError;
|
|
908
956
|
const labels = _objectSpread2(_objectSpread2({}, defaultLabels$26), customLabels);
|
|
909
957
|
const [submitted, setSubmitted] = useState(false);
|
|
958
|
+
const formId = useId();
|
|
910
959
|
const defaultValues = {};
|
|
911
960
|
for (const field of fields) {
|
|
912
961
|
var _field$defaultValue;
|
|
@@ -932,9 +981,10 @@ function SettingsForm({ fields, onSubmit, labels: customLabels }) {
|
|
|
932
981
|
}
|
|
933
982
|
return errors;
|
|
934
983
|
},
|
|
935
|
-
onSubmit: (values) => {
|
|
984
|
+
onSubmit: async (values) => {
|
|
985
|
+
setSubmitted(false);
|
|
986
|
+
await (onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(values));
|
|
936
987
|
setSubmitted(true);
|
|
937
|
-
onSubmit === null || onSubmit === void 0 || onSubmit(values);
|
|
938
988
|
}
|
|
939
989
|
});
|
|
940
990
|
if (fields.length === 0) return /* @__PURE__ */ jsx(EmptyState, {
|
|
@@ -947,138 +997,142 @@ function SettingsForm({ fields, onSubmit, labels: customLabels }) {
|
|
|
947
997
|
className: "gap-group flex flex-col",
|
|
948
998
|
"data-opt-id": "9TEEJ",
|
|
949
999
|
"data-opt-slug": "settings-form.SettingsForm",
|
|
950
|
-
children: [
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
/* @__PURE__ */ jsx(
|
|
966
|
-
|
|
967
|
-
children: field.label
|
|
968
|
-
}),
|
|
969
|
-
/* @__PURE__ */ jsx(TagInput, {
|
|
1000
|
+
children: [
|
|
1001
|
+
/* @__PURE__ */ jsxs(FormRoot$1, {
|
|
1002
|
+
store: form,
|
|
1003
|
+
"aria-label": labels.formAriaLabel,
|
|
1004
|
+
children: [fields.map((field) => {
|
|
1005
|
+
var _field$type;
|
|
1006
|
+
if (field.visible !== void 0) {
|
|
1007
|
+
const currentValues = form.getValues();
|
|
1008
|
+
if (!(typeof field.visible === "function" ? field.visible(currentValues) : field.visible)) return null;
|
|
1009
|
+
}
|
|
1010
|
+
if (field.type === "tags") {
|
|
1011
|
+
const rawValue = form.getValue(field.name);
|
|
1012
|
+
const tags = Array.isArray(rawValue) ? rawValue : [];
|
|
1013
|
+
return /* @__PURE__ */ jsx(FormField$1, {
|
|
1014
|
+
name: field.name,
|
|
1015
|
+
children: /* @__PURE__ */ jsx(TagInput, {
|
|
1016
|
+
label: field.label,
|
|
970
1017
|
value: tags,
|
|
971
1018
|
onChange: (next) => form.setValue(field.name, next),
|
|
972
|
-
placeholder: field.placeholder
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1019
|
+
placeholder: field.placeholder,
|
|
1020
|
+
error: form.getFieldTouched(field.name) ? form.getError(field.name) : void 0
|
|
1021
|
+
})
|
|
1022
|
+
}, field.name);
|
|
1023
|
+
}
|
|
1024
|
+
if (field.type === "radio") {
|
|
1025
|
+
var _field$options;
|
|
1026
|
+
return /* @__PURE__ */ jsxs("fieldset", {
|
|
1027
|
+
className: "gap-element flex flex-col",
|
|
1028
|
+
children: [
|
|
1029
|
+
/* @__PURE__ */ jsx("legend", {
|
|
1030
|
+
className: "text-text-primary text-sm font-medium",
|
|
1031
|
+
children: field.label
|
|
1032
|
+
}),
|
|
1033
|
+
/* @__PURE__ */ jsx(FormRadioGroup$1, { children: /* @__PURE__ */ jsx("div", {
|
|
1034
|
+
className: "gap-group flex",
|
|
1035
|
+
children: (_field$options = field.options) === null || _field$options === void 0 ? void 0 : _field$options.map((option) => /* @__PURE__ */ jsxs("label", {
|
|
1036
|
+
className: "text-text-primary gap-element flex items-center text-sm",
|
|
1037
|
+
children: [/* @__PURE__ */ jsx(FormRadio$1, {
|
|
1038
|
+
name: field.name,
|
|
1039
|
+
value: option.value
|
|
1040
|
+
}), option.label]
|
|
1041
|
+
}, option.value))
|
|
1042
|
+
}) }),
|
|
1043
|
+
/* @__PURE__ */ jsx(FormError$1, { name: field.name })
|
|
1044
|
+
]
|
|
1045
|
+
}, field.name);
|
|
1046
|
+
}
|
|
1047
|
+
if (field.type === "checkbox") return /* @__PURE__ */ jsxs(FormField$1, {
|
|
1048
|
+
name: field.name,
|
|
1049
|
+
children: [/* @__PURE__ */ jsxs("label", {
|
|
1050
|
+
className: "text-text-primary gap-element flex items-center text-sm",
|
|
1051
|
+
children: [/* @__PURE__ */ jsx(FormCheckbox$1, { name: field.name }), /* @__PURE__ */ jsx("span", { children: field.label })]
|
|
1052
|
+
}), /* @__PURE__ */ jsx(FormError$1, { name: field.name })]
|
|
976
1053
|
}, field.name);
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
1054
|
+
if (field.type === "textarea") {
|
|
1055
|
+
var _field$rows, _field$resize;
|
|
1056
|
+
return /* @__PURE__ */ jsxs(FormField$1, {
|
|
1057
|
+
name: field.name,
|
|
1058
|
+
children: [
|
|
1059
|
+
/* @__PURE__ */ jsx(FormLabel$1, { children: field.label }),
|
|
1060
|
+
/* @__PURE__ */ jsx(FormTextarea$1, {
|
|
1061
|
+
name: field.name,
|
|
1062
|
+
placeholder: field.placeholder,
|
|
1063
|
+
rows: (_field$rows = field.rows) !== null && _field$rows !== void 0 ? _field$rows : 4,
|
|
1064
|
+
resize: (_field$resize = field.resize) !== null && _field$resize !== void 0 ? _field$resize : "vertical",
|
|
1065
|
+
className: field.font === "mono" ? "font-mono text-sm" : void 0
|
|
1066
|
+
}),
|
|
1067
|
+
/* @__PURE__ */ jsx(FormError$1, { name: field.name })
|
|
1068
|
+
]
|
|
1069
|
+
}, field.name);
|
|
1070
|
+
}
|
|
1071
|
+
if (field.type === "select") {
|
|
1072
|
+
var _field$options2;
|
|
1073
|
+
return /* @__PURE__ */ jsxs(FormField$1, {
|
|
1074
|
+
name: field.name,
|
|
1075
|
+
children: [
|
|
1076
|
+
/* @__PURE__ */ jsx(FormLabel$1, {
|
|
1077
|
+
htmlFor: `${formId}-${field.name}`,
|
|
1078
|
+
children: field.label
|
|
1079
|
+
}),
|
|
1080
|
+
/* @__PURE__ */ jsx(FormSelect, {
|
|
1081
|
+
name: field.name,
|
|
1082
|
+
id: `${formId}-${field.name}`,
|
|
1083
|
+
"aria-label": field.label,
|
|
1084
|
+
"aria-describedby": form.getError(field.name) && form.getFieldTouched(field.name) ? `${formId}-${field.name}-error` : void 0,
|
|
1085
|
+
placeholder: field.placeholder,
|
|
1086
|
+
children: /* @__PURE__ */ jsx(FormSelectPopover, { children: (_field$options2 = field.options) === null || _field$options2 === void 0 ? void 0 : _field$options2.map((option) => /* @__PURE__ */ jsx(FormSelectItem, {
|
|
1087
|
+
value: option.value,
|
|
1088
|
+
children: option.label
|
|
1089
|
+
}, option.value)) })
|
|
1090
|
+
}),
|
|
1091
|
+
/* @__PURE__ */ jsx(FormError$1, {
|
|
1092
|
+
name: field.name,
|
|
1093
|
+
id: `${formId}-${field.name}-error`
|
|
1094
|
+
})
|
|
1095
|
+
]
|
|
1096
|
+
}, field.name);
|
|
1097
|
+
}
|
|
1098
|
+
if (field.type === "switch") return /* @__PURE__ */ jsxs("label", {
|
|
1099
|
+
className: "gap-group flex items-center",
|
|
1100
|
+
children: [/* @__PURE__ */ jsx(FormSwitch$1, { name: field.name }), /* @__PURE__ */ jsxs("div", {
|
|
1101
|
+
className: "flex flex-col",
|
|
1102
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
984
1103
|
className: "text-text-primary text-sm font-medium",
|
|
985
1104
|
children: field.label
|
|
986
|
-
}),
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
children: [/* @__PURE__ */ jsx(FormRadio$1, {
|
|
992
|
-
name: field.name,
|
|
993
|
-
value: option.value
|
|
994
|
-
}), option.label]
|
|
995
|
-
}, option.value))
|
|
996
|
-
}) }),
|
|
997
|
-
/* @__PURE__ */ jsx(FormError$1, { name: field.name })
|
|
998
|
-
]
|
|
999
|
-
}, field.name);
|
|
1000
|
-
}
|
|
1001
|
-
if (field.type === "checkbox") return /* @__PURE__ */ jsxs("div", {
|
|
1002
|
-
className: "gap-element flex flex-col",
|
|
1003
|
-
children: [/* @__PURE__ */ jsxs("label", {
|
|
1004
|
-
className: "text-text-primary gap-element flex items-center text-sm",
|
|
1005
|
-
children: [/* @__PURE__ */ jsx(FormCheckbox$1, { name: field.name }), /* @__PURE__ */ jsx("span", { children: field.label })]
|
|
1006
|
-
}), /* @__PURE__ */ jsx(FormError$1, { name: field.name })]
|
|
1007
|
-
}, field.name);
|
|
1008
|
-
if (field.type === "textarea") {
|
|
1009
|
-
var _field$rows, _field$resize;
|
|
1010
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1011
|
-
className: "gap-element flex flex-col",
|
|
1012
|
-
children: [
|
|
1013
|
-
/* @__PURE__ */ jsx(FormLabel$1, {
|
|
1014
|
-
name: field.name,
|
|
1015
|
-
children: field.label
|
|
1016
|
-
}),
|
|
1017
|
-
/* @__PURE__ */ jsx(FormTextarea, {
|
|
1018
|
-
name: field.name,
|
|
1019
|
-
placeholder: field.placeholder,
|
|
1020
|
-
rows: (_field$rows = field.rows) !== null && _field$rows !== void 0 ? _field$rows : 4,
|
|
1021
|
-
resize: (_field$resize = field.resize) !== null && _field$resize !== void 0 ? _field$resize : "vertical",
|
|
1022
|
-
className: field.font === "mono" ? "font-mono text-sm" : void 0
|
|
1023
|
-
}),
|
|
1024
|
-
/* @__PURE__ */ jsx(FormError$1, { name: field.name })
|
|
1025
|
-
]
|
|
1105
|
+
}), field.description && /* @__PURE__ */ jsx("span", {
|
|
1106
|
+
className: "text-text-secondary text-sm",
|
|
1107
|
+
children: field.description
|
|
1108
|
+
})]
|
|
1109
|
+
})]
|
|
1026
1110
|
}, field.name);
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
var _field$options2;
|
|
1030
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1031
|
-
className: "gap-element flex flex-col",
|
|
1111
|
+
return /* @__PURE__ */ jsxs(FormField$1, {
|
|
1112
|
+
name: field.name,
|
|
1032
1113
|
children: [
|
|
1033
|
-
/* @__PURE__ */ jsx(FormLabel$1, {
|
|
1114
|
+
/* @__PURE__ */ jsx(FormLabel$1, { children: field.label }),
|
|
1115
|
+
/* @__PURE__ */ jsx(FormInput$1, {
|
|
1034
1116
|
name: field.name,
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
/* @__PURE__ */ jsx(FormSelect, {
|
|
1038
|
-
name: field.name,
|
|
1039
|
-
placeholder: field.placeholder,
|
|
1040
|
-
children: /* @__PURE__ */ jsx(FormSelectPopover, { children: (_field$options2 = field.options) === null || _field$options2 === void 0 ? void 0 : _field$options2.map((option) => /* @__PURE__ */ jsx(FormSelectItem, {
|
|
1041
|
-
value: option.value,
|
|
1042
|
-
children: option.label
|
|
1043
|
-
}, option.value)) })
|
|
1117
|
+
type: (_field$type = field.type) !== null && _field$type !== void 0 ? _field$type : "text",
|
|
1118
|
+
placeholder: field.placeholder
|
|
1044
1119
|
}),
|
|
1045
1120
|
/* @__PURE__ */ jsx(FormError$1, { name: field.name })
|
|
1046
1121
|
]
|
|
1047
1122
|
}, field.name);
|
|
1048
|
-
}
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
}, field.name);
|
|
1062
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
1063
|
-
className: "gap-element flex flex-col",
|
|
1064
|
-
children: [
|
|
1065
|
-
/* @__PURE__ */ jsx(FormLabel$1, {
|
|
1066
|
-
name: field.name,
|
|
1067
|
-
children: field.label
|
|
1068
|
-
}),
|
|
1069
|
-
/* @__PURE__ */ jsx(FormInput$1, {
|
|
1070
|
-
name: field.name,
|
|
1071
|
-
type: (_field$type = field.type) !== null && _field$type !== void 0 ? _field$type : "text",
|
|
1072
|
-
placeholder: field.placeholder
|
|
1073
|
-
}),
|
|
1074
|
-
/* @__PURE__ */ jsx(FormError$1, { name: field.name })
|
|
1075
|
-
]
|
|
1076
|
-
}, field.name);
|
|
1077
|
-
}), /* @__PURE__ */ jsx(FormSubmit$1, { children: labels.submitButton })]
|
|
1078
|
-
}), submitted && /* @__PURE__ */ jsx("div", {
|
|
1079
|
-
className: "border-success bg-success-subtle text-success-fg rounded-lg border p-3 text-sm",
|
|
1080
|
-
children: labels.successMessage
|
|
1081
|
-
})]
|
|
1123
|
+
}), /* @__PURE__ */ jsx(FormSubmit$1, { children: labels.submitButton })]
|
|
1124
|
+
}),
|
|
1125
|
+
form.getSubmitError() !== void 0 && /* @__PURE__ */ jsx("div", {
|
|
1126
|
+
role: "alert",
|
|
1127
|
+
className: "border-danger bg-danger-subtle text-danger-fg rounded-lg border p-3 text-sm",
|
|
1128
|
+
children: (_labels$submitError = labels.submitError) !== null && _labels$submitError !== void 0 ? _labels$submitError : "처리에 실패했습니다. 다시 시도해주세요."
|
|
1129
|
+
}),
|
|
1130
|
+
submitted && /* @__PURE__ */ jsx("div", {
|
|
1131
|
+
role: "status",
|
|
1132
|
+
className: "border-success bg-success-subtle text-success-fg rounded-lg border p-3 text-sm",
|
|
1133
|
+
children: labels.successMessage
|
|
1134
|
+
})
|
|
1135
|
+
]
|
|
1082
1136
|
});
|
|
1083
1137
|
}
|
|
1084
1138
|
//#endregion
|
|
@@ -8576,4 +8630,650 @@ function FieldValues({ values, loading, total, sampledRows, onFilter, labels })
|
|
|
8576
8630
|
});
|
|
8577
8631
|
}
|
|
8578
8632
|
//#endregion
|
|
8579
|
-
|
|
8633
|
+
//#region src/shells/replay-scrubber.tsx
|
|
8634
|
+
const MARKER_MERGE_PX = 14;
|
|
8635
|
+
function defaultFormatTime(offset) {
|
|
8636
|
+
const total = Math.max(0, Math.floor(offset / 1e3));
|
|
8637
|
+
const minutes = Math.floor(total / 60);
|
|
8638
|
+
const seconds = total % 60;
|
|
8639
|
+
return `${minutes}:${String(seconds).padStart(2, "0")}`;
|
|
8640
|
+
}
|
|
8641
|
+
const DEFAULT_SEGMENT_NAMES = {
|
|
8642
|
+
active: "Active",
|
|
8643
|
+
inactive: "Inactive",
|
|
8644
|
+
gap: "No data",
|
|
8645
|
+
buffer: "Loading"
|
|
8646
|
+
};
|
|
8647
|
+
const SEGMENT_CLASS = {
|
|
8648
|
+
active: "bg-accent/35",
|
|
8649
|
+
inactive: "bg-bg-muted",
|
|
8650
|
+
gap: "bg-transparent",
|
|
8651
|
+
buffer: "bg-[repeating-linear-gradient(45deg,transparent_0,transparent_3px,var(--color-border)_3px,var(--color-border)_6px)]"
|
|
8652
|
+
};
|
|
8653
|
+
const MARKER_CLASS = {
|
|
8654
|
+
default: "bg-text-tertiary",
|
|
8655
|
+
accent: "bg-accent",
|
|
8656
|
+
danger: "bg-danger",
|
|
8657
|
+
warning: "bg-warning"
|
|
8658
|
+
};
|
|
8659
|
+
/**
|
|
8660
|
+
* Merge markers that would render within `mergePx` of each other. Each group
|
|
8661
|
+
* anchors on its first marker; the tooltip lists every member.
|
|
8662
|
+
*/
|
|
8663
|
+
function groupReplayMarkers(markers, start, end, widthPx, mergePx = MARKER_MERGE_PX) {
|
|
8664
|
+
const span = Math.max(1, end - start);
|
|
8665
|
+
const threshold = widthPx > 0 ? mergePx / widthPx * span : 0;
|
|
8666
|
+
const sorted = [...markers].filter((marker) => marker.time >= start && marker.time <= end).sort((a, b) => a.time - b.time);
|
|
8667
|
+
const groups = [];
|
|
8668
|
+
for (const marker of sorted) {
|
|
8669
|
+
var _marker$tone;
|
|
8670
|
+
const last = groups[groups.length - 1];
|
|
8671
|
+
if (last && marker.time - last.time <= threshold) {
|
|
8672
|
+
last.items.push(marker);
|
|
8673
|
+
if (marker.tone === "danger") last.tone = "danger";
|
|
8674
|
+
else if (marker.tone === "warning" && last.tone !== "danger") last.tone = "warning";
|
|
8675
|
+
continue;
|
|
8676
|
+
}
|
|
8677
|
+
groups.push({
|
|
8678
|
+
key: marker.id,
|
|
8679
|
+
time: marker.time,
|
|
8680
|
+
items: [marker],
|
|
8681
|
+
tone: (_marker$tone = marker.tone) !== null && _marker$tone !== void 0 ? _marker$tone : "default"
|
|
8682
|
+
});
|
|
8683
|
+
}
|
|
8684
|
+
return groups;
|
|
8685
|
+
}
|
|
8686
|
+
/** Build the SVG area path for the activity band (log scale, like a seismograph). */
|
|
8687
|
+
function activityPath(activity) {
|
|
8688
|
+
if (!activity.length) return "";
|
|
8689
|
+
const n = activity.length;
|
|
8690
|
+
return `M 0,100 ${activity.map((value, index) => {
|
|
8691
|
+
const x = n === 1 ? 50 : index / (n - 1) * 100;
|
|
8692
|
+
const y = 100 - Math.log1p(Math.min(1, Math.max(0, value)) * 9) / Math.log(10) * 100;
|
|
8693
|
+
return `L ${x.toFixed(2)},${y.toFixed(2)}`;
|
|
8694
|
+
}).join(" ")} L 100,100 Z`;
|
|
8695
|
+
}
|
|
8696
|
+
/**
|
|
8697
|
+
* ReplayScrubber — the timeline of a session recording.
|
|
8698
|
+
*
|
|
8699
|
+
* A plain `Slider` knows only a position. A recording also has stretches
|
|
8700
|
+
* where nothing happened, stretches that have not loaded, events worth
|
|
8701
|
+
* jumping to, and a rhythm of activity. This track layers all of those
|
|
8702
|
+
* under one playhead and commits a seek only when the interaction ends,
|
|
8703
|
+
* so dragging never floods the player.
|
|
8704
|
+
*/
|
|
8705
|
+
function ReplayScrubber({ start, end, position, segments = [], buffered = [], activity, markers = [], onSeek, onPreview, onMarkerSelect, formatTime, stepMs = 5e3, largeStepMs = 3e4, disabled = false, labels, className }) {
|
|
8706
|
+
var _labels$track, _labels$markerGroup;
|
|
8707
|
+
const id = useId();
|
|
8708
|
+
const trackRef = useRef(null);
|
|
8709
|
+
const [width, setWidth] = useState(0);
|
|
8710
|
+
const [hover, setHover] = useState(null);
|
|
8711
|
+
const [dragging, setDragging] = useState(null);
|
|
8712
|
+
const span = Math.max(1, end - start);
|
|
8713
|
+
const clamp = useCallback((time) => Math.min(end, Math.max(start, time)), [start, end]);
|
|
8714
|
+
const format = useCallback((time) => formatTime ? formatTime(time) : defaultFormatTime(time - start), [formatTime, start]);
|
|
8715
|
+
const shown = clamp(dragging !== null && dragging !== void 0 ? dragging : position);
|
|
8716
|
+
const percent = (shown - start) / span * 100;
|
|
8717
|
+
useEffect(() => {
|
|
8718
|
+
const node = trackRef.current;
|
|
8719
|
+
if (!node || typeof ResizeObserver === "undefined") {
|
|
8720
|
+
var _node$clientWidth;
|
|
8721
|
+
setWidth((_node$clientWidth = node === null || node === void 0 ? void 0 : node.clientWidth) !== null && _node$clientWidth !== void 0 ? _node$clientWidth : 0);
|
|
8722
|
+
return;
|
|
8723
|
+
}
|
|
8724
|
+
const observer = new ResizeObserver((entries) => {
|
|
8725
|
+
const entry = entries[0];
|
|
8726
|
+
if (entry) setWidth(entry.contentRect.width);
|
|
8727
|
+
});
|
|
8728
|
+
observer.observe(node);
|
|
8729
|
+
setWidth(node.clientWidth);
|
|
8730
|
+
return () => observer.disconnect();
|
|
8731
|
+
}, []);
|
|
8732
|
+
const timeAt = useCallback((clientX) => {
|
|
8733
|
+
var _trackRef$current;
|
|
8734
|
+
const rect = (_trackRef$current = trackRef.current) === null || _trackRef$current === void 0 ? void 0 : _trackRef$current.getBoundingClientRect();
|
|
8735
|
+
if (!rect || rect.width === 0) return start;
|
|
8736
|
+
return start + Math.min(1, Math.max(0, (clientX - rect.left) / rect.width)) * span;
|
|
8737
|
+
}, [start, span]);
|
|
8738
|
+
const handlePointerDown = (event) => {
|
|
8739
|
+
var _event$currentTarget$, _event$currentTarget;
|
|
8740
|
+
if (disabled || event.button !== 0) return;
|
|
8741
|
+
event.preventDefault();
|
|
8742
|
+
(_event$currentTarget$ = (_event$currentTarget = event.currentTarget).setPointerCapture) === null || _event$currentTarget$ === void 0 || _event$currentTarget$.call(_event$currentTarget, event.pointerId);
|
|
8743
|
+
event.currentTarget.focus();
|
|
8744
|
+
const time = timeAt(event.clientX);
|
|
8745
|
+
setDragging(time);
|
|
8746
|
+
onPreview === null || onPreview === void 0 || onPreview(time);
|
|
8747
|
+
};
|
|
8748
|
+
const handlePointerMove = (event) => {
|
|
8749
|
+
if (disabled) return;
|
|
8750
|
+
const time = timeAt(event.clientX);
|
|
8751
|
+
if (dragging !== null) {
|
|
8752
|
+
setDragging(time);
|
|
8753
|
+
onPreview === null || onPreview === void 0 || onPreview(time);
|
|
8754
|
+
} else {
|
|
8755
|
+
setHover(time);
|
|
8756
|
+
onPreview === null || onPreview === void 0 || onPreview(time);
|
|
8757
|
+
}
|
|
8758
|
+
};
|
|
8759
|
+
const handlePointerUp = (event) => {
|
|
8760
|
+
var _event$currentTarget$2, _event$currentTarget2;
|
|
8761
|
+
if (dragging === null) return;
|
|
8762
|
+
(_event$currentTarget$2 = (_event$currentTarget2 = event.currentTarget).releasePointerCapture) === null || _event$currentTarget$2 === void 0 || _event$currentTarget$2.call(_event$currentTarget2, event.pointerId);
|
|
8763
|
+
const time = clamp(timeAt(event.clientX));
|
|
8764
|
+
setDragging(null);
|
|
8765
|
+
onSeek(time);
|
|
8766
|
+
};
|
|
8767
|
+
const handlePointerCancel = () => {
|
|
8768
|
+
setDragging(null);
|
|
8769
|
+
onPreview === null || onPreview === void 0 || onPreview(null);
|
|
8770
|
+
};
|
|
8771
|
+
const handlePointerLeave = () => {
|
|
8772
|
+
setHover(null);
|
|
8773
|
+
if (dragging === null) onPreview === null || onPreview === void 0 || onPreview(null);
|
|
8774
|
+
};
|
|
8775
|
+
const handleKeyDown = (event) => {
|
|
8776
|
+
if (disabled) return;
|
|
8777
|
+
let next = null;
|
|
8778
|
+
switch (event.key) {
|
|
8779
|
+
case "ArrowLeft":
|
|
8780
|
+
case "ArrowDown":
|
|
8781
|
+
next = shown - (event.shiftKey ? largeStepMs : stepMs);
|
|
8782
|
+
break;
|
|
8783
|
+
case "ArrowRight":
|
|
8784
|
+
case "ArrowUp":
|
|
8785
|
+
next = shown + (event.shiftKey ? largeStepMs : stepMs);
|
|
8786
|
+
break;
|
|
8787
|
+
case "PageDown":
|
|
8788
|
+
next = shown - largeStepMs;
|
|
8789
|
+
break;
|
|
8790
|
+
case "PageUp":
|
|
8791
|
+
next = shown + largeStepMs;
|
|
8792
|
+
break;
|
|
8793
|
+
case "Home":
|
|
8794
|
+
next = start;
|
|
8795
|
+
break;
|
|
8796
|
+
case "End":
|
|
8797
|
+
next = end;
|
|
8798
|
+
break;
|
|
8799
|
+
default: return;
|
|
8800
|
+
}
|
|
8801
|
+
event.preventDefault();
|
|
8802
|
+
onSeek(clamp(next));
|
|
8803
|
+
};
|
|
8804
|
+
const groups = useMemo(() => groupReplayMarkers(markers, start, end, width), [
|
|
8805
|
+
markers,
|
|
8806
|
+
start,
|
|
8807
|
+
end,
|
|
8808
|
+
width
|
|
8809
|
+
]);
|
|
8810
|
+
const path = useMemo(() => activity ? activityPath(activity) : "", [activity]);
|
|
8811
|
+
const trackLabel = (_labels$track = labels === null || labels === void 0 ? void 0 : labels.track) !== null && _labels$track !== void 0 ? _labels$track : "Replay position";
|
|
8812
|
+
const segmentNames = _objectSpread2(_objectSpread2({}, DEFAULT_SEGMENT_NAMES), labels === null || labels === void 0 ? void 0 : labels.segment);
|
|
8813
|
+
const groupLabel = (_labels$markerGroup = labels === null || labels === void 0 ? void 0 : labels.markerGroup) !== null && _labels$markerGroup !== void 0 ? _labels$markerGroup : ((count) => `${count} events`);
|
|
8814
|
+
const preview = dragging !== null && dragging !== void 0 ? dragging : hover;
|
|
8815
|
+
const previewPercent = preview === null ? null : (clamp(preview) - start) / span * 100;
|
|
8816
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
8817
|
+
className: cn("relative flex w-full flex-col", className),
|
|
8818
|
+
"data-opt-id": "B98JP",
|
|
8819
|
+
"data-opt-slug": "replay-scrubber.ReplayScrubber",
|
|
8820
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
8821
|
+
className: "relative h-3",
|
|
8822
|
+
"aria-hidden": groups.length === 0,
|
|
8823
|
+
children: groups.map((group) => {
|
|
8824
|
+
const left = (group.time - start) / span * 100;
|
|
8825
|
+
const title = group.items.length === 1 ? group.items[0].label : `${groupLabel(group.items.length)}\n${group.items.slice(0, 8).map((item) => `${format(item.time)} ${item.label}`).join("\n")}${group.items.length > 8 ? "\n…" : ""}`;
|
|
8826
|
+
return /* @__PURE__ */ jsx("button", {
|
|
8827
|
+
type: "button",
|
|
8828
|
+
title,
|
|
8829
|
+
"aria-label": group.items.length === 1 ? `${format(group.time)} · ${group.items[0].label}` : `${format(group.time)} · ${groupLabel(group.items.length)}`,
|
|
8830
|
+
disabled,
|
|
8831
|
+
onClick: (event) => {
|
|
8832
|
+
event.stopPropagation();
|
|
8833
|
+
const marker = group.items[0];
|
|
8834
|
+
if (onMarkerSelect) onMarkerSelect(marker);
|
|
8835
|
+
else onSeek(clamp(marker.time));
|
|
8836
|
+
},
|
|
8837
|
+
className: cn("absolute top-0 flex h-3 w-3 -translate-x-1/2 items-center justify-center rounded-sm", OPT_FOCUS_VISIBLE),
|
|
8838
|
+
style: { left: `${left}%` },
|
|
8839
|
+
children: /* @__PURE__ */ jsx("span", { className: cn("block h-2.5 rounded-sm", group.items.length > 1 ? "w-2" : "w-[3px]", MARKER_CLASS[group.tone]) })
|
|
8840
|
+
}, group.key);
|
|
8841
|
+
})
|
|
8842
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
8843
|
+
ref: trackRef,
|
|
8844
|
+
id,
|
|
8845
|
+
role: "slider",
|
|
8846
|
+
tabIndex: disabled ? -1 : 0,
|
|
8847
|
+
"aria-label": trackLabel,
|
|
8848
|
+
"aria-valuemin": start,
|
|
8849
|
+
"aria-valuemax": end,
|
|
8850
|
+
"aria-valuenow": shown,
|
|
8851
|
+
"aria-valuetext": format(shown),
|
|
8852
|
+
"aria-disabled": disabled || void 0,
|
|
8853
|
+
onPointerDown: handlePointerDown,
|
|
8854
|
+
onPointerMove: handlePointerMove,
|
|
8855
|
+
onPointerUp: handlePointerUp,
|
|
8856
|
+
onPointerCancel: handlePointerCancel,
|
|
8857
|
+
onPointerLeave: handlePointerLeave,
|
|
8858
|
+
onKeyDown: handleKeyDown,
|
|
8859
|
+
className: cn("group relative h-6 w-full touch-none select-none rounded", disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer", OPT_FOCUS_VISIBLE),
|
|
8860
|
+
children: [
|
|
8861
|
+
path ? /* @__PURE__ */ jsx("svg", {
|
|
8862
|
+
"aria-hidden": "true",
|
|
8863
|
+
viewBox: "0 0 100 100",
|
|
8864
|
+
preserveAspectRatio: "none",
|
|
8865
|
+
className: "pointer-events-none absolute inset-x-0 bottom-1 h-5 w-full opacity-40 transition-opacity group-hover:opacity-80",
|
|
8866
|
+
children: /* @__PURE__ */ jsx("path", {
|
|
8867
|
+
d: path,
|
|
8868
|
+
className: "fill-accent/40"
|
|
8869
|
+
})
|
|
8870
|
+
}) : null,
|
|
8871
|
+
/* @__PURE__ */ jsxs("div", {
|
|
8872
|
+
className: "bg-bg-subtle absolute inset-x-0 bottom-0 h-2 overflow-hidden rounded-full",
|
|
8873
|
+
children: [
|
|
8874
|
+
segments.map((segment, index) => {
|
|
8875
|
+
const left = (segment.start - start) / span * 100;
|
|
8876
|
+
const w = (segment.end - segment.start) / span * 100;
|
|
8877
|
+
if (w <= 0) return null;
|
|
8878
|
+
return /* @__PURE__ */ jsx("span", {
|
|
8879
|
+
role: "img",
|
|
8880
|
+
"aria-label": `${segmentNames[segment.kind]} ${format(segment.start)}–${format(segment.end)}`,
|
|
8881
|
+
className: cn("absolute inset-y-0", SEGMENT_CLASS[segment.kind]),
|
|
8882
|
+
style: {
|
|
8883
|
+
left: `${left}%`,
|
|
8884
|
+
width: `${w}%`
|
|
8885
|
+
}
|
|
8886
|
+
}, `${segment.kind}-${index}`);
|
|
8887
|
+
}),
|
|
8888
|
+
buffered.map((range, index) => {
|
|
8889
|
+
const left = (range.start - start) / span * 100;
|
|
8890
|
+
const w = (range.end - range.start) / span * 100;
|
|
8891
|
+
if (w <= 0) return null;
|
|
8892
|
+
return /* @__PURE__ */ jsx("span", {
|
|
8893
|
+
"aria-hidden": "true",
|
|
8894
|
+
className: "bg-text-tertiary/25 absolute inset-y-0",
|
|
8895
|
+
style: {
|
|
8896
|
+
left: `${left}%`,
|
|
8897
|
+
width: `${w}%`
|
|
8898
|
+
}
|
|
8899
|
+
}, `buffered-${index}`);
|
|
8900
|
+
}),
|
|
8901
|
+
/* @__PURE__ */ jsx("span", {
|
|
8902
|
+
"aria-hidden": "true",
|
|
8903
|
+
className: "bg-accent/80 absolute inset-y-0 left-0",
|
|
8904
|
+
style: { width: `${percent}%` }
|
|
8905
|
+
})
|
|
8906
|
+
]
|
|
8907
|
+
}),
|
|
8908
|
+
/* @__PURE__ */ jsx("span", {
|
|
8909
|
+
"aria-hidden": "true",
|
|
8910
|
+
className: "border-surface bg-accent pointer-events-none absolute bottom-0 h-4 w-4 -translate-x-1/2 translate-y-1/4 rounded-full border-2 shadow-sm",
|
|
8911
|
+
style: { left: `${percent}%` }
|
|
8912
|
+
}),
|
|
8913
|
+
previewPercent !== null && !disabled ? /* @__PURE__ */ jsx("output", {
|
|
8914
|
+
"aria-hidden": "true",
|
|
8915
|
+
className: cn("pointer-events-none absolute -top-6 -translate-x-1/2 rounded bg-surface-overlay px-1.5 py-0.5 text-[11px] font-medium tabular-nums shadow", OPT_TEXT_SECONDARY),
|
|
8916
|
+
style: { left: `${Math.min(97, Math.max(3, previewPercent))}%` },
|
|
8917
|
+
children: format(clamp(preview))
|
|
8918
|
+
}) : null
|
|
8919
|
+
]
|
|
8920
|
+
})]
|
|
8921
|
+
});
|
|
8922
|
+
}
|
|
8923
|
+
ReplayScrubber.displayName = "ReplayScrubber";
|
|
8924
|
+
//#endregion
|
|
8925
|
+
//#region src/shells/replay-transport.tsx
|
|
8926
|
+
const DEFAULT_SPEEDS = [
|
|
8927
|
+
.5,
|
|
8928
|
+
1,
|
|
8929
|
+
1.5,
|
|
8930
|
+
2,
|
|
8931
|
+
3,
|
|
8932
|
+
4,
|
|
8933
|
+
8,
|
|
8934
|
+
16
|
|
8935
|
+
];
|
|
8936
|
+
/** `m:ss` for anything under an hour, `h:mm:ss` beyond it. */
|
|
8937
|
+
function formatReplayElapsed(ms) {
|
|
8938
|
+
const total = Math.max(0, Math.floor(ms / 1e3));
|
|
8939
|
+
const hours = Math.floor(total / 3600);
|
|
8940
|
+
const minutes = Math.floor(total % 3600 / 60);
|
|
8941
|
+
const seconds = total % 60;
|
|
8942
|
+
const mmss = `${hours ? String(minutes).padStart(2, "0") : minutes}:${String(seconds).padStart(2, "0")}`;
|
|
8943
|
+
return hours ? `${hours}:${mmss}` : mmss;
|
|
8944
|
+
}
|
|
8945
|
+
function defaultClock(time) {
|
|
8946
|
+
return new Date(time).toLocaleTimeString(void 0, {
|
|
8947
|
+
hour: "2-digit",
|
|
8948
|
+
minute: "2-digit",
|
|
8949
|
+
second: "2-digit"
|
|
8950
|
+
});
|
|
8951
|
+
}
|
|
8952
|
+
/**
|
|
8953
|
+
* ReplayTransport — play/pause, jumps, speed and skip-inactivity for a
|
|
8954
|
+
* session recording.
|
|
8955
|
+
*
|
|
8956
|
+
* It renders controls only; the host owns playback. Timecode shows elapsed
|
|
8957
|
+
* time by default and can flip to the clock time of the visitor's session,
|
|
8958
|
+
* which is what an engineer needs when matching a recording against logs.
|
|
8959
|
+
*/
|
|
8960
|
+
function ReplayTransport({ playing, ended = false, position, start, end, onTogglePlay, onSkip, onSkipToStart, jumpMs = 1e4, speed, speeds = DEFAULT_SPEEDS, onSpeedChange, skipInactive, onSkipInactiveChange, fullscreen, onFullscreenChange, timecode = "elapsed", onTimecodeChange, formatClock = defaultClock, disabled = false, children, labels, shortcuts, className }) {
|
|
8961
|
+
var _labels$play, _labels$pause, _labels$restart, _labels$skipToStart, _labels$back, _labels$forward, _labels$speed, _labels$skipInactive, _labels$enterFullscre, _labels$exitFullscree, _labels$elapsed, _labels$clock;
|
|
8962
|
+
const jumpSeconds = Math.round(jumpMs / 1e3);
|
|
8963
|
+
const text = {
|
|
8964
|
+
play: (_labels$play = labels === null || labels === void 0 ? void 0 : labels.play) !== null && _labels$play !== void 0 ? _labels$play : "Play",
|
|
8965
|
+
pause: (_labels$pause = labels === null || labels === void 0 ? void 0 : labels.pause) !== null && _labels$pause !== void 0 ? _labels$pause : "Pause",
|
|
8966
|
+
restart: (_labels$restart = labels === null || labels === void 0 ? void 0 : labels.restart) !== null && _labels$restart !== void 0 ? _labels$restart : "Restart",
|
|
8967
|
+
skipToStart: (_labels$skipToStart = labels === null || labels === void 0 ? void 0 : labels.skipToStart) !== null && _labels$skipToStart !== void 0 ? _labels$skipToStart : "Go to start",
|
|
8968
|
+
back: (_labels$back = labels === null || labels === void 0 ? void 0 : labels.back) !== null && _labels$back !== void 0 ? _labels$back : ((s) => `Back ${s}s`),
|
|
8969
|
+
forward: (_labels$forward = labels === null || labels === void 0 ? void 0 : labels.forward) !== null && _labels$forward !== void 0 ? _labels$forward : ((s) => `Forward ${s}s`),
|
|
8970
|
+
speed: (_labels$speed = labels === null || labels === void 0 ? void 0 : labels.speed) !== null && _labels$speed !== void 0 ? _labels$speed : "Playback speed",
|
|
8971
|
+
skipInactive: (_labels$skipInactive = labels === null || labels === void 0 ? void 0 : labels.skipInactive) !== null && _labels$skipInactive !== void 0 ? _labels$skipInactive : "Skip inactivity",
|
|
8972
|
+
enterFullscreen: (_labels$enterFullscre = labels === null || labels === void 0 ? void 0 : labels.enterFullscreen) !== null && _labels$enterFullscre !== void 0 ? _labels$enterFullscre : "Enter fullscreen",
|
|
8973
|
+
exitFullscreen: (_labels$exitFullscree = labels === null || labels === void 0 ? void 0 : labels.exitFullscreen) !== null && _labels$exitFullscree !== void 0 ? _labels$exitFullscree : "Exit fullscreen",
|
|
8974
|
+
elapsed: (_labels$elapsed = labels === null || labels === void 0 ? void 0 : labels.elapsed) !== null && _labels$elapsed !== void 0 ? _labels$elapsed : "Show elapsed time",
|
|
8975
|
+
clock: (_labels$clock = labels === null || labels === void 0 ? void 0 : labels.clock) !== null && _labels$clock !== void 0 ? _labels$clock : "Show clock time"
|
|
8976
|
+
};
|
|
8977
|
+
const clamped = Math.min(end, Math.max(start, position));
|
|
8978
|
+
const timeLabel = timecode === "clock" ? formatClock(clamped) : `${formatReplayElapsed(clamped - start)} / ${formatReplayElapsed(end - start)}`;
|
|
8979
|
+
const withHint = (label, key) => key ? /* @__PURE__ */ jsxs("span", {
|
|
8980
|
+
className: "inline-flex items-center gap-1",
|
|
8981
|
+
children: [label, /* @__PURE__ */ jsx(Kbd, {
|
|
8982
|
+
hint: true,
|
|
8983
|
+
children: key
|
|
8984
|
+
})]
|
|
8985
|
+
}) : label;
|
|
8986
|
+
const playLabel = playing ? text.pause : ended ? text.restart : text.play;
|
|
8987
|
+
const PlayIcon = playing ? Pause : ended ? RotateCcw : Play;
|
|
8988
|
+
const selectSpeed = useCallback((value) => () => onSpeedChange(value), [onSpeedChange]);
|
|
8989
|
+
useEffect(() => {
|
|
8990
|
+
if (!speeds.includes(speed)) onSpeedChange(speeds.includes(1) ? 1 : speeds[0]);
|
|
8991
|
+
}, [
|
|
8992
|
+
speed,
|
|
8993
|
+
speeds,
|
|
8994
|
+
onSpeedChange
|
|
8995
|
+
]);
|
|
8996
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
8997
|
+
className: cn("flex w-full flex-wrap items-center gap-element", className),
|
|
8998
|
+
"data-opt-id": "RW9FW",
|
|
8999
|
+
"data-opt-slug": "replay-transport.ReplayTransport",
|
|
9000
|
+
children: [
|
|
9001
|
+
/* @__PURE__ */ jsxs("div", {
|
|
9002
|
+
className: "flex items-center gap-0.5",
|
|
9003
|
+
children: [
|
|
9004
|
+
onSkipToStart ? /* @__PURE__ */ jsx(Button, {
|
|
9005
|
+
type: "button",
|
|
9006
|
+
size: "sm",
|
|
9007
|
+
variant: "ghost",
|
|
9008
|
+
"aria-label": text.skipToStart,
|
|
9009
|
+
title: text.skipToStart,
|
|
9010
|
+
disabled,
|
|
9011
|
+
onClick: onSkipToStart,
|
|
9012
|
+
children: /* @__PURE__ */ jsx(SkipBack, {
|
|
9013
|
+
className: "size-4",
|
|
9014
|
+
"aria-hidden": "true"
|
|
9015
|
+
})
|
|
9016
|
+
}) : null,
|
|
9017
|
+
/* @__PURE__ */ jsx(Button, {
|
|
9018
|
+
type: "button",
|
|
9019
|
+
size: "sm",
|
|
9020
|
+
variant: "ghost",
|
|
9021
|
+
"aria-label": text.back(jumpSeconds),
|
|
9022
|
+
title: (shortcuts === null || shortcuts === void 0 ? void 0 : shortcuts.back) ? `${text.back(jumpSeconds)} (${shortcuts.back})` : text.back(jumpSeconds),
|
|
9023
|
+
disabled,
|
|
9024
|
+
onClick: () => onSkip(-jumpMs),
|
|
9025
|
+
children: /* @__PURE__ */ jsx(Rewind, {
|
|
9026
|
+
className: "size-4",
|
|
9027
|
+
"aria-hidden": "true"
|
|
9028
|
+
})
|
|
9029
|
+
}),
|
|
9030
|
+
/* @__PURE__ */ jsx(Button, {
|
|
9031
|
+
type: "button",
|
|
9032
|
+
size: "md",
|
|
9033
|
+
variant: "primary",
|
|
9034
|
+
"aria-label": playLabel,
|
|
9035
|
+
"aria-pressed": playing,
|
|
9036
|
+
title: (shortcuts === null || shortcuts === void 0 ? void 0 : shortcuts.playPause) ? `${playLabel} (${shortcuts.playPause})` : playLabel,
|
|
9037
|
+
disabled,
|
|
9038
|
+
onClick: onTogglePlay,
|
|
9039
|
+
className: "rounded-full px-0 [&]:h-9 [&]:w-9",
|
|
9040
|
+
children: /* @__PURE__ */ jsx(PlayIcon, {
|
|
9041
|
+
className: "size-4",
|
|
9042
|
+
"aria-hidden": "true"
|
|
9043
|
+
})
|
|
9044
|
+
}),
|
|
9045
|
+
/* @__PURE__ */ jsx(Button, {
|
|
9046
|
+
type: "button",
|
|
9047
|
+
size: "sm",
|
|
9048
|
+
variant: "ghost",
|
|
9049
|
+
"aria-label": text.forward(jumpSeconds),
|
|
9050
|
+
title: (shortcuts === null || shortcuts === void 0 ? void 0 : shortcuts.forward) ? `${text.forward(jumpSeconds)} (${shortcuts.forward})` : text.forward(jumpSeconds),
|
|
9051
|
+
disabled,
|
|
9052
|
+
onClick: () => onSkip(jumpMs),
|
|
9053
|
+
children: /* @__PURE__ */ jsx(FastForward, {
|
|
9054
|
+
className: "size-4",
|
|
9055
|
+
"aria-hidden": "true"
|
|
9056
|
+
})
|
|
9057
|
+
})
|
|
9058
|
+
]
|
|
9059
|
+
}),
|
|
9060
|
+
/* @__PURE__ */ jsx("button", {
|
|
9061
|
+
type: "button",
|
|
9062
|
+
className: cn("min-w-[7.5rem] rounded px-1 text-left text-sm tabular-nums", OPT_TEXT_SECONDARY, onTimecodeChange ? "cursor-pointer hover:bg-bg-subtle" : "cursor-default"),
|
|
9063
|
+
"aria-label": timecode === "clock" ? text.elapsed : text.clock,
|
|
9064
|
+
title: timecode === "clock" ? text.elapsed : text.clock,
|
|
9065
|
+
disabled: !onTimecodeChange,
|
|
9066
|
+
onClick: () => onTimecodeChange === null || onTimecodeChange === void 0 ? void 0 : onTimecodeChange(timecode === "clock" ? "elapsed" : "clock"),
|
|
9067
|
+
children: /* @__PURE__ */ jsx("output", {
|
|
9068
|
+
"aria-live": "off",
|
|
9069
|
+
children: timeLabel
|
|
9070
|
+
})
|
|
9071
|
+
}),
|
|
9072
|
+
/* @__PURE__ */ jsxs("div", {
|
|
9073
|
+
className: "gap-element ml-auto flex items-center",
|
|
9074
|
+
children: [
|
|
9075
|
+
/* @__PURE__ */ jsxs(MenuRoot$1, { children: [/* @__PURE__ */ jsxs(MenuTrigger$1, {
|
|
9076
|
+
"aria-label": text.speed,
|
|
9077
|
+
title: text.speed,
|
|
9078
|
+
disabled,
|
|
9079
|
+
className: cn("inline-flex h-[var(--opt-control-sm)] cursor-pointer items-center gap-1 rounded-lg px-2 text-xs font-medium tabular-nums hover:bg-bg-subtle disabled:cursor-not-allowed disabled:opacity-40", OPT_TEXT_SECONDARY),
|
|
9080
|
+
children: [
|
|
9081
|
+
speed,
|
|
9082
|
+
"×",
|
|
9083
|
+
/* @__PURE__ */ jsx(ChevronDown, {
|
|
9084
|
+
className: "size-3",
|
|
9085
|
+
"aria-hidden": "true"
|
|
9086
|
+
})
|
|
9087
|
+
]
|
|
9088
|
+
}), /* @__PURE__ */ jsx(MenuPopover$1, {
|
|
9089
|
+
"aria-label": text.speed,
|
|
9090
|
+
className: "min-w-[6rem]",
|
|
9091
|
+
children: speeds.map((value) => /* @__PURE__ */ jsxs(MenuItemRadio$1, {
|
|
9092
|
+
name: "replay-speed",
|
|
9093
|
+
value: String(value),
|
|
9094
|
+
checked: value === speed,
|
|
9095
|
+
onChange: selectSpeed(value),
|
|
9096
|
+
className: "tabular-nums",
|
|
9097
|
+
children: [value, "×"]
|
|
9098
|
+
}, value))
|
|
9099
|
+
})] }),
|
|
9100
|
+
/* @__PURE__ */ jsx(Toggle$1, {
|
|
9101
|
+
size: "sm",
|
|
9102
|
+
pressed: skipInactive,
|
|
9103
|
+
onPressedChange: onSkipInactiveChange,
|
|
9104
|
+
disabled,
|
|
9105
|
+
"aria-label": text.skipInactive,
|
|
9106
|
+
title: (shortcuts === null || shortcuts === void 0 ? void 0 : shortcuts.skipInactive) ? `${text.skipInactive} (${shortcuts.skipInactive})` : text.skipInactive,
|
|
9107
|
+
children: withHint(text.skipInactive, void 0)
|
|
9108
|
+
}),
|
|
9109
|
+
children,
|
|
9110
|
+
onFullscreenChange ? /* @__PURE__ */ jsx(Button, {
|
|
9111
|
+
type: "button",
|
|
9112
|
+
size: "sm",
|
|
9113
|
+
variant: "ghost",
|
|
9114
|
+
"aria-label": fullscreen ? text.exitFullscreen : text.enterFullscreen,
|
|
9115
|
+
"aria-pressed": !!fullscreen,
|
|
9116
|
+
title: (shortcuts === null || shortcuts === void 0 ? void 0 : shortcuts.fullscreen) ? `${fullscreen ? text.exitFullscreen : text.enterFullscreen} (${shortcuts.fullscreen})` : fullscreen ? text.exitFullscreen : text.enterFullscreen,
|
|
9117
|
+
onClick: () => onFullscreenChange(!fullscreen),
|
|
9118
|
+
children: fullscreen ? /* @__PURE__ */ jsx(Minimize2, {
|
|
9119
|
+
className: "size-4",
|
|
9120
|
+
"aria-hidden": "true"
|
|
9121
|
+
}) : /* @__PURE__ */ jsx(Maximize2, {
|
|
9122
|
+
className: "size-4",
|
|
9123
|
+
"aria-hidden": "true"
|
|
9124
|
+
})
|
|
9125
|
+
}) : null
|
|
9126
|
+
]
|
|
9127
|
+
}),
|
|
9128
|
+
(shortcuts === null || shortcuts === void 0 ? void 0 : shortcuts.playPause) ? /* @__PURE__ */ jsxs("span", {
|
|
9129
|
+
className: cn("sr-only", OPT_TEXT_TERTIARY),
|
|
9130
|
+
children: [
|
|
9131
|
+
playLabel,
|
|
9132
|
+
" ",
|
|
9133
|
+
shortcuts.playPause
|
|
9134
|
+
]
|
|
9135
|
+
}) : null
|
|
9136
|
+
]
|
|
9137
|
+
});
|
|
9138
|
+
}
|
|
9139
|
+
ReplayTransport.displayName = "ReplayTransport";
|
|
9140
|
+
//#endregion
|
|
9141
|
+
//#region src/shells/replay-player-layout.tsx
|
|
9142
|
+
function isTextTarget(target) {
|
|
9143
|
+
if (!(target instanceof HTMLElement)) return false;
|
|
9144
|
+
const tag = target.tagName;
|
|
9145
|
+
return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || target.isContentEditable;
|
|
9146
|
+
}
|
|
9147
|
+
/** Normalize a keyboard event into a shortcut key such as `space`, `arrowleft`, `shift+arrowright`. */
|
|
9148
|
+
function replayShortcutKey(event) {
|
|
9149
|
+
const key = event.key === " " ? "space" : event.key.toLowerCase();
|
|
9150
|
+
return [...[
|
|
9151
|
+
event.ctrlKey || event.metaKey ? "mod" : "",
|
|
9152
|
+
event.altKey ? "alt" : "",
|
|
9153
|
+
event.shiftKey ? "shift" : ""
|
|
9154
|
+
].filter(Boolean), key].join("+");
|
|
9155
|
+
}
|
|
9156
|
+
/**
|
|
9157
|
+
* ReplayPlayerLayout — stage, chrome and inspector for a session replay.
|
|
9158
|
+
*
|
|
9159
|
+
* Owns three things every replay UI otherwise reinvents: a resizable
|
|
9160
|
+
* inspector that keeps its width, fullscreen on the whole player rather
|
|
9161
|
+
* than on the recording iframe alone, and a keyboard scope that reacts to
|
|
9162
|
+
* shortcuts only while the player is focused and never while someone is
|
|
9163
|
+
* typing in a field.
|
|
9164
|
+
*/
|
|
9165
|
+
function ReplayPlayerLayout({ stage, meta, controls, inspector, inspectorOpen = true, onInspectorOpenChange, inspectorSize = 34, layoutId, fullscreen = false, onFullscreenChange, shortcuts, overlay, labels, className }) {
|
|
9166
|
+
var _labels$region, _labels$openInspector, _labels$closeInspecto;
|
|
9167
|
+
const rootRef = useRef(null);
|
|
9168
|
+
const [hovering, setHovering] = useState(false);
|
|
9169
|
+
const shortcutsRef = useRef(shortcuts);
|
|
9170
|
+
shortcutsRef.current = shortcuts;
|
|
9171
|
+
const text = {
|
|
9172
|
+
region: (_labels$region = labels === null || labels === void 0 ? void 0 : labels.region) !== null && _labels$region !== void 0 ? _labels$region : "Session replay",
|
|
9173
|
+
openInspector: (_labels$openInspector = labels === null || labels === void 0 ? void 0 : labels.openInspector) !== null && _labels$openInspector !== void 0 ? _labels$openInspector : "Show details",
|
|
9174
|
+
closeInspector: (_labels$closeInspecto = labels === null || labels === void 0 ? void 0 : labels.closeInspector) !== null && _labels$closeInspecto !== void 0 ? _labels$closeInspecto : "Hide details"
|
|
9175
|
+
};
|
|
9176
|
+
useEffect(() => {
|
|
9177
|
+
const node = rootRef.current;
|
|
9178
|
+
if (!node || typeof document === "undefined") return;
|
|
9179
|
+
const active = document.fullscreenElement === node;
|
|
9180
|
+
if (fullscreen && !active && node.requestFullscreen) node.requestFullscreen().catch(() => onFullscreenChange === null || onFullscreenChange === void 0 ? void 0 : onFullscreenChange(false));
|
|
9181
|
+
else if (!fullscreen && active && document.exitFullscreen) document.exitFullscreen().catch(() => {});
|
|
9182
|
+
}, [fullscreen, onFullscreenChange]);
|
|
9183
|
+
useEffect(() => {
|
|
9184
|
+
if (typeof document === "undefined") return;
|
|
9185
|
+
const handleChange = () => {
|
|
9186
|
+
const active = document.fullscreenElement === rootRef.current;
|
|
9187
|
+
if (active !== fullscreen) onFullscreenChange === null || onFullscreenChange === void 0 || onFullscreenChange(active);
|
|
9188
|
+
};
|
|
9189
|
+
document.addEventListener("fullscreenchange", handleChange);
|
|
9190
|
+
return () => document.removeEventListener("fullscreenchange", handleChange);
|
|
9191
|
+
}, [fullscreen, onFullscreenChange]);
|
|
9192
|
+
const handleKeyDown = useCallback((event) => {
|
|
9193
|
+
const map = shortcutsRef.current;
|
|
9194
|
+
if (!map || isTextTarget(event.target)) return;
|
|
9195
|
+
const handler = map[replayShortcutKey(event.nativeEvent)];
|
|
9196
|
+
if (!handler) return;
|
|
9197
|
+
if (handler(event.nativeEvent) !== false) {
|
|
9198
|
+
event.preventDefault();
|
|
9199
|
+
event.stopPropagation();
|
|
9200
|
+
}
|
|
9201
|
+
}, []);
|
|
9202
|
+
const showInspector = Boolean(inspector) && inspectorOpen;
|
|
9203
|
+
const toggle = onInspectorOpenChange ? /* @__PURE__ */ jsx(Button, {
|
|
9204
|
+
type: "button",
|
|
9205
|
+
size: "sm",
|
|
9206
|
+
variant: "ghost",
|
|
9207
|
+
"aria-label": inspectorOpen ? text.closeInspector : text.openInspector,
|
|
9208
|
+
"aria-pressed": inspectorOpen,
|
|
9209
|
+
title: inspectorOpen ? text.closeInspector : text.openInspector,
|
|
9210
|
+
onClick: () => onInspectorOpenChange(!inspectorOpen),
|
|
9211
|
+
children: inspectorOpen ? /* @__PURE__ */ jsx(PanelRightClose, {
|
|
9212
|
+
className: "size-4",
|
|
9213
|
+
"aria-hidden": "true"
|
|
9214
|
+
}) : /* @__PURE__ */ jsx(PanelRightOpen, {
|
|
9215
|
+
className: "size-4",
|
|
9216
|
+
"aria-hidden": "true"
|
|
9217
|
+
})
|
|
9218
|
+
}) : null;
|
|
9219
|
+
const main = /* @__PURE__ */ jsxs("div", {
|
|
9220
|
+
className: "flex h-full min-h-0 min-w-0 flex-1 flex-col",
|
|
9221
|
+
children: [
|
|
9222
|
+
meta || toggle ? /* @__PURE__ */ jsxs("div", {
|
|
9223
|
+
className: "gap-element border-border flex min-h-9 items-center border-b px-3 py-1 text-xs",
|
|
9224
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
9225
|
+
className: "min-w-0 flex-1",
|
|
9226
|
+
children: meta
|
|
9227
|
+
}), toggle]
|
|
9228
|
+
}) : null,
|
|
9229
|
+
/* @__PURE__ */ jsxs("div", {
|
|
9230
|
+
className: "bg-bg-subtle relative min-h-0 flex-1",
|
|
9231
|
+
onPointerEnter: () => setHovering(true),
|
|
9232
|
+
onPointerLeave: () => setHovering(false),
|
|
9233
|
+
"data-hovering": hovering || void 0,
|
|
9234
|
+
children: [stage, overlay ? /* @__PURE__ */ jsx("div", {
|
|
9235
|
+
className: "pointer-events-none absolute inset-0 flex items-center justify-center [&>*]:pointer-events-auto",
|
|
9236
|
+
children: overlay
|
|
9237
|
+
}) : null]
|
|
9238
|
+
}),
|
|
9239
|
+
controls ? /* @__PURE__ */ jsx("div", {
|
|
9240
|
+
className: "border-border border-t px-3 py-2",
|
|
9241
|
+
children: controls
|
|
9242
|
+
}) : null
|
|
9243
|
+
]
|
|
9244
|
+
});
|
|
9245
|
+
return /* @__PURE__ */ jsx("div", {
|
|
9246
|
+
ref: rootRef,
|
|
9247
|
+
role: "region",
|
|
9248
|
+
"aria-label": text.region,
|
|
9249
|
+
tabIndex: -1,
|
|
9250
|
+
onKeyDown: handleKeyDown,
|
|
9251
|
+
className: cn("flex min-h-0 w-full flex-col overflow-hidden rounded-lg bg-surface", OPT_BORDER, OPT_FOCUS_VISIBLE, fullscreen ? "h-screen" : "h-full", className),
|
|
9252
|
+
"data-opt-id": "A3VYS",
|
|
9253
|
+
"data-opt-slug": "replay-player-layout.ReplayPlayerLayout",
|
|
9254
|
+
children: showInspector ? /* @__PURE__ */ jsxs(ResizablePanelGroup, {
|
|
9255
|
+
direction: "horizontal",
|
|
9256
|
+
id: layoutId,
|
|
9257
|
+
children: [
|
|
9258
|
+
/* @__PURE__ */ jsx(ResizablePanel, {
|
|
9259
|
+
defaultSize: 100 - inspectorSize,
|
|
9260
|
+
minSize: 40,
|
|
9261
|
+
children: main
|
|
9262
|
+
}),
|
|
9263
|
+
/* @__PURE__ */ jsx(ResizableHandle, { withHandle: true }),
|
|
9264
|
+
/* @__PURE__ */ jsx(ResizablePanel, {
|
|
9265
|
+
defaultSize: inspectorSize,
|
|
9266
|
+
minSize: 20,
|
|
9267
|
+
maxSize: 60,
|
|
9268
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
9269
|
+
className: "flex h-full min-h-0 flex-col overflow-hidden",
|
|
9270
|
+
children: inspector
|
|
9271
|
+
})
|
|
9272
|
+
})
|
|
9273
|
+
]
|
|
9274
|
+
}) : main
|
|
9275
|
+
});
|
|
9276
|
+
}
|
|
9277
|
+
ReplayPlayerLayout.displayName = "ReplayPlayerLayout";
|
|
9278
|
+
//#endregion
|
|
9279
|
+
export { safeParseStoredSpec as $, AppMenubar as $t, EventTimeline as A, QueryToolbar as At, createThemeBootScript as B, toast as Bt, fieldValues as C, VALUELESS_OPERATORS as Ct, withFieldValue as D, defaultQueryChartConfig as Dt, removeClause as E, QueryResultChart as Et, OptThemeControllerContext as F, WorkflowCanvas as Ft, GLOBAL_THEME_STYLE_ID as G, EnvPanel as Gt, readStoredTheme as H, DataTable as Ht, useOptThemeController as I, KeyValueEditor as It, buildTokenCache as J, SettingsForm as Jt, THEME_SPEC_STORAGE_KEY as K, ProjectSwitcher as Kt, ALL_COMPOUND_THEMES as L, FailureList as Lt, OptThemeProvider as M, QueryResultsTable as Mt, useOptTheme as N, formatQueryCell as Nt, withoutField as O, numericResultColumns as Ot, useOptionalOptTheme as P, Flyout as Pt, resolveDescriptor as Q, SidebarNav as Qt, FORCE_LIGHT_PRESETS as R, NotificationToast as Rt, applyCellAction as S, QueryFiltersMenu as St, queryFields as T, hasActiveQueryFilters as Tt, resolveThemeMode as U, DomainTable as Ut, getCompoundTheme as V, useToast as Vt, GENERATED_THEME_ID_DEFAULT as W, BranchSelect as Wt, getCompoundIdForSpec as X, ContentTabs as Xt, computeGeneratedTokens as Y, FaqAccordion as Yt, isForceLightSpec as Z, EditorToolbar as Zt, quoteValue as _, SaveQueryDialog as _t, ReplayScrubber as a, CommandPalette as an, generatedTheme as at, EMPTY_QUERY as b, isValidQueryVariableName as bt, LogTable as c, CommandEmpty$1 as cn, isPresetSpec as ct, TimeRangeControl as d, CommandItem$1 as dn, THEME_PRESET_IDS as dt, StatusSelect as en, safeParseStoredTokens as et, useAutoRefresh as f, CommandList$1 as fn, THEME_REGISTRY as ft, formatQuery as g, Callout as gn, useMediaQuery as gt, formatClause as h, SegmentedControl as hn, QueryWorkspace as ht, formatReplayElapsed as i, CommandPaletteTrigger as in, defineThemeOverrides as it, SqlEditor as j, SchemaTree as jt, ScoreBreakdown as k, QueryOutputPane as kt, TimeSeriesPanel as l, CommandGroup$1 as ln, presetTheme as lt, describeClause as m, CommandShortcut as mn, isThemePresetId as mt, replayShortcutKey as n, useAsyncSearch as nn, serializeThemeRule as nt, groupReplayMarkers as o, Command as on, isDescriptorSpec as ot, QueryBar as p, CommandSeparator$1 as pn, getThemeDescriptor as pt, THEME_TOKENS_STORAGE_KEY as q, DeploymentTimeline as qt, ReplayTransport as r, DashboardGrid as rn, serializeTokenBody as rt, FieldSidebar as s, CommandDialog as sn, isGeneratedSpec as st, ReplayPlayerLayout as t, SearchCombobox as tn, serializeGeneratedTheme as tt, DEFAULT_TIME_PRESETS as u, CommandInput$1 as un, FORCE_LIGHT_PRESET_IDS as ut, parseQuery as v, QueryHistoryList as vt, isEmptyQuery as w, countActiveQueryFilters as wt, addClause as x, EMPTY_QUERY_FILTERS as xt, parseQueryWithDiagnostics as y, QueryVariablesMenu as yt, coerceThemeMode as z, ToastProvider as zt };
|