@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.
Files changed (38) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/COMPONENT_CATALOG.md +222 -69
  3. package/README.md +26 -0
  4. package/dist/core/index.cjs +4 -4
  5. package/dist/core/index.d.cts +2 -2
  6. package/dist/core/index.d.ts +2 -2
  7. package/dist/core/index.js +2 -2
  8. package/dist/docs/02-components/01-core.md +37 -33
  9. package/dist/docs/02-components/02-visuals.md +1 -1
  10. package/dist/docs/02-components/03-shells.md +191 -36
  11. package/dist/docs/02-components/04-surfaces.md +1 -1
  12. package/dist/docs/02-components/index.md +4 -4
  13. package/dist/id-registry.cjs +24 -0
  14. package/dist/id-registry.js +24 -0
  15. package/dist/id-registry.json +51 -0
  16. package/dist/index.cjs +445 -381
  17. package/dist/index.d.cts +16 -13
  18. package/dist/index.d.ts +16 -13
  19. package/dist/index.js +303 -245
  20. package/dist/{key-pad-menu-CqFI6la9.js → key-pad-menu-BCOeYvsH.js} +50 -61
  21. package/dist/{key-pad-menu-D6pYzaj-.d.cts → key-pad-menu-CaIsTHB2.d.cts} +12 -8
  22. package/dist/{key-pad-menu-BgtSL3xe.cjs → key-pad-menu-Jaqxhx9P.cjs} +48 -71
  23. package/dist/{key-pad-menu-Yh7Fpb8m.d.ts → key-pad-menu-odAEycIX.d.ts} +12 -8
  24. package/dist/meta.cjs +447 -7
  25. package/dist/meta.js +447 -7
  26. package/dist/{field-sidebar-mSVCMAZp.js → replay-player-layout-BVUNsUS1.js} +891 -191
  27. package/dist/{field-sidebar-ePqFX6MN.cjs → replay-player-layout-Cmzptq89.cjs} +931 -189
  28. package/dist/{field-sidebar-CJ4HxP8D.d.cts → replay-player-layout-DpqdfdQ6.d.ts} +246 -3
  29. package/dist/{field-sidebar-DnhpcT2A.d.ts → replay-player-layout-DuZW-gFF.d.cts} +246 -3
  30. package/dist/shells/index.cjs +58 -52
  31. package/dist/shells/index.d.cts +2 -2
  32. package/dist/shells/index.d.ts +2 -2
  33. package/dist/shells/index.js +2 -2
  34. package/dist/{text-truncate-Bd-0WkSP.js → text-truncate-B-8nJgnu.js} +200 -101
  35. package/dist/{text-truncate-Bbul-jMD.d.cts → text-truncate-DOxUijpB.d.cts} +2 -0
  36. package/dist/{text-truncate-Bbul-jMD.d.ts → text-truncate-DOxUijpB.d.ts} +2 -0
  37. package/dist/{text-truncate-BeYW6XZu.cjs → text-truncate-gQhpMy-Z.cjs} +214 -97
  38. package/package.json +1 -1
@@ -1,10 +1,10 @@
1
1
  "use client";
