@reopt-ai/opt-ui 1.13.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 (40) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/COMPONENT_CATALOG.md +644 -123
  3. package/README.md +35 -7
  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 +261 -58
  9. package/dist/docs/02-components/02-visuals.md +1 -1
  10. package/dist/docs/02-components/03-shells.md +391 -65
  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 +32 -0
  14. package/dist/id-registry.js +32 -0
  15. package/dist/id-registry.json +68 -0
  16. package/dist/index.cjs +1196 -427
  17. package/dist/index.d.cts +208 -19
  18. package/dist/index.d.ts +208 -19
  19. package/dist/index.js +1050 -295
  20. package/dist/{key-pad-menu-DMmbQ1jF.js → key-pad-menu-BCOeYvsH.js} +59 -67
  21. package/dist/{key-pad-menu-BOVu97bj.d.cts → key-pad-menu-CaIsTHB2.d.cts} +22 -9
  22. package/dist/{key-pad-menu-0KWOnELe.cjs → key-pad-menu-Jaqxhx9P.cjs} +57 -77
  23. package/dist/{key-pad-menu-NlZ9zNF8.d.ts → key-pad-menu-odAEycIX.d.ts} +22 -9
  24. package/dist/meta.cjs +877 -26
  25. package/dist/meta.js +877 -26
  26. package/dist/{field-sidebar-CRdToHRO.js → replay-player-layout-BVUNsUS1.js} +904 -203
  27. package/dist/{field-sidebar-CxxSK4JO.cjs → replay-player-layout-Cmzptq89.cjs} +944 -201
  28. package/dist/{field-sidebar-D-rwuN_w.d.cts → replay-player-layout-DpqdfdQ6.d.ts} +246 -3
  29. package/dist/{field-sidebar-Dz7J4ett.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-eMzvU_7k.d.cts → text-truncate-DOxUijpB.d.cts} +3 -30
  36. package/dist/{text-truncate-eMzvU_7k.d.ts → text-truncate-DOxUijpB.d.ts} +3 -30
  37. package/dist/{text-truncate-BeYW6XZu.cjs → text-truncate-gQhpMy-Z.cjs} +214 -97
  38. package/dist/theme/server.d.cts +2 -2
  39. package/dist/theme/server.d.ts +2 -2
  40. package/package.json +2 -2
@@ -1,5 +1,5 @@
1
1
  "use client";
2
- const require_text_truncate = require("./text-truncate-BeYW6XZu.cjs");
2
+ const require_text_truncate = require("./text-truncate-gQhpMy-Z.cjs");
3
3
  let react_jsx_runtime = require("react/jsx-runtime");
4
4
  let react = require("react");
5
5
  react = require_text_truncate.__toESM(react, 1);
@@ -278,7 +278,7 @@ function CommandGroup(_ref5) {
278
278
  className: "text-text-tertiary px-2 py-1.5 text-xs font-medium",
279
279
  children: heading
280
280
  }) : void 0,
281
- className: require_text_truncate.cn("py-1", className)
281
+ className: require_text_truncate.cn("py-1 [&:not(:has([data-slot=command-item]))]:hidden", className)
282
282
  }, props), {}, {
283
283
  "data-opt-id": "JPK1Y",
284
284
  "data-opt-slug": "command.CommandGroup"
@@ -359,16 +359,20 @@ function CommandPalette({ commands, open, onOpenChange, onSelect, inline = false
359
359
  "aria-label": resolvedLabels.list,
360
360
  children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandEmpty, { children: empty !== null && empty !== void 0 ? empty : resolvedLabels.empty }), hasCommands ? Array.from(grouped.entries()).map(([group, items]) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandGroup, {
361
361
  heading: group,
362
- children: items.map((cmd) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CommandItem, {
363
- value: cmd.id,
364
- keywords: [
365
- cmd.label,
366
- cmd.group,
367
- ...cmd.shortcut ? [cmd.shortcut] : []
368
- ],
369
- onSelect: handleSelect,
370
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: cmd.label }), cmd.shortcut && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandShortcut, { children: cmd.shortcut })]
371
- }, cmd.id))
362
+ children: items.map((cmd) => {
363
+ var _cmd$keywords;
364
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(CommandItem, {
365
+ value: cmd.id,
366
+ keywords: [
367
+ cmd.label,
368
+ cmd.group,
369
+ ...(_cmd$keywords = cmd.keywords) !== null && _cmd$keywords !== void 0 ? _cmd$keywords : [],
370
+ ...cmd.shortcut ? [cmd.shortcut] : []
371
+ ],
372
+ onSelect: handleSelect,
373
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: cmd.label }), cmd.shortcut && /* @__PURE__ */ (0, react_jsx_runtime.jsx)(CommandShortcut, { children: cmd.shortcut })]
374
+ }, cmd.id);
375
+ })
372
376
  }, group)) : null]
373
377
  }),
374
378
  footer
@@ -458,9 +462,65 @@ function DashboardGrid({ stats, columns = 3, labels: customLabels }) {
458
462
  });
459
463
  }
460
464
  //#endregion
465
+ //#region src/lib/use-async-search.ts
466
+ /** Debounced search with explicit failure and latest-request-only results. */
467
+ function useAsyncSearch({ query, search, enabled, debounceMs }) {
468
+ const [results, setResults] = (0, react.useState)([]);
469
+ const [loading, setLoading] = (0, react.useState)(false);
470
+ const [failed, setFailed] = (0, react.useState)(false);
471
+ const [attempt, setAttempt] = (0, react.useState)(0);
472
+ const generationRef = (0, react.useRef)(0);
473
+ const timerRef = (0, react.useRef)(void 0);
474
+ const invalidate = (0, react.useCallback)(() => {
475
+ generationRef.current += 1;
476
+ clearTimeout(timerRef.current);
477
+ setResults([]);
478
+ setLoading(false);
479
+ setFailed(false);
480
+ }, []);
481
+ (0, react.useEffect)(() => {
482
+ const generation = ++generationRef.current;
483
+ setResults([]);
484
+ setFailed(false);
485
+ setLoading(enabled && Boolean(search));
486
+ if (enabled && search) timerRef.current = setTimeout(async () => {
487
+ try {
488
+ const items = await search(query);
489
+ if (generationRef.current === generation) setResults(items);
490
+ } catch (_unused) {
491
+ if (generationRef.current === generation) setFailed(true);
492
+ } finally {
493
+ if (generationRef.current === generation) setLoading(false);
494
+ }
495
+ }, debounceMs);
496
+ return () => {
497
+ generationRef.current += 1;
498
+ clearTimeout(timerRef.current);
499
+ };
500
+ }, [
501
+ query,
502
+ search,
503
+ enabled,
504
+ debounceMs,
505
+ attempt
506
+ ]);
507
+ return {
508
+ results,
509
+ loading,
510
+ failed,
511
+ retry: (0, react.useCallback)(() => {
512
+ invalidate();
513
+ setAttempt((current) => current + 1);
514
+ }, [invalidate]),
515
+ invalidate
516
+ };
517
+ }
518
+ //#endregion
461
519
  //#region src/shells/search-combobox.tsx
462
520
  const defaultLabels$33 = {
463
521
  noResults: "결과가 없습니다.",
522
+ error: "검색에 실패했습니다. 다시 시도해주세요.",
523
+ retry: "다시 시도",
464
524
  loading: "Loading…"
465
525
  };
466
526
  /** Renders the search combobox component. */
