@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,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, {
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, {
1035
1117
  name: field.name,
1036
- children: field.label
1037
- }),
1038
- /* @__PURE__ */ (0, react_jsx_runtime.jsx)(require_text_truncate.FormSelect, {
1039
- 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
@@ -8577,6 +8631,652 @@ function FieldValues({ values, loading, total, sampledRows, onFilter, labels })
8577
8631
  });
8578
8632
  }
8579
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
8580
9280
  Object.defineProperty(exports, "ALL_COMPOUND_THEMES", {
8581
9281
  enumerable: true,
8582
9282
  get: function() {
@@ -8871,6 +9571,24 @@ Object.defineProperty(exports, "QueryWorkspace", {
8871
9571
  return QueryWorkspace;
8872
9572
  }
8873
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
+ });
8874
9592
  Object.defineProperty(exports, "SaveQueryDialog", {
8875
9593
  enumerable: true,
8876
9594
  get: function() {
@@ -9063,6 +9781,12 @@ Object.defineProperty(exports, "formatQueryCell", {
9063
9781
  return formatQueryCell;
9064
9782
  }
9065
9783
  });
9784
+ Object.defineProperty(exports, "formatReplayElapsed", {
9785
+ enumerable: true,
9786
+ get: function() {
9787
+ return formatReplayElapsed;
9788
+ }
9789
+ });
9066
9790
  Object.defineProperty(exports, "generatedTheme", {
9067
9791
  enumerable: true,
9068
9792
  get: function() {
@@ -9087,6 +9811,12 @@ Object.defineProperty(exports, "getThemeDescriptor", {
9087
9811
  return getThemeDescriptor;
9088
9812
  }
9089
9813
  });
9814
+ Object.defineProperty(exports, "groupReplayMarkers", {
9815
+ enumerable: true,
9816
+ get: function() {
9817
+ return groupReplayMarkers;
9818
+ }
9819
+ });
9090
9820
  Object.defineProperty(exports, "hasActiveQueryFilters", {
9091
9821
  enumerable: true,
9092
9822
  get: function() {
@@ -9183,6 +9913,12 @@ Object.defineProperty(exports, "removeClause", {
9183
9913
  return removeClause;
9184
9914
  }
9185
9915
  });
9916
+ Object.defineProperty(exports, "replayShortcutKey", {
9917
+ enumerable: true,
9918
+ get: function() {
9919
+ return replayShortcutKey;
9920
+ }
9921
+ });
9186
9922
  Object.defineProperty(exports, "resolveDescriptor", {
9187
9923
  enumerable: true,
9188
9924
  get: function() {
@@ -9231,6 +9967,12 @@ Object.defineProperty(exports, "toast", {
9231
9967
  return toast;
9232
9968
  }
9233
9969
  });
9970
+ Object.defineProperty(exports, "useAsyncSearch", {
9971
+ enumerable: true,
9972
+ get: function() {
9973
+ return useAsyncSearch;
9974
+ }
9975
+ });
9234
9976
  Object.defineProperty(exports, "useAutoRefresh", {
9235
9977
  enumerable: true,
9236
9978
  get: function() {