issue-sticker 0.3.1 → 0.5.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.
@@ -1,7 +1,7 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { memo, useRef, useState, useCallback, useEffect } from "react";
3
- import { c as createLucideIcon, u as ungzip_1, a as cn, R as Replayer } from "./createLucideIcon-CmSNiW63.js";
4
- import { T as TooltipProvider, a as Tooltip, b as TooltipTrigger, c as TooltipContent } from "./tooltip-DciWyUWs.js";
3
+ import { c as createLucideIcon, u as ungzip_1, a as cn, R as Replayer } from "./createLucideIcon-BzRMP02h.js";
4
+ import { T as TooltipProvider, a as Tooltip, b as TooltipTrigger, c as TooltipContent } from "./tooltip-B6UPO-U8.js";
5
5
  /**
6
6
  * @license lucide-react v0.468.0 - ISC
7
7
  *
@@ -72,8 +72,107 @@ async function loadReplayEvents(url) {
72
72
  }
73
73
  return parsed;
74
74
  }
75
+ const TYPE_LABEL = {
76
+ click: "클릭",
77
+ input: "입력",
78
+ nav: "이동"
79
+ };
80
+ const NODE_TYPE_ELEMENT = 2;
81
+ const NODE_TYPE_TEXT = 3;
82
+ class NodeIndex {
83
+ constructor() {
84
+ this.nodes = /* @__PURE__ */ new Map();
85
+ }
86
+ /**
87
+ * 색인을 비우고 주어진 트리로 다시 채운다.
88
+ *
89
+ * FullSnapshot 마다 호출한다. rrweb 은 문서마다 `record()` 를 새로 돌리며 nodeId 를
90
+ * 1부터 다시 매기므로, 문서가 바뀌면 앞 문서의 색인이 남아 있으면 안 된다 — 남으면
91
+ * 같은 id 의 엉뚱한 요소 이름이 조용히 붙는다.
92
+ */
93
+ reset(root) {
94
+ this.nodes = /* @__PURE__ */ new Map();
95
+ if (root) this.add(root);
96
+ }
97
+ /** 노드와 그 하위 트리를 색인에 넣는다. `parentId` 를 주면 부모의 자식으로도 잇는다. */
98
+ add(node, parentId) {
99
+ var _a;
100
+ if (!node || typeof node.id !== "number") return;
101
+ this.nodes.set(node.id, { node, childIds: [] });
102
+ if (parentId !== void 0) {
103
+ (_a = this.nodes.get(parentId)) == null ? void 0 : _a.childIds.push(node.id);
104
+ }
105
+ for (const child of node.childNodes ?? []) {
106
+ this.add(child, node.id);
107
+ }
108
+ }
109
+ get(id) {
110
+ var _a;
111
+ return (_a = this.nodes.get(id)) == null ? void 0 : _a.node;
112
+ }
113
+ /** 직접 자식 노드들. 트리 안의 childNodes 와 mutation 으로 붙은 것을 함께 본다. */
114
+ childrenOf(id) {
115
+ const entry = this.nodes.get(id);
116
+ if (!entry) return [];
117
+ const fromTree = entry.node.childNodes ?? [];
118
+ const fromMutation = entry.childIds.map((childId) => {
119
+ var _a;
120
+ return (_a = this.nodes.get(childId)) == null ? void 0 : _a.node;
121
+ }).filter((n) => n !== void 0 && !fromTree.includes(n));
122
+ return [...fromTree, ...fromMutation];
123
+ }
124
+ /** 하위 텍스트를 모두 이어 붙인다. button·a 같은 리프 레이블용. */
125
+ deepText(id, depth = 0) {
126
+ if (depth > 10) return "";
127
+ const node = this.get(id);
128
+ if (!node) return "";
129
+ if (node.type === NODE_TYPE_TEXT) return node.textContent ?? "";
130
+ return this.childrenOf(id).map(
131
+ (child) => child.type === NODE_TYPE_TEXT ? child.textContent ?? "" : typeof child.id === "number" ? this.deepText(child.id, depth + 1) : ""
132
+ ).join("");
133
+ }
134
+ /**
135
+ * 상호작용 대상을 사람이 읽을 짧은 레이블로 바꾼다. 우선순위는 다음과 같다.
136
+ * 1. aria-label / title
137
+ * 2. form 컨트롤: placeholder → name → type (예: input[email])
138
+ * 3. 직접 자식 Text 노드만 결합 (컨테이너가 자손 텍스트를 통째로 빨아들이는 것 방지)
139
+ * 4. button / a: 하위 텍스트 전체 (리프 레이블이라 짧다)
140
+ * 5. 압축 셀렉터: tag.firstClass 또는 <tag>
141
+ *
142
+ * 40자로 자른다. 색인에 없으면 `TYPE_LABEL[type]` 폴백.
143
+ */
144
+ describe(id, type) {
145
+ const node = id === void 0 ? void 0 : this.get(id);
146
+ if (!node || node.type !== NODE_TYPE_ELEMENT || id === void 0) return TYPE_LABEL[type];
147
+ const attrs = node.attributes ?? {};
148
+ const attr = (name) => {
149
+ const value = attrs[name];
150
+ return typeof value === "string" ? value : "";
151
+ };
152
+ const ariaLabel = (attr("aria-label") || attr("title")).trim();
153
+ if (ariaLabel) return ariaLabel.slice(0, 40);
154
+ const tag = (node.tagName ?? "").toLowerCase();
155
+ if (tag === "input" || tag === "textarea" || tag === "select") {
156
+ const placeholder = tag === "select" ? "" : attr("placeholder");
157
+ const name = attr("name");
158
+ const inputType = tag === "input" ? attr("type") : "";
159
+ const label = placeholder || name || (inputType ? `${tag}[${inputType}]` : tag);
160
+ return label.trim().slice(0, 40);
161
+ }
162
+ const directText = this.childrenOf(id).filter((child) => child.type === NODE_TYPE_TEXT).map((child) => child.textContent ?? "").join("").trim().replace(/\s+/g, " ");
163
+ if (directText) return directText.slice(0, 40);
164
+ if (tag === "button" || tag === "a") {
165
+ const deep = this.deepText(id).trim().replace(/\s+/g, " ");
166
+ if (deep) return deep.slice(0, 40);
167
+ }
168
+ const firstClass = attr("class").trim().split(/\s+/)[0];
169
+ return (firstClass ? `${tag}.${firstClass}` : `<${tag}>`).slice(0, 40);
170
+ }
171
+ }
172
+ const EVENT_TYPE_FULL_SNAPSHOT = 2;
75
173
  const EVENT_TYPE_INCREMENTAL = 3;