@@ -468,10 +528,13 @@ function SearchCombobox({ items, placeholder = "검색...", label = "검색", on
468
528
  const labels = require_text_truncate._objectSpread2(require_text_truncate._objectSpread2({}, defaultLabels$33), customLabels);
469
529
  const inputId = (0, react.useId)();
470
530
  const [query, setQuery] = (0, react.useState)("");
471
- const [asyncResults, setAsyncResults] = (0, react.useState)([]);
472
- const [loading, setLoading] = (0, react.useState)(false);
473
- const timerRef = (0, react.useRef)(void 0);
474
- const searchGenerationRef = (0, react.useRef)(0);
531
+ const inputRef = (0, react.useRef)(null);
532
+ const { results: asyncResults, loading, failed, retry, invalidate } = useAsyncSearch({
533
+ query,
534
+ search: onSearch,
535
+ enabled: isAsync && Boolean(query),
536
+ debounceMs
537
+ });
475
538
  const syncMatches = (0, react.useMemo)(() => {
476
539
  if (isAsync) return [];
477
540
  if (!query) return items;
@@ -482,45 +545,15 @@ function SearchCombobox({ items, placeholder = "검색...", label = "검색", on
482
545
  isAsync
483
546
  ]);
484
547
  const handleInput = (event) => {
485
- searchGenerationRef.current += 1;
548
+ inputRef.current = event.currentTarget;
549
+ invalidate();
486
550
  setQuery(event.currentTarget.value);
487
551
  };
488
552
  const handleValueChange = (item) => {
489
- searchGenerationRef.current += 1;
553
+ invalidate();
490
554
  setQuery(item);
491
555
  onSelect === null || onSelect === void 0 || onSelect(item);
492
556
  };
493
- (0, react.useEffect)(() => {
494
- const generation = ++searchGenerationRef.current;
495
- if (timerRef.current) clearTimeout(timerRef.current);
496
- if (!isAsync || !onSearch) {
497
- setLoading(false);
498
- return;
499
- }
500
- if (!query) {
501
- setAsyncResults([]);
502
- setLoading(false);
503
- return;
504
- }
505
- setLoading(true);
506
- timerRef.current = setTimeout(async () => {
507
- try {
508
- const results = await onSearch(query);
509
- if (searchGenerationRef.current !== generation) return;
510
- setAsyncResults(results);
511
- } finally {
512
- if (searchGenerationRef.current === generation) setLoading(false);
513
- }
514
- }, debounceMs);
515
- return () => {
516
- if (timerRef.current) clearTimeout(timerRef.current);
517
- };
518
- }, [
519
- query,
520
- isAsync,
521
- onSearch,
522
- debounceMs
523
- ]);
524
557
  const matches = isAsync ? asyncResults : syncMatches;
525
558
  return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
526
559
  className: "gap-element flex flex-col",
@@ -534,29 +567,42 @@ function SearchCombobox({ items, placeholder = "검색...", label = "검색", on
534
567
  className: require_text_truncate.OPT_TEXT_LABEL,
535
568
  children: label
536
569
  }),
537
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.ComboboxInput, {
570
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.ComboboxInput, require_text_truncate._objectSpread2({
538
571
  id: inputId,
539
572
  placeholder,
540
573
  className: "max-w-xs",
541
574
  "aria-busy": loading || void 0,
542
575
  onInput: handleInput
543
- }),
544
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.ComboboxPopover, {
545
- className: "max-w-xs",
546
- children: isAsync && loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
547
- className: "text-text-secondary gap-element flex items-center px-3 py-2 text-sm",
548
- role: "status",
549
- "aria-live": "polite",
550
- "aria-busy": "true",
551
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.Spinner, {
552
- size: "sm",
553
- "aria-hidden": "true"
554
- }), labels.loading]
555
- }) : matches.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.ComboboxEmpty, { children: labels.noResults }) : matches.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.ComboboxItem, {
556
- value: item,
557
- children: item
558
- }, item))
559
- })
576
+ }, failed ? {
577
+ "aria-expanded": false,
578
+ "aria-controls": void 0
579
+ } : {})),
580
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.ComboboxPopover, require_text_truncate._objectSpread2(require_text_truncate._objectSpread2({ className: "max-w-xs" }, failed ? { role: "presentation" } : {}), {}, { children: isAsync && failed ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
581
+ role: "alert",
582
+ className: "gap-element flex flex-col px-3 py-2 text-sm",
583
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: labels.error }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.Button, {
584
+ variant: "secondary",
585
+ size: "sm",
586
+ onClick: () => {
587
+ var _inputRef$current;
588
+ retry();
589
+ (_inputRef$current = inputRef.current) === null || _inputRef$current === void 0 || _inputRef$current.focus();
590
+ },
591
+ children: labels.retry
592
+ })]
593
+ }) : isAsync && loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
594
+ className: "text-text-secondary gap-element flex items-center px-3 py-2 text-sm",
595
+ role: "status",
596
+ "aria-live": "polite",
597
+ "aria-busy": "true",
598
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.Spinner, {
599
+ size: "sm",
600
+ "aria-hidden": "true"
601
+ }), labels.loading]
602
+ }) : matches.length === 0 ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.ComboboxEmpty, { children: labels.noResults }) : matches.map((item) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.ComboboxItem, {
603
+ value: item,
604
+ children: item
605
+ }, item)) }))
560
606
  ]
561
607
  })
562
608
  });
@@ -901,13 +947,16 @@ function FaqAccordion({ items, labels: customLabels }) {
901
947
  //#region src/shells/settings-form.tsx
902
948
  const defaultLabels$26 = {
903
949
  formAriaLabel: "설정 폼",
950
+ submitError: "저장에 실패했습니다. 다시 시도해주세요.",
904
951
  submitButton: "저장",
905
952
  successMessage: "설정이 저장되었습니다."
906
953
  };
907
954
  /** Renders the settings form component. */
908
955
  function SettingsForm({ fields, onSubmit, labels: customLabels }) {
956
+ var _labels$submitError;
909
957
  const labels = require_text_truncate._objectSpread2(require_text_truncate._objectSpread2({}, defaultLabels$26), customLabels);
910
958
  const [submitted, setSubmitted] = (0, react.useState)(false);
959
+ const formId = (0, react.useId)();
911
960
  const defaultValues = {};
912
961
  for (const field of fields) {
913
962
  var _field$defaultValue;
@@ -933,9 +982,10 @@ function SettingsForm({ fields, onSubmit, labels: customLabels }) {
933
982
  }
934
983
  return errors;
935
984
  },
936
- onSubmit: (values) => {
985
+ onSubmit: async (values) => {
986
+ setSubmitted(false);
987
+ await (onSubmit === null || onSubmit === void 0 ? void 0 : onSubmit(values));
937
988
  setSubmitted(true);
938
- onSubmit === null || onSubmit === void 0 || onSubmit(values);
939
989
  }
940
990
  });
941
991
  if (fields.length === 0) return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.EmptyState, {
@@ -948,138 +998,142 @@ function SettingsForm({ fields, onSubmit, labels: customLabels }) {
948
998
  className: "gap-group flex flex-col",
949
999
  "data-opt-id": "9TEEJ",
950
1000
  "data-opt-slug": "settings-form.SettingsForm",
951
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_text_truncate.FormRoot, {
952
- store: form,
953
- "aria-label": labels.formAriaLabel,
954
- children: [fields.map((field) => {
955
- var _field$type;
956
- if (field.visible !== void 0) {
957
- const currentValues = form.getValues();
958
- if (!(typeof field.visible === "function" ? field.visible(currentValues) : field.visible)) return null;
959
- }
960
- if (field.type === "tags") {
961
- const rawValue = form.getValue(field.name);
962
- const tags = Array.isArray(rawValue) ? rawValue : [];
963
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
964
- className: "gap-element flex flex-col",
965
- children: [
966
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormLabel, {
967
- name: field.name,
968
- children: field.label
969
- }),
970
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.TagInput, {
1001
+ children: [
1002
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_text_truncate.FormRoot, {
1003
+ store: form,
1004
+ "aria-label": labels.formAriaLabel,
1005
+ children: [fields.map((field) => {
1006
+ var _field$type;
1007
+ if (field.visible !== void 0) {
1008
+ const currentValues = form.getValues();
1009
+ if (!(typeof field.visible === "function" ? field.visible(currentValues) : field.visible)) return null;
1010
+ }
1011
+ if (field.type === "tags") {
1012
+ const rawValue = form.getValue(field.name);
1013
+ const tags = Array.isArray(rawValue) ? rawValue : [];
1014
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormField, {
1015
+ name: field.name,
1016
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.TagInput, {
1017
+ label: field.label,
971
1018
  value: tags,
972
1019
  onChange: (next) => form.setValue(field.name, next),
973
- placeholder: field.placeholder
974
- }),
975
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormError, { name: field.name })
976
- ]
1020
+ placeholder: field.placeholder,
1021
+ error: form.getFieldTouched(field.name) ? form.getError(field.name) : void 0
1022
+ })
1023
+ }, field.name);
1024
+ }
1025
+ if (field.type === "radio") {
1026
+ var _field$options;
1027
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
1028
+ className: "gap-element flex flex-col",
1029
+ children: [
1030
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("legend", {
1031
+ className: "text-text-primary text-sm font-medium",
1032
+ children: field.label
1033
+ }),
1034
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormRadioGroup, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1035
+ className: "gap-group flex",
1036
+ children: (_field$options = field.options) === null || _field$options === void 0 ? void 0 : _field$options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1037
+ className: "text-text-primary gap-element flex items-center text-sm",
1038
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormRadio, {
1039
+ name: field.name,
1040
+ value: option.value
1041
+ }), option.label]
1042
+ }, option.value))
1043
+ }) }),
1044
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormError, { name: field.name })
1045
+ ]
1046
+ }, field.name);
1047
+ }
1048
+ if (field.type === "checkbox") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_text_truncate.FormField, {
1049
+ name: field.name,
1050
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1051
+ className: "text-text-primary gap-element flex items-center text-sm",
1052
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormCheckbox, { name: field.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: field.label })]
1053
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormError, { name: field.name })]
977
1054
  }, field.name);