2
- import { $t as CompositeItem, Bn as OPT_BADGE_NEUTRAL, Bt as SelectItem$1, C as FormCheckbox$1, Ct as ComboboxEmpty, Dt as ComboboxItem$1, En as Skeleton, Et as ComboboxInput$1, Fn as OPT_ACTIVE_ITEM, Ft as DialogFooter, G as FormSelectPopover, Gn as OPT_DISCLOSURE_TRIGGER, Gt as Tab$1, H as FormRoot$1, Hn as OPT_BADGE_YELLOW, Ht as SelectPopover$1, It as DialogHeader, J as FormSubmit$1, Jn as OPT_DOT_GREEN, Jt as TabsRoot$1, Kn as OPT_DIVIDER, Kt as TabList$1, Ln as OPT_ANIMATE_ENTER_EXIT, Lt as DialogHeading$1, Mt as DialogDescription$1, N as FormInput$1, O as FormError$1, Ot as ComboboxPopover$1, P as FormLabel$1, Qn as OPT_FOCUS_VISIBLE, Qt as Composite, R as FormRadio$1, Rn as OPT_BADGE_BLUE, Rt as DialogPanel$1, S as formatOptTime, St as MenubarRoot$1, Tn as StatCard, U as FormSelect, Un as OPT_BORDER, Ut as SelectRoot$1, Vn as OPT_BADGE_RED, Vt as SelectLabel$1, W as FormSelectItem, Wn as OPT_DISCLOSURE_CONTENT, Wt as SelectTrigger$1, Xn as OPT_DOT_YELLOW, Xt as DisclosureRoot$1, Y as FormSwitch$1, Yn as OPT_DOT_RED, Yt as DisclosureContent$1, Z as FormTextarea, Zn as OPT_FOCUS, Zt as DisclosureTrigger$1, _ as DropdownTrigger, _n as Spinner, _r as cn, _t as MenuPopover$1, b as formatOptNumber, bn as EmptyState, bt as MenuTrigger$1, c as ResizableHandle, cr as OPT_TD_MUTED, ct as PopoverContent$1, d as Checkbox, dn as ToolbarRoot$1, dr as OPT_TEXT_SECONDARY, en as CompositeProvider, er as OPT_INPUT, et as useFormStore, fn as ToolbarSeparator$1, fr as OPT_TEXT_TERTIARY, gn as Alert, gr as Kbd, gt as MenuItemRadio$1, h as DropdownMenu, hn as TooltipProvider, hr as _objectWithoutProperties, ht as MenuItemCheckbox$1, i as FieldToken, in as Textarea, ir as OPT_SURFACE_POPOVER, jt as DialogBody, kt as ComboboxRoot$1, l as ResizablePanel, ln as ToolbarButton$1, lr as OPT_TEXT_LABEL, lt as PopoverRoot$1, m as DropdownItem, mn as TooltipAnchor$1, mr as _objectSpread2, mt as MenuItem$1, n as Highlight, nr as OPT_ITEM_BASE, nt as DateRangePicker, o as resolveFieldType, on as Switch, or as OPT_TABLE_ROW_ACTIVE, p as DropdownContent, pn as Tooltip$1, pr as OPT_TH, pt as MenuButtonArrow$1, qt as TabPanel$1, rn as Meter$1, rr as OPT_SURFACE, rt as TagInput, s as Pagination, sn as Input, sr as OPT_TD, t as TextTruncate, tn as CompositeRow, tt as DEFAULT_PRESETS, u as ResizablePanelGroup, un as ToolbarContainer$1, ur as OPT_TEXT_PRIMARY, ut as PopoverTrigger$1, v as OPT_UI_DEFAULT_LOCALE, vn as Badge, vt as MenuRoot$1, xn as Button, xt as MenubarContainer$1, y as formatOptDateTime, yn as DescriptionList, yt as MenuSeparator$1, z as FormRadioGroup$1, zn as OPT_BADGE_GREEN, zt as DialogRoot$1 } from "./text-truncate-Bd-0WkSP.js";
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) => /* @__PURE__ */ jsxs(CommandItem$1, {
362
- value: cmd.id,
363
- keywords: [
364
- cmd.label,
365
- cmd.group,
366
- ...cmd.shortcut ? [cmd.shortcut] : []
367
- ],
368
- onSelect: handleSelect,
369
- children: [/* @__PURE__ */ jsx("span", { children: cmd.label }), cmd.shortcut && /* @__PURE__ */ jsx(CommandShortcut, { children: cmd.shortcut })]
370
- }, cmd.id))
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 [asyncResults, setAsyncResults] = useState([]);
471
- const [loading, setLoading] = useState(false);
472
- const timerRef = useRef(void 0);
473
- const searchGenerationRef = useRef(0);
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
- searchGenerationRef.current += 1;
547
+ inputRef.current = event.currentTarget;
548
+ invalidate();
485
549
  setQuery(event.currentTarget.value);
486
550
  };