76
174
  const EVENT_TYPE_META = 4;
175
+ const SOURCE_MUTATION$1 = 0;
77
176
  const SOURCE_MOUSE_INTERACTION = 2;
78
177
  const SOURCE_INPUT = 5;
79
178
  const MOUSE_CLICK = 2;
@@ -85,6 +184,7 @@ function deriveTimeline(events) {
85
184
  let recordedHeight = 0;
86
185
  let seenFirstMeta = false;
87
186
  const out = [];
187
+ const index = new NodeIndex();
88
188
  for (const ev of list) {
89
189
  const ts = Number((ev == null ? void 0 : ev.timestamp) ?? 0);
90
190
  const t = ts - startTime;
@@ -96,15 +196,26 @@ function deriveTimeline(events) {
96
196
  recordedHeight = Number(data.height ?? 0);
97
197
  continue;
98
198
  }
99
- out.push({ t, type: "nav", href: String(data.href ?? "") });
199
+ const href = String(data.href ?? "");
200
+ out.push({ t, type: "nav", href, label: href || TYPE_LABEL.nav });
201
+ continue;
202
+ }
203
+ if ((ev == null ? void 0 : ev.type) === EVENT_TYPE_FULL_SNAPSHOT) {
204
+ index.reset(data.node);
100
205
  continue;
101
206
  }
102
207
  if ((ev == null ? void 0 : ev.type) === EVENT_TYPE_INCREMENTAL) {
103
208
  const source = Number(data.source);
104
- if (source === SOURCE_MOUSE_INTERACTION && Number(data.type) === MOUSE_CLICK) {
105
- out.push({ t, type: "click", nodeId: Number(data.id) });
209
+ if (source === SOURCE_MUTATION$1) {
210
+ for (const add of data.adds ?? []) {
211
+ index.add(add == null ? void 0 : add.node, add == null ? void 0 : add.parentId);
212
+ }
213
+ } else if (source === SOURCE_MOUSE_INTERACTION && Number(data.type) === MOUSE_CLICK) {
214
+ const nodeId = Number(data.id);
215
+ out.push({ t, type: "click", nodeId, label: index.describe(nodeId, "click") });
106
216
  } else if (source === SOURCE_INPUT) {
107
- out.push({ t, type: "input", nodeId: Number(data.id) });
217
+ const nodeId = Number(data.id);
218
+ out.push({ t, type: "input", nodeId, label: index.describe(nodeId, "input") });
108
219
  }
109
220
  }
110
221
  }
@@ -265,38 +376,6 @@ function stripWidgetEvents(events) {
265
376
  }
266
377
  return { events: out, stripped: widgetIds.size > 0, removedEvents };
267
378
  }