978
- }
979
- if (field.type === "radio") {
980
- var _field$options;
981
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("fieldset", {
982
- className: "gap-element flex flex-col",
983
- children: [
984
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)("legend", {
1055
+ if (field.type === "textarea") {
1056
+ var _field$rows, _field$resize;
1057
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_text_truncate.FormField, {
1058
+ name: field.name,
1059
+ children: [
1060
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormLabel, { children: field.label }),
1061
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormTextarea, {
1062
+ name: field.name,
1063
+ placeholder: field.placeholder,
1064
+ rows: (_field$rows = field.rows) !== null && _field$rows !== void 0 ? _field$rows : 4,
1065
+ resize: (_field$resize = field.resize) !== null && _field$resize !== void 0 ? _field$resize : "vertical",
1066
+ className: field.font === "mono" ? "font-mono text-sm" : void 0
1067
+ }),
1068
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormError, { name: field.name })
1069
+ ]
1070
+ }, field.name);
1071
+ }
1072
+ if (field.type === "select") {
1073
+ var _field$options2;
1074
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_text_truncate.FormField, {
1075
+ name: field.name,
1076
+ children: [
1077
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormLabel, {
1078
+ htmlFor: `${formId}-${field.name}`,
1079
+ children: field.label
1080
+ }),
1081
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormSelect, {
1082
+ name: field.name,
1083
+ id: `${formId}-${field.name}`,
1084
+ "aria-label": field.label,
1085
+ "aria-describedby": form.getError(field.name) && form.getFieldTouched(field.name) ? `${formId}-${field.name}-error` : void 0,
1086
+ placeholder: field.placeholder,
1087
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormSelectPopover, { children: (_field$options2 = field.options) === null || _field$options2 === void 0 ? void 0 : _field$options2.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormSelectItem, {
1088
+ value: option.value,
1089
+ children: option.label
1090
+ }, option.value)) })
1091
+ }),
1092
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormError, {
1093
+ name: field.name,
1094
+ id: `${formId}-${field.name}-error`
1095
+ })
1096
+ ]
1097
+ }, field.name);
1098
+ }
1099
+ if (field.type === "switch") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1100
+ className: "gap-group flex items-center",
1101
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormSwitch, { name: field.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1102
+ className: "flex flex-col",
1103
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
985
1104
  className: "text-text-primary text-sm font-medium",
986
1105
  children: field.label
987
- }),
988
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormRadioGroup, { children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
989
- className: "gap-group flex",
990
- children: (_field$options = field.options) === null || _field$options === void 0 ? void 0 : _field$options.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
991
- className: "text-text-primary gap-element flex items-center text-sm",
992
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormRadio, {
993
- name: field.name,
994
- value: option.value
995
- }), option.label]
996
- }, option.value))
997
- }) }),
998
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormError, { name: field.name })
999
- ]
1000
- }, field.name);
1001
- }
1002
- if (field.type === "checkbox") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1003
- className: "gap-element flex flex-col",
1004
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1005
- className: "text-text-primary gap-element flex items-center text-sm",
1006
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormCheckbox, { name: field.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { children: field.label })]
1007
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormError, { name: field.name })]
1008
- }, field.name);
1009
- if (field.type === "textarea") {
1010
- var _field$rows, _field$resize;
1011
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1012
- className: "gap-element flex flex-col",
1013
- children: [
1014
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormLabel, {
1015
- name: field.name,
1016
- children: field.label
1017
- }),
1018
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormTextarea, {
1019
- name: field.name,
1020
- placeholder: field.placeholder,
1021
- rows: (_field$rows = field.rows) !== null && _field$rows !== void 0 ? _field$rows : 4,
1022
- resize: (_field$resize = field.resize) !== null && _field$resize !== void 0 ? _field$resize : "vertical",
1023
- className: field.font === "mono" ? "font-mono text-sm" : void 0
1024
- }),
1025
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormError, { name: field.name })
1026
- ]
1106
+ }), field.description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1107
+ className: "text-text-secondary text-sm",
1108
+ children: field.description
1109
+ })]
1110
+ })]
1027
1111
  }, field.name);
1028
- }
1029
- if (field.type === "select") {
1030
- var _field$options2;
1031
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1032
- className: "gap-element flex flex-col",
1112
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_text_truncate.FormField, {
1113
+ name: field.name,
1033
1114
  children: [
1034
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormLabel, {
1035
- name: field.name,
1036
- children: field.label
1037
- }),
1038
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormSelect, {
1115
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormLabel, { children: field.label }),
1116
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormInput, {
1039
1117
  name: field.name,
1040
- placeholder: field.placeholder,
1041
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormSelectPopover, { children: (_field$options2 = field.options) === null || _field$options2 === void 0 ? void 0 : _field$options2.map((option) => /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormSelectItem, {
1042
- value: option.value,
1043
- children: option.label
1044
- }, option.value)) })
1118
+ type: (_field$type = field.type) !== null && _field$type !== void 0 ? _field$type : "text",
1119
+ placeholder: field.placeholder
1045
1120
  }),
1046
1121
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormError, { name: field.name })
1047
1122
  ]
1048
1123
  }, field.name);
1049
- }
1050
- if (field.type === "switch") return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("label", {
1051
- className: "gap-group flex items-center",
1052
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormSwitch, { name: field.name }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1053
- className: "flex flex-col",
1054
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1055
- className: "text-text-primary text-sm font-medium",
1056
- children: field.label
1057
- }), field.description && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
1058
- className: "text-text-secondary text-sm",
1059
- children: field.description
1060
- })]
1061
- })]
1062
- }, field.name);
1063
- return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
1064
- className: "gap-element flex flex-col",
1065
- children: [
1066
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormLabel, {
1067
- name: field.name,
1068
- children: field.label
1069
- }),
1070
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormInput, {
1071
- name: field.name,
1072
- type: (_field$type = field.type) !== null && _field$type !== void 0 ? _field$type : "text",
1073
- placeholder: field.placeholder
1074
- }),
1075
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormError, { name: field.name })
1076
- ]
1077
- }, field.name);
1078
- }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormSubmit, { children: labels.submitButton })]
1079
- }), submitted && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1080
- className: "border-success bg-success-subtle text-success-fg rounded-lg border p-3 text-sm",
1081
- children: labels.successMessage
1082
- })]
1124
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormSubmit, { children: labels.submitButton })]
1125
+ }),
1126
+ form.getSubmitError() !== void 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1127
+ role: "alert",
1128
+ className: "border-danger bg-danger-subtle text-danger-fg rounded-lg border p-3 text-sm",
1129
+ children: (_labels$submitError = labels.submitError) !== null && _labels$submitError !== void 0 ? _labels$submitError : "처리에 실패했습니다. 다시 시도해주세요."
1130
+ }),
1131
+ submitted && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
1132
+ role: "status",
1133
+ className: "border-success bg-success-subtle text-success-fg rounded-lg border p-3 text-sm",
1134
+ children: labels.successMessage
1135
+ })
1136
+ ]
1083
1137
  });