487
551
  const handleValueChange = (item) => {
488
- searchGenerationRef.current += 1;
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
- /* @__PURE__ */ jsx(ComboboxPopover$1, {
544
- className: "max-w-xs",
545
- children: isAsync && loading ? /* @__PURE__ */ jsxs("div", {
546
- className: "text-text-secondary gap-element flex items-center px-3 py-2 text-sm",
547
- role: "status",
548
- "aria-live": "polite",
549
- "aria-busy": "true",
550
- children: [/* @__PURE__ */ jsx(Spinner, {
551
- size: "sm",
552
- "aria-hidden": "true"
553
- }), labels.loading]
554
- }) : matches.length === 0 ? /* @__PURE__ */ jsx(ComboboxEmpty, { children: labels.noResults }) : matches.map((item) => /* @__PURE__ */ jsx(ComboboxItem$1, {
555
- value: item,
556
- children: item
557
- }, item))
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: [/* @__PURE__ */ jsxs(FormRoot$1, {
951
- store: form,
952
- "aria-label": labels.formAriaLabel,
953
- children: [fields.map((field) => {
954
- var _field$type;
955
- if (field.visible !== void 0) {
956
- const currentValues = form.getValues();
957
- if (!(typeof field.visible === "function" ? field.visible(currentValues) : field.visible)) return null;
958
- }
959
- if (field.type === "tags") {
960
- const rawValue = form.getValue(field.name);
961
- const tags = Array.isArray(rawValue) ? rawValue : [];
962
- return /* @__PURE__ */ jsxs("div", {
963
- className: "gap-element flex flex-col",
964
- children: [
965
- /* @__PURE__ */ jsx(FormLabel$1, {
966
- name: field.name,
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
- /* @__PURE__ */ jsx(FormError$1, { name: field.name })
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
- if (field.type === "radio") {
979
- var _field$options;
980
- return /* @__PURE__ */ jsxs("fieldset", {
981
- className: "gap-element flex flex-col",
982
- children: [
983
- /* @__PURE__ */ jsx("legend", {
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
- /* @__PURE__ */ jsx(FormRadioGroup$1, { children: /* @__PURE__ */ jsx("div", {
988
- className: "gap-group flex",
989
- children: (_field$options = field.options) === null || _field$options === void 0 ? void 0 : _field$options.map((option) => /* @__PURE__ */ jsxs("label", {
990
- className: "text-text-primary gap-element flex items-center text-sm",
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
- if (field.type === "select") {
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
- children: field.label
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
- if (field.type === "switch") return /* @__PURE__ */ jsxs("label", {
1050
- className: "gap-group flex items-center",
1051
- children: [/* @__PURE__ */ jsx(FormSwitch$1, { name: field.name }), /* @__PURE__ */ jsxs("div", {
1052
- className: "flex flex-col",
1053
- children: [/* @__PURE__ */ jsx("span", {
1054
- className: "text-text-primary text-sm font-medium",
1055
- children: field.label
1056
- }), field.description && /* @__PURE__ */ jsx("span", {
1057
- className: "text-text-secondary text-sm",
1058
- children: field.description
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
- export { generatedTheme as $, Command as $t, useOptThemeController as A, KeyValueEditor as At, THEME_SPEC_STORAGE_KEY as B, ProjectSwitcher as Bt, ScoreBreakdown as C, QueryOutputPane as Ct, useOptTheme as D, formatQueryCell as Dt, OptThemeProvider as E, QueryResultsTable as Et, getCompoundTheme as F, useToast as Ft, isForceLightSpec as G, EditorToolbar as Gt, buildTokenCache as H, SettingsForm as Ht, readStoredTheme as I, DataTable as It, safeParseStoredTokens as J, StatusSelect as Jt, resolveDescriptor as K, SidebarNav as Kt, resolveThemeMode as L, DomainTable as Lt, FORCE_LIGHT_PRESETS as M, NotificationToast as Mt, coerceThemeMode as N, ToastProvider as Nt, useOptionalOptTheme as O, Flyout as Ot, createThemeBootScript as P, toast as Pt, defineThemeOverrides as Q, CommandPalette as Qt, GENERATED_THEME_ID_DEFAULT as R, BranchSelect as Rt, withoutField as S, numericResultColumns as St, SqlEditor as T, SchemaTree as Tt, computeGeneratedTokens as U, FaqAccordion as Ut, THEME_TOKENS_STORAGE_KEY as V, DeploymentTimeline as Vt, getCompoundIdForSpec as W, ContentTabs as Wt, serializeThemeRule as X, DashboardGrid as Xt, serializeGeneratedTheme as Y, SearchCombobox as Yt, serializeTokenBody as Z, CommandPaletteTrigger as Zt, fieldValues as _, VALUELESS_OPERATORS as _t, TimeRangeControl as a, CommandList$1 as an, THEME_PRESET_IDS as at, removeClause as b, QueryResultChart as bt, describeClause as c, SegmentedControl as cn, isThemePresetId as ct, quoteValue as d, SaveQueryDialog as dt, CommandDialog as en, isDescriptorSpec as et, parseQuery as f, QueryHistoryList as ft, applyCellAction as g, QueryFiltersMenu as gt, addClause as h, EMPTY_QUERY_FILTERS as ht, DEFAULT_TIME_PRESETS as i, CommandItem$1 as in, FORCE_LIGHT_PRESET_IDS as it, ALL_COMPOUND_THEMES as j, FailureList as jt, OptThemeControllerContext as k, WorkflowCanvas as kt, formatClause as l, Callout as ln, QueryWorkspace as lt, EMPTY_QUERY as m, isValidQueryVariableName as mt, LogTable as n, CommandGroup$1 as nn, isPresetSpec as nt, useAutoRefresh as o, CommandSeparator$1 as on, THEME_REGISTRY as ot, parseQueryWithDiagnostics as p, QueryVariablesMenu as pt, safeParseStoredSpec as q, AppMenubar as qt, TimeSeriesPanel as r, CommandInput$1 as rn, presetTheme as rt, QueryBar as s, CommandShortcut as sn, getThemeDescriptor as st, FieldSidebar as t, CommandEmpty$1 as tn, isGeneratedSpec as tt, formatQuery as u, useMediaQuery as ut, isEmptyQuery as v, countActiveQueryFilters as vt, EventTimeline as w, QueryToolbar as wt, withFieldValue as x, defaultQueryChartConfig as xt, queryFields as y, hasActiveQueryFilters as yt, GLOBAL_THEME_STYLE_ID as z, EnvPanel as zt };
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 };