268
- const TYPE_LABEL = {
269
- click: "클릭",
270
- input: "입력",
271
- nav: "이동"
272
- };
273
- function describeNode(el, type) {
274
- if (!el) return TYPE_LABEL[type];
275
- const ariaLabel = (el.getAttribute("aria-label") ?? el.getAttribute("title") ?? "").trim();
276
- if (ariaLabel) return ariaLabel.slice(0, 40);
277
- const tag = el.tagName.toLowerCase();
278
- if (tag === "input" || tag === "textarea" || tag === "select") {
279
- const placeholder = tag !== "select" ? el.placeholder ?? "" : "";
280
- const name = el.name ?? "";
281
- const inputType = tag === "input" ? el.type ?? "" : "";
282
- const label = placeholder || name || (inputType ? `${tag}[${inputType}]` : tag);
283
- return label.trim().slice(0, 40);
284
- }
285
- let directText = "";
286
- for (const child of Array.from(el.childNodes)) {
287
- if (child.nodeType === Node.TEXT_NODE) {
288
- directText += child.textContent ?? "";
289
- }
290
- }
291
- directText = directText.trim().replace(/\s+/g, " ");
292
- if (directText) return directText.slice(0, 40);
293
- if (tag === "button" || tag === "a") {
294
- const text = (el.textContent ?? "").trim().replace(/\s+/g, " ");
295
- if (text) return text.slice(0, 40);
296
- }
297
- const firstClass = el.classList.item(0);
298
- return (firstClass ? `${tag}.${firstClass}` : `<${tag}>`).slice(0, 40);
299
- }
300
379
  const MOUSE_TAIL_CONFIG = {
301
380
  lineCap: "round",
302
381
  lineWidth: 3,
@@ -315,36 +394,14 @@ function formatTime(ms) {
315
394
  const s = total % 60;
316
395
  return `${m}:${s.toString().padStart(2, "0")}`;
317
396
  }
318
- function describeEvent(mirror, ev) {
319
- if (ev.type === "nav") return ev.href || TYPE_LABEL.nav;
320
- const node = ev.nodeId != null && mirror ? mirror.getNode(ev.nodeId) : null;
321
- return describeNode(node, ev.type);
322
- }
323
- function resolveLabel(mirror, row, cache) {
324
- if (row.type === "nav") return row.href || TYPE_LABEL.nav;
325
- if (row.nodeId == null) return TYPE_LABEL[row.type];
326
- const cached = cache.get(row.nodeId);
327
- if (cached !== void 0) return cached;
328
- const label = describeEvent(mirror, row);
329
- if (label !== TYPE_LABEL[row.type]) cache.set(row.nodeId, label);
330
- return label;
331
- }
332
397
  const EventLog = memo(function EventLog2({
333
398
  rows,
334
399
  currentIndex,
335
400
  filter,
336
401
  onFilterChange,
337
402
  onSeek,
338
- total,
339
- mirror
403
+ total
340
404
  }) {
341
- const labelCacheRef = useRef(/* @__PURE__ */ new Map());
342
- const prevRowsRef = useRef(rows);
343
- if (prevRowsRef.current !== rows) {
344
- prevRowsRef.current = rows;
345
- labelCacheRef.current = /* @__PURE__ */ new Map();
346
- }
347
- const labelCache = labelCacheRef.current;
348
405
  const filtered = filter === "all" ? rows : rows.filter((r) => r.type === filter);
349
406
  return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 min-w-0 flex-col rounded-md border border-line-subtle bg-surface-1", children: [
350
407
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-line-subtle px-3 py-2.5", children: [
@@ -381,7 +438,7 @@ const EventLog = memo(function EventLog2({
381
438
  children: [
382
439
  /* @__PURE__ */ jsx("span", { className: "w-9 shrink-0 tabular-nums text-text-tertiary", children: formatTime(row.t) }),
383
440
  /* @__PURE__ */ jsx("span", { className: cn("h-2 w-2 shrink-0 rounded-sm", TYPE_DOT[row.type]) }),
384
- /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-text-secondary", children: resolveLabel(mirror, row, labelCache) }),
441
+ /* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate text-text-secondary", children: row.label }),
385
442
  /* @__PURE__ */ jsx("span", { className: "shrink-0 text-text-tertiary", children: TYPE_LABEL[row.type] })
386
443
  ]
387
444
  },
@@ -400,7 +457,6 @@ function SessionReplayPlayer({ url }) {
400
457
  const durationRef = useRef(0);
401
458
  const isPlayingRef = useRef(false);
402
459
  const showTrailRef = useRef(INITIAL_SHOW_TRAIL);
403
- const mirrorRef = useRef(null);
404
460
  const [state, setState] = useState("loading");
405
461
  const [isPlaying, setIsPlaying] = useState(false);
406
462
  const [showTrail, setShowTrail] = useState(INITIAL_SHOW_TRAIL);
@@ -438,7 +494,6 @@ function SessionReplayPlayer({ url }) {
438
494
  applyIsPlaying(false);
439
495
  setShowTrail(INITIAL_SHOW_TRAIL);
440
496
  showTrailRef.current = INITIAL_SHOW_TRAIL;
441
- mirrorRef.current = null;
442
497
  setCurrentMs(0);
443
498
  startedRef.current = false;
444
499
  (async () => {
@@ -463,6 +518,7 @@ function SessionReplayPlayer({ url }) {
463
518
  }
464
519
  (_a = stageRef.current) == null ? void 0 : _a.remove();
465
520
  const stage = document.createElement("div");
521
+ stage.style.pointerEvents = "none";
466
522
  if (derived.recordedWidth > 0) {
467
523
  const recW = derived.recordedWidth;
468
524
  const recH = derived.recordedHeight || derived.recordedWidth;
@@ -494,15 +550,13 @@ function SessionReplayPlayer({ url }) {
494
550
  stopRaf();
495
551
  });
496
552
  replayerRef.current = replayer;
497
- mirrorRef.current = replayer.getMirror();
498
553
  setTimeline(derived.events);
499
554
  const totalTime = replayer.getMetaData().totalTime;
500
555
  applyDuration(totalTime);
501
556
  const rows = derived.events.map((ev, i) => ({
502
557
  t: ev.t,
503
558
  type: ev.type,
504
- nodeId: ev.nodeId,
505
- href: ev.href,
559
+ label: ev.label,
506
560
  index: i
507
561
  }));
508
562
  setLogRows(rows);
@@ -596,10 +650,10 @@ function SessionReplayPlayer({ url }) {
596
650
  );
597
651
  const currentIndex = logRows.reduce((acc, row) => row.t <= currentMs ? row.index : acc, -1);
598
652
  if (state === "error") {
599
- return /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: "리플레이를 불러오지 못했어요." });
653
+ return /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: "리플레이를 불러오지 못했습니다." });
600
654
  }
601
655
  if (state === "empty") {
602
- return /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: "재생할 리플레이 데이터가 없어요." });
656
+ return /* @__PURE__ */ jsx("p", { className: "text-sm text-text-secondary", children: "재생할 리플레이 데이터가 없습니다." });
603
657
  }
604
658
  return /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(
605
659
  "div",
@@ -620,7 +674,11 @@ function SessionReplayPlayer({ url }) {
620
674
  {
621
675
  ref: hostRef,
622
676
  "data-testid": "session-replay-host",
623
- className: "overflow-hidden rounded-t-md border border-line-subtle bg-surface-2",
677
+ onClick: state === "ready" ? togglePlay : void 0,
678
+ className: cn(
679
+ "overflow-hidden rounded-t-md border border-line-subtle bg-surface-2",
680
+ state === "ready" && "cursor-pointer"
681
+ ),
624
682
  style: { position: "relative", maxHeight: "80vh" }
625
683
  }
626
684
  ),
@@ -708,8 +766,7 @@ function SessionReplayPlayer({ url }) {
708
766
  filter,
709
767
  onFilterChange: setFilter,
710
768
  onSeek: seek,
711
- total: timeline.length,
712
- mirror: mirrorRef.current
769
+ total: timeline.length
713
770
  }
714
771
  ) }) }) : null
715
772
  ]