1084
1138
  }
1085
1139
  //#endregion
@@ -4621,18 +4675,6 @@ function QueryHistoryList({ items, loading = false, onLoad, onRerun, onClear, he
4621
4675
  "data-opt-slug": "query-history-list.QueryHistoryList",
4622
4676
  children: [
4623
4677
  header,
4624
- onClear && items.length > 0 && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4625
- className: "flex flex-none justify-end px-2 pt-1",
4626
- children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_text_truncate.Button, {
4627
- variant: "ghost",
4628
- size: "sm",
4629
- onClick: onClear,
4630
- children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Trash2, {
4631
- className: "size-3.5",
4632
- "aria-hidden": true
4633
- }), labels.clear]
4634
- })
4635
- }),
4636
4678
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4637
4679
  className: "min-h-0 flex-1 overflow-y-auto",
4638
4680
  children: loading ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("p", {
@@ -4695,6 +4737,19 @@ function QueryHistoryList({ items, loading = false, onLoad, onRerun, onClear, he
4695
4737
  }, item.id);
4696
4738
  })
4697
4739
  })
4740
+ }),
4741
+ onClear && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4742
+ className: "border-border-subtle flex flex-none justify-end border-t px-2 py-1",
4743
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_text_truncate.Button, {
4744
+ variant: "ghost",
4745
+ size: "sm",
4746
+ onClick: onClear,
4747
+ disabled: items.length === 0,
4748
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Trash2, {
4749
+ className: "size-3.5",
4750
+ "aria-hidden": true
4751
+ }), labels.clear]
4752
+ })
4698
4753
  })
4699
4754
  ]
4700
4755
  });
@@ -8576,6 +8631,652 @@ function FieldValues({ values, loading, total, sampledRows, onFilter, labels })
8576
8631
  });
8577
8632
  }
8578
8633
  //#endregion
8634
+ //#region src/shells/replay-scrubber.tsx
8635
+ const MARKER_MERGE_PX = 14;
8636
+ function defaultFormatTime(offset) {
8637
+ const total = Math.max(0, Math.floor(offset / 1e3));
8638
+ const minutes = Math.floor(total / 60);
8639
+ const seconds = total % 60;
8640
+ return `${minutes}:${String(seconds).padStart(2, "0")}`;
8641
+ }
8642
+ const DEFAULT_SEGMENT_NAMES = {
8643
+ active: "Active",
8644
+ inactive: "Inactive",
8645
+ gap: "No data",
8646
+ buffer: "Loading"
8647
+ };
8648
+ const SEGMENT_CLASS = {
8649
+ active: "bg-accent/35",
8650
+ inactive: "bg-bg-muted",
8651
+ gap: "bg-transparent",
8652
+ buffer: "bg-[repeating-linear-gradient(45deg,transparent_0,transparent_3px,var(--color-border)_3px,var(--color-border)_6px)]"
8653
+ };
8654
+ const MARKER_CLASS = {
8655
+ default: "bg-text-tertiary",
8656
+ accent: "bg-accent",
8657
+ danger: "bg-danger",
8658
+ warning: "bg-warning"
8659
+ };
8660
+ /**
8661
+ * Merge markers that would render within `mergePx` of each other. Each group
8662
+ * anchors on its first marker; the tooltip lists every member.
8663
+ */
8664
+ function groupReplayMarkers(markers, start, end, widthPx, mergePx = MARKER_MERGE_PX) {
8665
+ const span = Math.max(1, end - start);
8666
+ const threshold = widthPx > 0 ? mergePx / widthPx * span : 0;
8667
+ const sorted = [...markers].filter((marker) => marker.time >= start && marker.time <= end).sort((a, b) => a.time - b.time);
8668
+ const groups = [];
8669
+ for (const marker of sorted) {
8670
+ var _marker$tone;
8671
+ const last = groups[groups.length - 1];
8672
+ if (last && marker.time - last.time <= threshold) {
8673
+ last.items.push(marker);
8674
+ if (marker.tone === "danger") last.tone = "danger";
8675
+ else if (marker.tone === "warning" && last.tone !== "danger") last.tone = "warning";
8676
+ continue;
8677
+ }
8678
+ groups.push({
8679
+ key: marker.id,
8680
+ time: marker.time,
8681
+ items: [marker],
8682
+ tone: (_marker$tone = marker.tone) !== null && _marker$tone !== void 0 ? _marker$tone : "default"
8683
+ });
8684
+ }
8685
+ return groups;
8686
+ }
8687
+ /** Build the SVG area path for the activity band (log scale, like a seismograph). */
8688
+ function activityPath(activity) {
8689
+ if (!activity.length) return "";
8690
+ const n = activity.length;
8691
+ return `M 0,100 ${activity.map((value, index) => {
8692
+ const x = n === 1 ? 50 : index / (n - 1) * 100;
8693
+ const y = 100 - Math.log1p(Math.min(1, Math.max(0, value)) * 9) / Math.log(10) * 100;
8694
+ return `L ${x.toFixed(2)},${y.toFixed(2)}`;
8695
+ }).join(" ")} L 100,100 Z`;
8696
+ }
8697
+ /**
8698
+ * ReplayScrubber — the timeline of a session recording.
8699
+ *
8700
+ * A plain `Slider` knows only a position. A recording also has stretches
8701
+ * where nothing happened, stretches that have not loaded, events worth
8702
+ * jumping to, and a rhythm of activity. This track layers all of those
8703
+ * under one playhead and commits a seek only when the interaction ends,
8704
+ * so dragging never floods the player.
8705
+ */
8706
+ function ReplayScrubber({ start, end, position, segments = [], buffered = [], activity, markers = [], onSeek, onPreview, onMarkerSelect, formatTime, stepMs = 5e3, largeStepMs = 3e4, disabled = false, labels, className }) {
8707
+ var _labels$track, _labels$markerGroup;
8708
+ const id = (0, react.useId)();
8709
+ const trackRef = (0, react.useRef)(null);
8710
+ const [width, setWidth] = (0, react.useState)(0);
8711
+ const [hover, setHover] = (0, react.useState)(null);
8712
+ const [dragging, setDragging] = (0, react.useState)(null);
8713
+ const span = Math.max(1, end - start);
8714
+ const clamp = (0, react.useCallback)((time) => Math.min(end, Math.max(start, time)), [start, end]);
8715
+ const format = (0, react.useCallback)((time) => formatTime ? formatTime(time) : defaultFormatTime(time - start), [formatTime, start]);
8716
+ const shown = clamp(dragging !== null && dragging !== void 0 ? dragging : position);
8717
+ const percent = (shown - start) / span * 100;
8718
+ (0, react.useEffect)(() => {
8719
+ const node = trackRef.current;
8720
+ if (!node || typeof ResizeObserver === "undefined") {
8721
+ var _node$clientWidth;
8722
+ setWidth((_node$clientWidth = node === null || node === void 0 ? void 0 : node.clientWidth) !== null && _node$clientWidth !== void 0 ? _node$clientWidth : 0);
8723
+ return;
8724
+ }
8725
+ const observer = new ResizeObserver((entries) => {
8726
+ const entry = entries[0];
8727
+ if (entry) setWidth(entry.contentRect.width);
8728
+ });
8729
+ observer.observe(node);
8730
+ setWidth(node.clientWidth);
8731
+ return () => observer.disconnect();
8732
+ }, []);
8733
+ const timeAt = (0, react.useCallback)((clientX) => {
8734
+ var _trackRef$current;
8735
+ const rect = (_trackRef$current = trackRef.current) === null || _trackRef$current === void 0 ? void 0 : _trackRef$current.getBoundingClientRect();
8736
+ if (!rect || rect.width === 0) return start;
8737
+ return start + Math.min(1, Math.max(0, (clientX - rect.left) / rect.width)) * span;
8738
+ }, [start, span]);
8739
+ const handlePointerDown = (event) => {
8740
+ var _event$currentTarget$, _event$currentTarget;
8741
+ if (disabled || event.button !== 0) return;
8742
+ event.preventDefault();
8743
+ (_event$currentTarget$ = (_event$currentTarget = event.currentTarget).setPointerCapture) === null || _event$currentTarget$ === void 0 || _event$currentTarget$.call(_event$currentTarget, event.pointerId);
8744
+ event.currentTarget.focus();
8745
+ const time = timeAt(event.clientX);
8746
+ setDragging(time);
8747
+ onPreview === null || onPreview === void 0 || onPreview(time);
8748
+ };
8749
+ const handlePointerMove = (event) => {
8750
+ if (disabled) return;
8751
+ const time = timeAt(event.clientX);
8752
+ if (dragging !== null) {
8753
+ setDragging(time);
8754
+ onPreview === null || onPreview === void 0 || onPreview(time);
8755
+ } else {
8756
+ setHover(time);
8757
+ onPreview === null || onPreview === void 0 || onPreview(time);
8758
+ }
8759
+ };
8760
+ const handlePointerUp = (event) => {
8761
+ var _event$currentTarget$2, _event$currentTarget2;
8762
+ if (dragging === null) return;
8763
+ (_event$currentTarget$2 = (_event$currentTarget2 = event.currentTarget).releasePointerCapture) === null || _event$currentTarget$2 === void 0 || _event$currentTarget$2.call(_event$currentTarget2, event.pointerId);
8764
+ const time = clamp(timeAt(event.clientX));
8765
+ setDragging(null);
8766
+ onSeek(time);
8767
+ };
8768
+ const handlePointerCancel = () => {
8769
+ setDragging(null);
8770
+ onPreview === null || onPreview === void 0 || onPreview(null);
8771
+ };
8772
+ const handlePointerLeave = () => {
8773
+ setHover(null);
8774
+ if (dragging === null) onPreview === null || onPreview === void 0 || onPreview(null);
8775
+ };
8776
+ const handleKeyDown = (event) => {
8777
+ if (disabled) return;
8778
+ let next = null;
8779
+ switch (event.key) {
8780
+ case "ArrowLeft":
8781
+ case "ArrowDown":
8782
+ next = shown - (event.shiftKey ? largeStepMs : stepMs);
8783
+ break;
8784
+ case "ArrowRight":
8785
+ case "ArrowUp":
8786
+ next = shown + (event.shiftKey ? largeStepMs : stepMs);
8787
+ break;
8788
+ case "PageDown":
8789
+ next = shown - largeStepMs;
8790
+ break;
8791
+ case "PageUp":
8792
+ next = shown + largeStepMs;
8793
+ break;
8794
+ case "Home":
8795
+ next = start;
8796
+ break;
8797
+ case "End":
8798
+ next = end;
8799
+ break;
8800
+ default: return;
8801
+ }
8802
+ event.preventDefault();
8803
+ onSeek(clamp(next));
8804
+ };
8805
+ const groups = (0, react.useMemo)(() => groupReplayMarkers(markers, start, end, width), [
8806
+ markers,
8807
+ start,
8808
+ end,
8809
+ width
8810
+ ]);
8811
+ const path = (0, react.useMemo)(() => activity ? activityPath(activity) : "", [activity]);
8812
+ const trackLabel = (_labels$track = labels === null || labels === void 0 ? void 0 : labels.track) !== null && _labels$track !== void 0 ? _labels$track : "Replay position";
8813
+ const segmentNames = require_text_truncate._objectSpread2(require_text_truncate._objectSpread2({}, DEFAULT_SEGMENT_NAMES), labels === null || labels === void 0 ? void 0 : labels.segment);
8814
+ const groupLabel = (_labels$markerGroup = labels === null || labels === void 0 ? void 0 : labels.markerGroup) !== null && _labels$markerGroup !== void 0 ? _labels$markerGroup : ((count) => `${count} events`);
8815
+ const preview = dragging !== null && dragging !== void 0 ? dragging : hover;
8816
+ const previewPercent = preview === null ? null : (clamp(preview) - start) / span * 100;
8817
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8818
+ className: require_text_truncate.cn("relative flex w-full flex-col", className),
8819
+ "data-opt-id": "B98JP",
8820
+ "data-opt-slug": "replay-scrubber.ReplayScrubber",
8821
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
8822
+ className: "relative h-3",
8823
+ "aria-hidden": groups.length === 0,
8824
+ children: groups.map((group) => {
8825
+ const left = (group.time - start) / span * 100;
8826
+ 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…" : ""}`;
8827
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
8828
+ type: "button",
8829
+ title,
8830
+ "aria-label": group.items.length === 1 ? `${format(group.time)} · ${group.items[0].label}` : `${format(group.time)} · ${groupLabel(group.items.length)}`,
8831
+ disabled,
8832
+ onClick: (event) => {
8833
+ event.stopPropagation();
8834
+ const marker = group.items[0];
8835
+ if (onMarkerSelect) onMarkerSelect(marker);
8836
+ else onSeek(clamp(marker.time));
8837
+ },
8838
+ className: require_text_truncate.cn("absolute top-0 flex h-3 w-3 -translate-x-1/2 items-center justify-center rounded-sm", require_text_truncate.OPT_FOCUS_VISIBLE),
8839
+ style: { left: `${left}%` },
8840
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: require_text_truncate.cn("block h-2.5 rounded-sm", group.items.length > 1 ? "w-2" : "w-[3px]", MARKER_CLASS[group.tone]) })
8841
+ }, group.key);
8842
+ })
8843
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8844
+ ref: trackRef,
8845
+ id,
8846
+ role: "slider",
8847
+ tabIndex: disabled ? -1 : 0,
8848
+ "aria-label": trackLabel,
8849
+ "aria-valuemin": start,
8850
+ "aria-valuemax": end,
8851
+ "aria-valuenow": shown,
8852
+ "aria-valuetext": format(shown),
8853
+ "aria-disabled": disabled || void 0,
8854
+ onPointerDown: handlePointerDown,
8855
+ onPointerMove: handlePointerMove,
8856
+ onPointerUp: handlePointerUp,
8857
+ onPointerCancel: handlePointerCancel,
8858
+ onPointerLeave: handlePointerLeave,
8859
+ onKeyDown: handleKeyDown,
8860
+ className: require_text_truncate.cn("group relative h-6 w-full touch-none select-none rounded", disabled ? "cursor-not-allowed opacity-50" : "cursor-pointer", require_text_truncate.OPT_FOCUS_VISIBLE),
8861
+ children: [
8862
+ path ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("svg", {
8863
+ "aria-hidden": "true",
8864
+ viewBox: "0 0 100 100",
8865
+ preserveAspectRatio: "none",
8866
+ className: "pointer-events-none absolute inset-x-0 bottom-1 h-5 w-full opacity-40 transition-opacity group-hover:opacity-80",
8867
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("path", {
8868
+ d: path,
8869
+ className: "fill-accent/40"
8870
+ })
8871
+ }) : null,
8872
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8873
+ className: "bg-bg-subtle absolute inset-x-0 bottom-0 h-2 overflow-hidden rounded-full",
8874
+ children: [
8875
+ segments.map((segment, index) => {
8876
+ const left = (segment.start - start) / span * 100;
8877
+ const w = (segment.end - segment.start) / span * 100;
8878
+ if (w <= 0) return null;
8879
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8880
+ role: "img",
8881
+ "aria-label": `${segmentNames[segment.kind]} ${format(segment.start)}–${format(segment.end)}`,
8882
+ className: require_text_truncate.cn("absolute inset-y-0", SEGMENT_CLASS[segment.kind]),
8883
+ style: {
8884
+ left: `${left}%`,
8885
+ width: `${w}%`
8886
+ }
8887
+ }, `${segment.kind}-${index}`);
8888
+ }),
8889
+ buffered.map((range, index) => {
8890
+ const left = (range.start - start) / span * 100;
8891
+ const w = (range.end - range.start) / span * 100;
8892
+ if (w <= 0) return null;
8893
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8894
+ "aria-hidden": "true",
8895
+ className: "bg-text-tertiary/25 absolute inset-y-0",
8896
+ style: {
8897
+ left: `${left}%`,
8898
+ width: `${w}%`
8899
+ }
8900
+ }, `buffered-${index}`);
8901
+ }),
8902
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8903
+ "aria-hidden": "true",
8904
+ className: "bg-accent/80 absolute inset-y-0 left-0",
8905
+ style: { width: `${percent}%` }
8906
+ })
8907
+ ]
8908
+ }),
8909
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", {
8910
+ "aria-hidden": "true",
8911
+ 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",
8912
+ style: { left: `${percent}%` }
8913
+ }),
8914
+ previewPercent !== null && !disabled ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("output", {
8915
+ "aria-hidden": "true",
8916
+ className: require_text_truncate.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", require_text_truncate.OPT_TEXT_SECONDARY),
8917
+ style: { left: `${Math.min(97, Math.max(3, previewPercent))}%` },
8918
+ children: format(clamp(preview))
8919
+ }) : null
8920
+ ]
8921
+ })]
8922
+ });
8923
+ }
8924
+ ReplayScrubber.displayName = "ReplayScrubber";
8925
+ //#endregion
8926
+ //#region src/shells/replay-transport.tsx
8927
+ const DEFAULT_SPEEDS = [
8928
+ .5,
8929
+ 1,
8930
+ 1.5,
8931
+ 2,
8932
+ 3,
8933
+ 4,
8934
+ 8,
8935
+ 16
8936
+ ];
8937
+ /** `m:ss` for anything under an hour, `h:mm:ss` beyond it. */
8938
+ function formatReplayElapsed(ms) {
8939
+ const total = Math.max(0, Math.floor(ms / 1e3));
8940
+ const hours = Math.floor(total / 3600);
8941
+ const minutes = Math.floor(total % 3600 / 60);
8942
+ const seconds = total % 60;
8943
+ const mmss = `${hours ? String(minutes).padStart(2, "0") : minutes}:${String(seconds).padStart(2, "0")}`;
8944
+ return hours ? `${hours}:${mmss}` : mmss;
8945
+ }
8946
+ function defaultClock(time) {
8947
+ return new Date(time).toLocaleTimeString(void 0, {
8948
+ hour: "2-digit",
8949
+ minute: "2-digit",
8950
+ second: "2-digit"
8951
+ });
8952
+ }
8953
+ /**
8954
+ * ReplayTransport — play/pause, jumps, speed and skip-inactivity for a
8955
+ * session recording.
8956
+ *
8957
+ * It renders controls only; the host owns playback. Timecode shows elapsed
8958
+ * time by default and can flip to the clock time of the visitor's session,
8959
+ * which is what an engineer needs when matching a recording against logs.
8960
+ */
8961
+ 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 }) {
8962
+ 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;
8963
+ const jumpSeconds = Math.round(jumpMs / 1e3);
8964
+ const text = {
8965
+ play: (_labels$play = labels === null || labels === void 0 ? void 0 : labels.play) !== null && _labels$play !== void 0 ? _labels$play : "Play",
8966
+ pause: (_labels$pause = labels === null || labels === void 0 ? void 0 : labels.pause) !== null && _labels$pause !== void 0 ? _labels$pause : "Pause",
8967
+ restart: (_labels$restart = labels === null || labels === void 0 ? void 0 : labels.restart) !== null && _labels$restart !== void 0 ? _labels$restart : "Restart",
8968
+ skipToStart: (_labels$skipToStart = labels === null || labels === void 0 ? void 0 : labels.skipToStart) !== null && _labels$skipToStart !== void 0 ? _labels$skipToStart : "Go to start",
8969
+ back: (_labels$back = labels === null || labels === void 0 ? void 0 : labels.back) !== null && _labels$back !== void 0 ? _labels$back : ((s) => `Back ${s}s`),
8970
+ forward: (_labels$forward = labels === null || labels === void 0 ? void 0 : labels.forward) !== null && _labels$forward !== void 0 ? _labels$forward : ((s) => `Forward ${s}s`),
8971
+ speed: (_labels$speed = labels === null || labels === void 0 ? void 0 : labels.speed) !== null && _labels$speed !== void 0 ? _labels$speed : "Playback speed",
8972
+ skipInactive: (_labels$skipInactive = labels === null || labels === void 0 ? void 0 : labels.skipInactive) !== null && _labels$skipInactive !== void 0 ? _labels$skipInactive : "Skip inactivity",
8973
+ enterFullscreen: (_labels$enterFullscre = labels === null || labels === void 0 ? void 0 : labels.enterFullscreen) !== null && _labels$enterFullscre !== void 0 ? _labels$enterFullscre : "Enter fullscreen",
8974
+ exitFullscreen: (_labels$exitFullscree = labels === null || labels === void 0 ? void 0 : labels.exitFullscreen) !== null && _labels$exitFullscree !== void 0 ? _labels$exitFullscree : "Exit fullscreen",
8975
+ elapsed: (_labels$elapsed = labels === null || labels === void 0 ? void 0 : labels.elapsed) !== null && _labels$elapsed !== void 0 ? _labels$elapsed : "Show elapsed time",
8976
+ clock: (_labels$clock = labels === null || labels === void 0 ? void 0 : labels.clock) !== null && _labels$clock !== void 0 ? _labels$clock : "Show clock time"
8977
+ };
8978
+ const clamped = Math.min(end, Math.max(start, position));
8979
+ const timeLabel = timecode === "clock" ? formatClock(clamped) : `${formatReplayElapsed(clamped - start)} / ${formatReplayElapsed(end - start)}`;
8980
+ const withHint = (label, key) => key ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
8981
+ className: "inline-flex items-center gap-1",
8982
+ children: [label, /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.Kbd, {
8983
+ hint: true,
8984
+ children: key
8985
+ })]
8986
+ }) : label;
8987
+ const playLabel = playing ? text.pause : ended ? text.restart : text.play;
8988
+ const PlayIcon = playing ? lucide_react.Pause : ended ? lucide_react.RotateCcw : lucide_react.Play;
8989
+ const selectSpeed = (0, react.useCallback)((value) => () => onSpeedChange(value), [onSpeedChange]);
8990
+ (0, react.useEffect)(() => {
8991
+ if (!speeds.includes(speed)) onSpeedChange(speeds.includes(1) ? 1 : speeds[0]);
8992
+ }, [
8993
+ speed,
8994
+ speeds,
8995
+ onSpeedChange
8996
+ ]);
8997
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
8998
+ className: require_text_truncate.cn("flex w-full flex-wrap items-center gap-element", className),
8999
+ "data-opt-id": "RW9FW",
9000
+ "data-opt-slug": "replay-transport.ReplayTransport",
9001
+ children: [
9002
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9003
+ className: "flex items-center gap-0.5",
9004
+ children: [
9005
+ onSkipToStart ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.Button, {
9006
+ type: "button",
9007
+ size: "sm",
9008
+ variant: "ghost",
9009
+ "aria-label": text.skipToStart,
9010
+ title: text.skipToStart,
9011
+ disabled,
9012
+ onClick: onSkipToStart,
9013
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.SkipBack, {
9014
+ className: "size-4",
9015
+ "aria-hidden": "true"
9016
+ })
9017
+ }) : null,
9018
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.Button, {
9019
+ type: "button",
9020
+ size: "sm",
9021
+ variant: "ghost",
9022
+ "aria-label": text.back(jumpSeconds),
9023
+ title: (shortcuts === null || shortcuts === void 0 ? void 0 : shortcuts.back) ? `${text.back(jumpSeconds)} (${shortcuts.back})` : text.back(jumpSeconds),
9024
+ disabled,
9025
+ onClick: () => onSkip(-jumpMs),
9026
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Rewind, {
9027
+ className: "size-4",
9028
+ "aria-hidden": "true"
9029
+ })
9030
+ }),
9031
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.Button, {
9032
+ type: "button",
9033
+ size: "md",
9034
+ variant: "primary",
9035
+ "aria-label": playLabel,
9036
+ "aria-pressed": playing,
9037
+ title: (shortcuts === null || shortcuts === void 0 ? void 0 : shortcuts.playPause) ? `${playLabel} (${shortcuts.playPause})` : playLabel,
9038
+ disabled,
9039
+ onClick: onTogglePlay,
9040
+ className: "rounded-full px-0 [&]:h-9 [&]:w-9",
9041
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(PlayIcon, {
9042
+ className: "size-4",
9043
+ "aria-hidden": "true"
9044
+ })
9045
+ }),
9046
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.Button, {
9047
+ type: "button",
9048
+ size: "sm",
9049
+ variant: "ghost",
9050
+ "aria-label": text.forward(jumpSeconds),
9051
+ title: (shortcuts === null || shortcuts === void 0 ? void 0 : shortcuts.forward) ? `${text.forward(jumpSeconds)} (${shortcuts.forward})` : text.forward(jumpSeconds),
9052
+ disabled,
9053
+ onClick: () => onSkip(jumpMs),
9054
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.FastForward, {
9055
+ className: "size-4",
9056
+ "aria-hidden": "true"
9057
+ })
9058
+ })
9059
+ ]
9060
+ }),
9061
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
9062
+ type: "button",
9063
+ className: require_text_truncate.cn("min-w-[7.5rem] rounded px-1 text-left text-sm tabular-nums", require_text_truncate.OPT_TEXT_SECONDARY, onTimecodeChange ? "cursor-pointer hover:bg-bg-subtle" : "cursor-default"),
9064
+ "aria-label": timecode === "clock" ? text.elapsed : text.clock,
9065
+ title: timecode === "clock" ? text.elapsed : text.clock,
9066
+ disabled: !onTimecodeChange,
9067
+ onClick: () => onTimecodeChange === null || onTimecodeChange === void 0 ? void 0 : onTimecodeChange(timecode === "clock" ? "elapsed" : "clock"),
9068
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("output", {
9069
+ "aria-live": "off",
9070
+ children: timeLabel
9071
+ })
9072
+ }),
9073
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9074
+ className: "gap-element ml-auto flex items-center",
9075
+ children: [
9076
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_text_truncate.MenuRoot, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_text_truncate.MenuTrigger, {
9077
+ "aria-label": text.speed,
9078
+ title: text.speed,
9079
+ disabled,
9080
+ className: require_text_truncate.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", require_text_truncate.OPT_TEXT_SECONDARY),
9081
+ children: [
9082
+ speed,
9083
+ "×",
9084
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.ChevronDown, {
9085
+ className: "size-3",
9086
+ "aria-hidden": "true"
9087
+ })
9088
+ ]
9089
+ }), /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.MenuPopover, {
9090
+ "aria-label": text.speed,
9091
+ className: "min-w-[6rem]",
9092
+ children: speeds.map((value) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_text_truncate.MenuItemRadio, {
9093
+ name: "replay-speed",
9094
+ value: String(value),
9095
+ checked: value === speed,
9096
+ onChange: selectSpeed(value),
9097
+ className: "tabular-nums",
9098
+ children: [value, "×"]
9099
+ }, value))
9100
+ })] }),
9101
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.Toggle, {
9102
+ size: "sm",
9103
+ pressed: skipInactive,
9104
+ onPressedChange: onSkipInactiveChange,
9105
+ disabled,
9106
+ "aria-label": text.skipInactive,
9107
+ title: (shortcuts === null || shortcuts === void 0 ? void 0 : shortcuts.skipInactive) ? `${text.skipInactive} (${shortcuts.skipInactive})` : text.skipInactive,
9108
+ children: withHint(text.skipInactive, void 0)
9109
+ }),
9110
+ children,
9111
+ onFullscreenChange ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.Button, {
9112
+ type: "button",
9113
+ size: "sm",
9114
+ variant: "ghost",
9115
+ "aria-label": fullscreen ? text.exitFullscreen : text.enterFullscreen,
9116
+ "aria-pressed": !!fullscreen,
9117
+ title: (shortcuts === null || shortcuts === void 0 ? void 0 : shortcuts.fullscreen) ? `${fullscreen ? text.exitFullscreen : text.enterFullscreen} (${shortcuts.fullscreen})` : fullscreen ? text.exitFullscreen : text.enterFullscreen,
9118
+ onClick: () => onFullscreenChange(!fullscreen),
9119
+ children: fullscreen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Minimize2, {
9120
+ className: "size-4",
9121
+ "aria-hidden": "true"
9122
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.Maximize2, {
9123
+ className: "size-4",
9124
+ "aria-hidden": "true"
9125
+ })
9126
+ }) : null
9127
+ ]
9128
+ }),
9129
+ (shortcuts === null || shortcuts === void 0 ? void 0 : shortcuts.playPause) ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("span", {
9130
+ className: require_text_truncate.cn("sr-only", require_text_truncate.OPT_TEXT_TERTIARY),
9131
+ children: [
9132
+ playLabel,
9133
+ " ",
9134
+ shortcuts.playPause
9135
+ ]
9136
+ }) : null
9137
+ ]
9138
+ });
9139
+ }
9140
+ ReplayTransport.displayName = "ReplayTransport";
9141
+ //#endregion
9142
+ //#region src/shells/replay-player-layout.tsx
9143
+ function isTextTarget(target) {
9144
+ if (!(target instanceof HTMLElement)) return false;
9145
+ const tag = target.tagName;
9146
+ return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || target.isContentEditable;
9147
+ }
9148
+ /** Normalize a keyboard event into a shortcut key such as `space`, `arrowleft`, `shift+arrowright`. */
9149
+ function replayShortcutKey(event) {
9150
+ const key = event.key === " " ? "space" : event.key.toLowerCase();
9151
+ return [...[
9152
+ event.ctrlKey || event.metaKey ? "mod" : "",
9153
+ event.altKey ? "alt" : "",
9154
+ event.shiftKey ? "shift" : ""
9155
+ ].filter(Boolean), key].join("+");
9156
+ }
9157
+ /**
9158
+ * ReplayPlayerLayout — stage, chrome and inspector for a session replay.
9159
+ *
9160
+ * Owns three things every replay UI otherwise reinvents: a resizable
9161
+ * inspector that keeps its width, fullscreen on the whole player rather
9162
+ * than on the recording iframe alone, and a keyboard scope that reacts to
9163
+ * shortcuts only while the player is focused and never while someone is
9164
+ * typing in a field.
9165
+ */
9166
+ function ReplayPlayerLayout({ stage, meta, controls, inspector, inspectorOpen = true, onInspectorOpenChange, inspectorSize = 34, layoutId, fullscreen = false, onFullscreenChange, shortcuts, overlay, labels, className }) {
9167
+ var _labels$region, _labels$openInspector, _labels$closeInspecto;
9168
+ const rootRef = (0, react.useRef)(null);
9169
+ const [hovering, setHovering] = (0, react.useState)(false);
9170
+ const shortcutsRef = (0, react.useRef)(shortcuts);
9171
+ shortcutsRef.current = shortcuts;
9172
+ const text = {
9173
+ region: (_labels$region = labels === null || labels === void 0 ? void 0 : labels.region) !== null && _labels$region !== void 0 ? _labels$region : "Session replay",
9174
+ openInspector: (_labels$openInspector = labels === null || labels === void 0 ? void 0 : labels.openInspector) !== null && _labels$openInspector !== void 0 ? _labels$openInspector : "Show details",
9175
+ closeInspector: (_labels$closeInspecto = labels === null || labels === void 0 ? void 0 : labels.closeInspector) !== null && _labels$closeInspecto !== void 0 ? _labels$closeInspecto : "Hide details"
9176
+ };
9177
+ (0, react.useEffect)(() => {
9178
+ const node = rootRef.current;
9179
+ if (!node || typeof document === "undefined") return;
9180
+ const active = document.fullscreenElement === node;
9181
+ if (fullscreen && !active && node.requestFullscreen) node.requestFullscreen().catch(() => onFullscreenChange === null || onFullscreenChange === void 0 ? void 0 : onFullscreenChange(false));
9182
+ else if (!fullscreen && active && document.exitFullscreen) document.exitFullscreen().catch(() => {});
9183
+ }, [fullscreen, onFullscreenChange]);
9184
+ (0, react.useEffect)(() => {
9185
+ if (typeof document === "undefined") return;
9186
+ const handleChange = () => {
9187
+ const active = document.fullscreenElement === rootRef.current;
9188
+ if (active !== fullscreen) onFullscreenChange === null || onFullscreenChange === void 0 || onFullscreenChange(active);
9189
+ };
9190
+ document.addEventListener("fullscreenchange", handleChange);
9191
+ return () => document.removeEventListener("fullscreenchange", handleChange);
9192
+ }, [fullscreen, onFullscreenChange]);
9193
+ const handleKeyDown = (0, react.useCallback)((event) => {
9194
+ const map = shortcutsRef.current;
9195
+ if (!map || isTextTarget(event.target)) return;
9196
+ const handler = map[replayShortcutKey(event.nativeEvent)];
9197
+ if (!handler) return;
9198
+ if (handler(event.nativeEvent) !== false) {
9199
+ event.preventDefault();
9200
+ event.stopPropagation();
9201
+ }
9202
+ }, []);
9203
+ const showInspector = Boolean(inspector) && inspectorOpen;
9204
+ const toggle = onInspectorOpenChange ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.Button, {
9205
+ type: "button",
9206
+ size: "sm",
9207
+ variant: "ghost",
9208
+ "aria-label": inspectorOpen ? text.closeInspector : text.openInspector,
9209
+ "aria-pressed": inspectorOpen,
9210
+ title: inspectorOpen ? text.closeInspector : text.openInspector,
9211
+ onClick: () => onInspectorOpenChange(!inspectorOpen),
9212
+ children: inspectorOpen ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.PanelRightClose, {
9213
+ className: "size-4",
9214
+ "aria-hidden": "true"
9215
+ }) : /* @__PURE__ */ (0, react_jsx_runtime.jsx)(lucide_react.PanelRightOpen, {
9216
+ className: "size-4",
9217
+ "aria-hidden": "true"
9218
+ })
9219
+ }) : null;
9220
+ const main = /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9221
+ className: "flex h-full min-h-0 min-w-0 flex-1 flex-col",
9222
+ children: [
9223
+ meta || toggle ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9224
+ className: "gap-element border-border flex min-h-9 items-center border-b px-3 py-1 text-xs",
9225
+ children: [/* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9226
+ className: "min-w-0 flex-1",
9227
+ children: meta
9228
+ }), toggle]
9229
+ }) : null,
9230
+ /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
9231
+ className: "bg-bg-subtle relative min-h-0 flex-1",
9232
+ onPointerEnter: () => setHovering(true),
9233
+ onPointerLeave: () => setHovering(false),
9234
+ "data-hovering": hovering || void 0,
9235
+ children: [stage, overlay ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9236
+ className: "pointer-events-none absolute inset-0 flex items-center justify-center [&>*]:pointer-events-auto",
9237
+ children: overlay
9238
+ }) : null]
9239
+ }),
9240
+ controls ? /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9241
+ className: "border-border border-t px-3 py-2",
9242
+ children: controls
9243
+ }) : null
9244
+ ]
9245
+ });
9246
+ return /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9247
+ ref: rootRef,
9248
+ role: "region",
9249
+ "aria-label": text.region,
9250
+ tabIndex: -1,
9251
+ onKeyDown: handleKeyDown,
9252
+ className: require_text_truncate.cn("flex min-h-0 w-full flex-col overflow-hidden rounded-lg bg-surface", require_text_truncate.OPT_BORDER, require_text_truncate.OPT_FOCUS_VISIBLE, fullscreen ? "h-screen" : "h-full", className),
9253
+ "data-opt-id": "A3VYS",
9254
+ "data-opt-slug": "replay-player-layout.ReplayPlayerLayout",
9255
+ children: showInspector ? /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(require_text_truncate.ResizablePanelGroup, {
9256
+ direction: "horizontal",
9257
+ id: layoutId,
9258
+ children: [
9259
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.ResizablePanel, {
9260
+ defaultSize: 100 - inspectorSize,
9261
+ minSize: 40,
9262
+ children: main
9263
+ }),
9264
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.ResizableHandle, { withHandle: true }),
9265
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.ResizablePanel, {
9266
+ defaultSize: inspectorSize,
9267
+ minSize: 20,
9268
+ maxSize: 60,
9269
+ children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
9270
+ className: "flex h-full min-h-0 flex-col overflow-hidden",
9271
+ children: inspector
9272
+ })
9273
+ })
9274
+ ]
9275
+ }) : main
9276
+ });
9277
+ }
9278
+ ReplayPlayerLayout.displayName = "ReplayPlayerLayout";
9279
+ //#endregion
8579
9280
  Object.defineProperty(exports, "ALL_COMPOUND_THEMES", {
8580
9281
  enumerable: true,
8581
9282
  get: function() {
@@ -8870,6 +9571,24 @@ Object.defineProperty(exports, "QueryWorkspace", {
8870
9571
  return QueryWorkspace;
8871
9572
  }
8872
9573
  });
9574
+ Object.defineProperty(exports, "ReplayPlayerLayout", {
9575
+ enumerable: true,
9576
+ get: function() {
9577
+ return ReplayPlayerLayout;
9578
+ }
9579
+ });
9580
+ Object.defineProperty(exports, "ReplayScrubber", {
9581
+ enumerable: true,
9582
+ get: function() {
9583
+ return ReplayScrubber;
9584
+ }
9585
+ });
9586
+ Object.defineProperty(exports, "ReplayTransport", {
9587
+ enumerable: true,
9588
+ get: function() {
9589
+ return ReplayTransport;
9590
+ }
9591
+ });
8873
9592
  Object.defineProperty(exports, "SaveQueryDialog", {
8874
9593
  enumerable: true,
8875
9594
  get: function() {
@@ -9062,6 +9781,12 @@ Object.defineProperty(exports, "formatQueryCell", {
9062
9781
  return formatQueryCell;
9063
9782
  }
9064
9783
  });
9784
+ Object.defineProperty(exports, "formatReplayElapsed", {
9785
+ enumerable: true,
9786
+ get: function() {
9787
+ return formatReplayElapsed;
9788
+ }
9789
+ });
9065
9790
  Object.defineProperty(exports, "generatedTheme", {
9066
9791
  enumerable: true,
9067
9792
  get: function() {
@@ -9086,6 +9811,12 @@ Object.defineProperty(exports, "getThemeDescriptor", {
9086
9811
  return getThemeDescriptor;
9087
9812
  }
9088
9813
  });
9814
+ Object.defineProperty(exports, "groupReplayMarkers", {
9815
+ enumerable: true,
9816
+ get: function() {
9817
+ return groupReplayMarkers;
9818
+ }
9819
+ });
9089
9820
  Object.defineProperty(exports, "hasActiveQueryFilters", {
9090
9821
  enumerable: true,
9091
9822
  get: function() {
@@ -9182,6 +9913,12 @@ Object.defineProperty(exports, "removeClause", {
9182
9913
  return removeClause;
9183
9914
  }
9184
9915
  });
9916
+ Object.defineProperty(exports, "replayShortcutKey", {
9917
+ enumerable: true,
9918
+ get: function() {
9919
+ return replayShortcutKey;
9920
+ }
9921
+ });
9185
9922
  Object.defineProperty(exports, "resolveDescriptor", {
9186
9923
  enumerable: true,
9187
9924
  get: function() {
@@ -9230,6 +9967,12 @@ Object.defineProperty(exports, "toast", {
9230
9967
  return toast;
9231
9968
  }
9232
9969
  });
9970
+ Object.defineProperty(exports, "useAsyncSearch", {
9971
+ enumerable: true,
9972
+ get: function() {
9973
+ return useAsyncSearch;
9974
+ }
9975
+ });
9233
9976
  Object.defineProperty(exports, "useAutoRefresh", {
9234
9977
  enumerable: true,
9235
9978
  get: function() {