issue-sticker 0.5.0 → 0.7.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,83 +1,8 @@
1
- import { d as OVERLAY_LAYER_INDEX, q as gzip_1, r as record, a as cn, o as useCallbackRef$1, h as useComposedRefs, e as Primitive, P as Presence, i as Portal$1, f as composeEventHandlers, j as createContextScope, k as createSlot, D as DismissableLayer, C as Content, t as createPopperScope, l as useControllableState, v as Root2$1, m as useId, A as Anchor, w as Arrow, b as usePortalContainer, O as OVERLAY_LAYER, s as stopEscapePropagation, n as useLayoutEffect2, V as VISUALLY_HIDDEN_STYLES, c as createLucideIcon, x as clsx, S as Slot$2 } from "./createLucideIcon-BzRMP02h.js";
1
+ import { q as gzip_1, r as record, a as cn, o as useCallbackRef$1, h as useComposedRefs, e as Primitive, P as Presence, i as Portal$1, f as composeEventHandlers, j as createContextScope, k as createSlot, D as DismissableLayer, C as Content, t as createPopperScope, l as useControllableState, v as Root2$1, m as useId, A as Anchor, w as Arrow, b as usePortalContainer, d as OVERLAY_LAYER, s as stopEscapePropagation, n as useLayoutEffect2, V as VISUALLY_HIDDEN_STYLES, c as createLucideIcon, x as clsx, S as Slot$2, O as OVERLAY_LAYER_INDEX, M as MousePointer2 } from "./mouse-pointer-2-BR00y3ir.js";
2
2
  import { jsx, jsxs, Fragment } from "react/jsx-runtime";
3
3
  import * as React from "react";
4
4
  import React__default, { useState, createContext, useContext, useCallback, useRef, useLayoutEffect, useEffect, useMemo } from "react";
5
5
  import * as ReactDOM from "react-dom";
6
- const ISSUE_STICKER_HOST_ELEMENT_ID = "issue-sticker-host";
7
- const ISSUE_STICKER_RUNTIME_OWNER_ATTRIBUTE = "data-issue-sticker-runtime";
8
- const ISSUE_STICKER_EXTENSION_RUNTIME_READY_EVENT = "issue-sticker:extension-runtime-ready";
9
- const ISSUE_STICKER_RUNTIME_OWNER = {
10
- extension: "extension",
11
- sdk: "sdk"
12
- };
13
- function resolveDocument(doc) {
14
- return typeof document === "undefined" ? null : document;
15
- }
16
- function getIssueStickerRuntimeHost(doc) {
17
- var _a;
18
- return ((_a = resolveDocument()) == null ? void 0 : _a.getElementById(ISSUE_STICKER_HOST_ELEMENT_ID)) ?? null;
19
- }
20
- function getIssueStickerRuntimeOwner(element) {
21
- const owner = element == null ? void 0 : element.getAttribute(ISSUE_STICKER_RUNTIME_OWNER_ATTRIBUTE);
22
- if (owner === ISSUE_STICKER_RUNTIME_OWNER.extension || owner === ISSUE_STICKER_RUNTIME_OWNER.sdk) {
23
- return owner;
24
- }
25
- return null;
26
- }
27
- function markIssueStickerRuntimeHost(element, owner) {
28
- element.setAttribute(ISSUE_STICKER_RUNTIME_OWNER_ATTRIBUTE, owner);
29
- }
30
- function createRuntimeHostElement(owner, doc) {
31
- const targetDocument = resolveDocument();
32
- if (!targetDocument) {
33
- throw new Error("createRuntimeHostElement: document 를 찾을 수 없습니다.");
34
- }
35
- const hostElement = targetDocument.createElement("div");
36
- hostElement.id = ISSUE_STICKER_HOST_ELEMENT_ID;
37
- markIssueStickerRuntimeHost(hostElement, owner);
38
- hostElement.style.all = "initial";
39
- hostElement.style.position = "fixed";
40
- hostElement.style.top = "0";
41
- hostElement.style.left = "0";
42
- hostElement.style.width = "0";
43
- hostElement.style.height = "0";
44
- hostElement.style.overflow = "visible";
45
- hostElement.style.background = "transparent";
46
- hostElement.style.pointerEvents = "none";
47
- hostElement.style.zIndex = String(OVERLAY_LAYER_INDEX.runtimeHost);
48
- hostElement.style.display = "block";
49
- hostElement.style.isolation = "isolate";
50
- return hostElement;
51
- }
52
- function isEditableTarget(target) {
53
- if (!(target instanceof HTMLElement)) return false;
54
- return target.tagName === "INPUT" || target.tagName === "TEXTAREA" || target.isContentEditable === true;
55
- }
56
- function attachHostTypingGuard(shadowRoot) {
57
- const handleKeyDown = (event) => {
58
- const keyboardEvent = event;
59
- if (keyboardEvent.ctrlKey || keyboardEvent.metaKey || keyboardEvent.altKey) return;
60
- if (keyboardEvent.key === "Escape") return;
61
- if (!isEditableTarget(keyboardEvent.composedPath()[0])) return;
62
- keyboardEvent.stopPropagation();
63
- };
64
- shadowRoot.addEventListener("keydown", handleKeyDown);
65
- return () => shadowRoot.removeEventListener("keydown", handleKeyDown);
66
- }
67
- const RUNTIME_OWNER_MARKER_KEY = "__issueStickerRuntimeOwner";
68
- function markerHost() {
69
- return typeof window === "undefined" ? null : window;
70
- }
71
- function getClaimedIssueStickerRuntimeOwner() {
72
- var _a;
73
- return ((_a = markerHost()) == null ? void 0 : _a[RUNTIME_OWNER_MARKER_KEY]) ?? null;
74
- }
75
- function isIssueStickerExtensionRuntimeActive(doc) {
76
- if (getClaimedIssueStickerRuntimeOwner() === ISSUE_STICKER_RUNTIME_OWNER.extension) {
77
- return true;
78
- }
79
- return getIssueStickerRuntimeOwner(getIssueStickerRuntimeHost()) === ISSUE_STICKER_RUNTIME_OWNER.extension;
80
- }
81
6
  const DEFAULT_MAX_DURATION_MS = 12e4;
82
7
  const MIME_TYPE_CANDIDATES = ["video/webm;codecs=vp9", "video/webm"];
83
8
  function createInitialSnapshot$1() {
@@ -3837,13 +3762,18 @@ const RESOURCE_REQUEST_TYPES = /* @__PURE__ */ new Set([
3837
3762
  "csp_report",
3838
3763
  "resource"
3839
3764
  ]);
3765
+ const EXCLUDED_REQUEST_TYPES = /* @__PURE__ */ new Set(["font"]);
3766
+ function isExcludedRequestType(type) {
3767
+ return EXCLUDED_REQUEST_TYPES.has((type ?? "").toLowerCase());
3768
+ }
3840
3769
  function isAppRequest(log) {
3841
3770
  if (log.detailLevel === "timing-only") return false;
3842
3771
  return !RESOURCE_REQUEST_TYPES.has((log.type ?? "").toLowerCase());
3843
3772
  }
3844
3773
  function selectNetworkLogsForSubmit(logs) {
3845
- const detailed = logs.filter(isAppRequest);
3846
- const timing = logs.filter((log) => !isAppRequest(log));
3774
+ const candidates = logs.filter((log) => !isExcludedRequestType(log.type));
3775
+ const detailed = candidates.filter(isAppRequest);
3776
+ const timing = candidates.filter((log) => !isAppRequest(log));
3847
3777
  const keptDetailed = detailed.slice(-NETWORK_LOG_SUBMIT_LIMIT);
3848
3778
  const timingQuota = Math.min(
3849
3779
  NETWORK_LOG_TIMING_SUBMIT_CAP,
@@ -3944,15 +3874,59 @@ class ConsoleCollector {
3944
3874
  this.logs = [];
3945
3875
  this.originals = {};
3946
3876
  this.running = false;
3877
+ this.seenErrors = /* @__PURE__ */ new WeakSet();
3878
+ this.handleWindowError = (event) => {
3879
+ if (this.markSeen(event.error)) return;
3880
+ const detail = event.error != null ? serializeArgs([event.error]) : event.message;
3881
+ this.addErrorLog(`Uncaught ${detail}`, event.error, {
3882
+ lineNumber: event.lineno || void 0,
3883
+ columnNumber: event.colno || void 0
3884
+ });
3885
+ };
3886
+ this.handleUnhandledRejection = (event) => {
3887
+ if (this.markSeen(event.reason)) return;
3888
+ this.addErrorLog(`Unhandled promise rejection: ${serializeArgs([event.reason])}`, event.reason);
3889
+ };
3890
+ this.wrappers = {};
3947
3891
  this.onChange = (options == null ? void 0 : options.onChange) ?? null;
3948
3892
  }
3893
+ /** 처음 보는 값이면 표시하고 false. 이미 본 오류 객체면 true. */
3894
+ markSeen(value) {
3895
+ if (typeof value !== "object" || value === null) return false;
3896
+ if (this.seenErrors.has(value)) return true;
3897
+ this.seenErrors.add(value);
3898
+ return false;
3899
+ }
3900
+ addErrorLog(message2, error, position) {
3901
+ this.addLog({
3902
+ type: "error",
3903
+ message: redactSensitiveText(message2),
3904
+ timestamp: Date.now(),
3905
+ url: getCurrentPageUrl() || void 0,
3906
+ ...position,
3907
+ stackTrace: redactSensitiveText(error instanceof Error ? error.stack : void 0)
3908
+ });
3909
+ }
3949
3910
  start() {
3950
3911
  if (this.running) return;
3951
3912
  this.running = true;
3913
+ if (typeof window !== "undefined") {
3914
+ window.addEventListener("error", this.handleWindowError);
3915
+ window.addEventListener("unhandledrejection", this.handleUnhandledRejection);
3916
+ }
3952
3917
  for (const method of CONSOLE_METHODS) {
3953
- this.originals[method] = console[method];
3954
- console[method] = (...args) => {
3955
- var _a;
3918
+ const original = console[method];
3919
+ this.originals[method] = original;
3920
+ const wrapper = (...args) => {
3921
+ if (this.wrappers[method] !== wrapper) {
3922
+ original.apply(console, args);
3923
+ return;
3924
+ }
3925
+ const duplicate = args.some((arg) => arg instanceof Error && this.markSeen(arg));
3926
+ if (duplicate) {
3927
+ original.apply(console, args);
3928
+ return;
3929
+ }
3956
3930
  this.addLog({
3957
3931
  type: methodToLogType(method),
3958
3932
  message: serializeArgs(args),
@@ -3960,19 +3934,26 @@ class ConsoleCollector {
3960
3934
  url: getCurrentPageUrl() || void 0,
3961
3935
  stackTrace: redactSensitiveText(captureConsoleStackTrace(args))
3962
3936
  });
3963
- (_a = this.originals[method]) == null ? void 0 : _a.apply(console, args);
3937
+ original.apply(console, args);
3964
3938
  };
3939
+ this.wrappers[method] = wrapper;
3940
+ console[method] = wrapper;
3965
3941
  }
3966
3942
  }
3967
3943
  stop() {
3968
3944
  if (!this.running) return;
3969
3945
  this.running = false;
3946
+ if (typeof window !== "undefined") {
3947
+ window.removeEventListener("error", this.handleWindowError);
3948
+ window.removeEventListener("unhandledrejection", this.handleUnhandledRejection);
3949
+ }
3970
3950
  for (const method of CONSOLE_METHODS) {
3971
- if (this.originals[method]) {
3951
+ if (this.originals[method] && console[method] === this.wrappers[method]) {
3972
3952
  console[method] = this.originals[method];
3973
3953
  }
3974
3954
  }
3975
3955
  this.originals = {};
3956
+ this.wrappers = {};
3976
3957
  }
3977
3958
  /** 리로드 이전 로그를 버퍼 앞에 되돌린다. */
3978
3959
  restore(logs) {
@@ -3994,6 +3975,7 @@ class ConsoleCollector {
3994
3975
  }
3995
3976
  clear() {
3996
3977
  this.logs = [];
3978
+ this.seenErrors = /* @__PURE__ */ new WeakSet();
3997
3979
  }
3998
3980
  addLog(log) {
3999
3981
  var _a;
@@ -12805,7 +12787,7 @@ const DEFAULT_LABELS = {
12805
12787
  close: "가이드 닫기",
12806
12788
  skip: "건너뛰기",
12807
12789
  skipConfirmTitle: "가이드를 건너뛸까요?",
12808
- // 표면마다 진입점이 다르다(admin=헤더 아이콘, extension=더보기 메뉴, sdk=FAB 메뉴).
12790
+ // 채널마다 진입점이 다르다(admin=헤더 아이콘, extension=더보기 메뉴, sdk=FAB 메뉴).
12809
12791
  // 위치를 특정하면 어느 한쪽에서는 틀린 안내가 되므로 라벨만 가리킨다.
12810
12792
  skipConfirmBody: "'가이드 다시 보기'를 클릭하면 언제든 다시 열 수 있습니다.",
12811
12793
  skipConfirmCancel: "계속 보기",
@@ -12817,8 +12799,9 @@ const DEFAULT_LABELS = {
12817
12799
  };
12818
12800
  const MASK_PADDING = 6;
12819
12801
  const CUTOUT_RADIUS = 12;
12820
- const POPOVER_WIDTH = 336;
12802
+ const POPOVER_WIDTH = 360;
12821
12803
  const POPOVER_FALLBACK_HEIGHT = 190;
12804
+ const DEFAULT_POPOVER_SIZE = { width: POPOVER_WIDTH, height: POPOVER_FALLBACK_HEIGHT };
12822
12805
  const POPOVER_GAP = 12;
12823
12806
  function clamp(value, min2, max2) {
12824
12807
  return Math.min(Math.max(value, min2), max2);
@@ -12884,12 +12867,14 @@ function areRectsEqual(previous, next) {
12884
12867
  if (!previous || !next) return previous === next;
12885
12868
  return Math.abs(previous.top - next.top) < 0.5 && Math.abs(previous.right - next.right) < 0.5 && Math.abs(previous.bottom - next.bottom) < 0.5 && Math.abs(previous.left - next.left) < 0.5 && Math.abs(previous.width - next.width) < 0.5 && Math.abs(previous.height - next.height) < 0.5;
12886
12869
  }
12887
- function getPopoverCoordinates(position, targetRect) {
12870
+ function getPopoverCoordinates(position, targetRect, size = {
12871
+ width: POPOVER_WIDTH,
12872
+ height: POPOVER_FALLBACK_HEIGHT
12873
+ }) {
12888
12874
  if (Array.isArray(position)) return position;
12889
12875
  const viewportWidth = window.innerWidth;
12890
12876
  const viewportHeight = window.innerHeight;
12891
- const width = POPOVER_WIDTH;
12892
- const height = POPOVER_FALLBACK_HEIGHT;
12877
+ const { width, height } = size;
12893
12878
  const maxX = Math.max(POPOVER_GAP, viewportWidth - width - POPOVER_GAP);
12894
12879
  const maxY = Math.max(POPOVER_GAP, viewportHeight - height - POPOVER_GAP);
12895
12880
  const xOnTarget = targetRect.left + targetRect.width / 2 - width / 2;
@@ -12923,6 +12908,49 @@ function getPopoverCoordinates(position, targetRect) {
12923
12908
  ];
12924
12909
  }
12925
12910
  }
12911
+ function rectsOverlap(a2, b) {
12912
+ return a2.left < b.right && a2.right > b.left && a2.top < b.bottom && a2.bottom > b.top;
12913
+ }
12914
+ function overlapArea(a2, b) {
12915
+ const w = Math.min(a2.right, b.right) - Math.max(a2.left, b.left);
12916
+ const h = Math.min(a2.bottom, b.bottom) - Math.max(a2.top, b.top);
12917
+ return w > 0 && h > 0 ? w * h : 0;
12918
+ }
12919
+ const SIDE_ORDER = ["bottom", "top", "right", "left"];
12920
+ const OPPOSITE = {
12921
+ top: "bottom",
12922
+ bottom: "top",
12923
+ left: "right",
12924
+ right: "left"
12925
+ };
12926
+ function resolvePopoverPlacement(position, targetRect, size, avoidRects = []) {
12927
+ if (Array.isArray(position)) return { left: position[0], top: position[1], side: null };
12928
+ if (position === "center") {
12929
+ const [left, top] = getPopoverCoordinates(position, targetRect, size);
12930
+ return { left, top, side: null };
12931
+ }
12932
+ const blockers = [targetRect, ...avoidRects];
12933
+ const requested = position ?? "bottom";
12934
+ const candidates = [requested, OPPOSITE[requested], ...SIDE_ORDER];
12935
+ let best = null;
12936
+ for (const candidate of candidates) {
12937
+ const [left, top] = getPopoverCoordinates(candidate, targetRect, size);
12938
+ const rect = {
12939
+ left,
12940
+ top,
12941
+ right: left + size.width,
12942
+ bottom: top + size.height,
12943
+ width: size.width,
12944
+ height: size.height
12945
+ };
12946
+ const area = blockers.reduce((sum, blocker) => sum + overlapArea(rect, blocker), 0);
12947
+ const placement = { left, top, side: candidate };
12948
+ if (!blockers.some((blocker) => rectsOverlap(rect, blocker))) return placement;
12949
+ if (!best || area < best.area) best = { placement, area };
12950
+ }
12951
+ const fallback = getPopoverCoordinates(requested, targetRect, size);
12952
+ return (best == null ? void 0 : best.placement) ?? { left: fallback[0], top: fallback[1], side: requested };
12953
+ }
12926
12954
  function getViewportRect() {
12927
12955
  return {
12928
12956
  top: 0,
@@ -12933,6 +12961,9 @@ function getViewportRect() {
12933
12961
  height: window.innerHeight
12934
12962
  };
12935
12963
  }
12964
+ const TAP_INSET = 8;
12965
+ const TAP_RIPPLE_SIZE = 44;
12966
+ const LINK_BUTTON_CLASS = "inline-flex h-[30px] items-center justify-center rounded-sm text-xs font-semibold text-text-tertiary underline-offset-4 transition-colors hover:text-text-primary hover:underline focus-visible:outline-none focus-visible:shadow-focus disabled:pointer-events-none disabled:opacity-50";
12936
12967
  function shouldRenderNextButton(step) {
12937
12968
  if (step.advanceOnTargetClick) return false;
12938
12969
  if (step.advanceOn && !step.allowManualAdvance) return false;
@@ -12950,18 +12981,67 @@ function OnboardingStyleTag() {
12950
12981
  transition: opacity ${POPOVER_FADE_MS}ms ease-out;
12951
12982
  }
12952
12983
 
12984
+ @keyframes issue-sticker-onboarding-tap {
12985
+ 0%,
12986
+ 55%,
12987
+ 100% {
12988
+ transform: translate(0, 0);
12989
+ }
12990
+ 30% {
12991
+ transform: translate(-5px, -5px);
12992
+ }
12993
+ }
12994
+
12995
+ @keyframes issue-sticker-onboarding-ripple {
12996
+ 0% {
12997
+ transform: translate(-50%, -50%) scale(0.3);
12998
+ opacity: 0.5;
12999
+ }
13000
+ 55%,
13001
+ 100% {
13002
+ transform: translate(-50%, -50%) scale(1.7);
13003
+ opacity: 0;
13004
+ }
13005
+ }
13006
+
13007
+ .issue-sticker-onboarding-tap {
13008
+ animation: issue-sticker-onboarding-tap 1.6s ease-in-out infinite;
13009
+ }
13010
+
13011
+ .issue-sticker-onboarding-tap-ripple {
13012
+ animation: issue-sticker-onboarding-ripple 1.6s ease-out infinite;
13013
+ }
13014
+
13015
+ @keyframes issue-sticker-onboarding-focus {
13016
+ from {
13017
+ transform: scale(1.05);
13018
+ opacity: 0;
13019
+ }
13020
+ to {
13021
+ transform: scale(1);
13022
+ opacity: 1;
13023
+ }
13024
+ }
13025
+
13026
+ .issue-sticker-onboarding-highlight {
13027
+ animation: issue-sticker-onboarding-focus 220ms ease-out;
13028
+ }
13029
+
12953
13030
  @media (prefers-reduced-motion: reduce) {
12954
13031
  .issue-sticker-onboarding-popover {
12955
13032
  transition-duration: 1ms !important;
12956
13033
  }
12957
13034
 
12958
- .issue-sticker-onboarding-highlight {
13035
+ .issue-sticker-onboarding-highlight,
13036
+ .issue-sticker-onboarding-tap,
13037
+ .issue-sticker-onboarding-tap-ripple {
12959
13038
  animation: none !important;
12960
13039
  }
12961
13040
  }
12962
13041
  ` });
12963
13042
  }
12964
13043
  function OnboardingPopoverContent({
13044
+ allowSkip,
12965
13045
  step,
12966
13046
  clickHint,
12967
13047
  labels,
@@ -13026,26 +13106,53 @@ function OnboardingPopoverContent({
13026
13106
  }
13027
13107
  );
13028
13108
  }
13109
+ const hasNextButton = shouldRenderNextButton(step);
13029
13110
  return /* @__PURE__ */ jsxs("div", { children: [
13030
13111
  /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold leading-5 text-text-primary", children: step.title }),
13031
- /* @__PURE__ */ jsx("p", { className: "mt-2 whitespace-pre-line text-sm leading-5 text-text-secondary", children: step.body }),
13032
- clickHint ? /* @__PURE__ */ jsxs("p", { className: "mt-2 flex items-center gap-1.5 text-xs font-semibold leading-5 text-brand", children: [
13033
- /* @__PURE__ */ jsx(MousePointerClick, { "aria-hidden": "true", className: "h-3.5 w-3.5 shrink-0" }),
13034
- clickHint
13035
- ] }) : null,
13036
- /* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center justify-between gap-2", children: [
13037
- /* @__PURE__ */ jsx(
13038
- "button",
13112
+ /* @__PURE__ */ jsx("p", { className: "mt-2 whitespace-pre-line text-sm leading-[1.55] text-text-secondary", children: step.body }),
13113
+ step.bodyExtra ? /* @__PURE__ */ jsx("div", { className: "mt-3", children: step.bodyExtra }) : null,
13114
+ clickHint ? (
13115
+ /* 진행 버튼이 없는 단계에서는 이 줄이 유일한 다음 행동이다. 그래서 버튼이 있던
13116
+ * 자리만큼의 무게를 준다 — 작은 글줄로 두면 사용자가 못 보고 다른 버튼을 누른다. */
13117
+ /* @__PURE__ */ jsxs(
13118
+ "p",
13039
13119
  {
13040
- type: "button",
13041
- onClick: onRequestSkip,
13042
- disabled: !isVisible,
13043
- className: "inline-flex h-[30px] items-center justify-center rounded-sm text-xs font-semibold text-text-tertiary underline-offset-4 transition-colors hover:text-text-primary hover:underline focus-visible:outline-none focus-visible:shadow-focus disabled:pointer-events-none disabled:opacity-50",
13044
- children: labels.skip
13120
+ className: cn(
13121
+ "mt-3 flex items-center gap-1.5 font-semibold text-brand",
13122
+ hasNextButton ? "text-xs leading-5" : "rounded-md border border-brand/40 bg-brand/10 px-3 py-2 text-sm leading-5"
13123
+ ),
13124
+ children: [
13125
+ /* @__PURE__ */ jsx(MousePointerClick, { "aria-hidden": "true", className: "h-4 w-4 shrink-0" }),
13126
+ clickHint
13127
+ ]
13045
13128
  }
13046
- ),
13129
+ )
13130
+ ) : null,
13131
+ /* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center justify-between gap-2", children: [
13132
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
13133
+ allowSkip ? /* @__PURE__ */ jsx(
13134
+ "button",
13135
+ {
13136
+ type: "button",
13137
+ onClick: onRequestSkip,
13138
+ disabled: !isVisible,
13139
+ className: LINK_BUTTON_CLASS,
13140
+ children: labels.skip
13141
+ }
13142
+ ) : null,
13143
+ canGoPrevious && !hasNextButton ? /* @__PURE__ */ jsx(
13144
+ "button",
13145
+ {
13146
+ type: "button",
13147
+ onClick: onPrevious,
13148
+ disabled: !isVisible || isSkipConfirmOpen,
13149
+ className: LINK_BUTTON_CLASS,
13150
+ children: labels.previous
13151
+ }
13152
+ ) : null
13153
+ ] }),
13047
13154
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
13048
- canGoPrevious ? /* @__PURE__ */ jsx(
13155
+ canGoPrevious && hasNextButton ? /* @__PURE__ */ jsx(
13049
13156
  "button",
13050
13157
  {
13051
13158
  type: "button",
@@ -13055,7 +13162,7 @@ function OnboardingPopoverContent({
13055
13162
  children: labels.previous
13056
13163
  }
13057
13164
  ) : null,
13058
- shouldRenderNextButton(step) ? /* @__PURE__ */ jsx(
13165
+ hasNextButton ? /* @__PURE__ */ jsx(
13059
13166
  "button",
13060
13167
  {
13061
13168
  type: "button",
@@ -13073,6 +13180,9 @@ function OnboardingOverlay({
13073
13180
  step,
13074
13181
  targetRect,
13075
13182
  targetRadius,
13183
+ avoidRects,
13184
+ elevateHighlight,
13185
+ allowSkip = true,
13076
13186
  className,
13077
13187
  labels,
13078
13188
  isPopoverVisible,
@@ -13083,6 +13193,7 @@ function OnboardingOverlay({
13083
13193
  onPrevious
13084
13194
  }) {
13085
13195
  const maskId = React.useId().replace(/:/g, "");
13196
+ const renderHighlight = (node) => elevateHighlight && typeof document !== "undefined" ? ReactDOM.createPortal(node, document.body) : node;
13086
13197
  const [isSkipConfirmOpen, setIsSkipConfirmOpen] = React.useState(false);
13087
13198
  const cutoutRect = targetRect ? padRect(targetRect, MASK_PADDING) : null;
13088
13199
  const cutoutRadius = cutoutRect ? Math.min(
@@ -13090,12 +13201,35 @@ function OnboardingOverlay({
13090
13201
  cutoutRect.width / 2,
13091
13202
  cutoutRect.height / 2
13092
13203
  ) : 0;
13093
- const [popoverLeft, popoverTop] = getPopoverCoordinates(
13204
+ const popoverRef = React.useRef(null);
13205
+ const [popoverSize, setPopoverSize] = React.useState(
13206
+ null
13207
+ );
13208
+ React.useLayoutEffect(() => {
13209
+ const element = popoverRef.current;
13210
+ if (!element) return void 0;
13211
+ const measure = () => {
13212
+ const rect = element.getBoundingClientRect();
13213
+ setPopoverSize(
13214
+ (current) => current && Math.abs(current.height - rect.height) < 0.5 && Math.abs(current.width - rect.width) < 0.5 ? current : { width: rect.width, height: rect.height }
13215
+ );
13216
+ };
13217
+ measure();
13218
+ if (typeof ResizeObserver === "undefined") return void 0;
13219
+ const observer = new ResizeObserver(measure);
13220
+ observer.observe(element);
13221
+ return () => observer.disconnect();
13222
+ }, [step.id, step.body, isSkipConfirmOpen]);
13223
+ const effectiveSize = popoverSize && popoverSize.width > 0 && popoverSize.height > 0 ? popoverSize : DEFAULT_POPOVER_SIZE;
13224
+ const placement = resolvePopoverPlacement(
13094
13225
  step.position ?? (cutoutRect ? "bottom" : "center"),
13095
- cutoutRect ?? getViewportRect()
13226
+ cutoutRect ?? getViewportRect(),
13227
+ effectiveSize,
13228
+ cutoutRect ? avoidRects ?? [] : []
13096
13229
  );
13230
+ const { left: popoverLeft, top: popoverTop } = placement;
13097
13231
  const shouldCenterPopover = !cutoutRect && (step.position ?? "center") === "center";
13098
- const maskColor = "rgba(8, 13, 18, 0.62)";
13232
+ const maskColor = isTargetInteractionStep(step) ? "rgba(8, 13, 18, 0.74)" : "rgba(8, 13, 18, 0.62)";
13099
13233
  const isPassthrough = !!step.advanceOn;
13100
13234
  const isTargetInteraction = isTargetInteractionStep(step);
13101
13235
  const clickHint = isTargetInteraction ? step.clickHint ?? labels.clickHint : null;
@@ -13186,58 +13320,101 @@ function OnboardingOverlay({
13186
13320
  }
13187
13321
  }
13188
13322
  ) : null,
13189
- cutoutRect ? /* @__PURE__ */ jsx(
13190
- "div",
13191
- {
13192
- "aria-hidden": "true",
13193
- "data-testid": "onboarding-cutout-outline",
13194
- className: cn(
13195
- "fixed",
13196
- isTargetInteraction ? "issue-sticker-onboarding-highlight animate-pulse-ring border-2 border-brand" : "border border-white/90"
13197
- ),
13198
- style: {
13199
- left: cutoutRect.left,
13200
- top: cutoutRect.top,
13201
- width: cutoutRect.width,
13202
- height: cutoutRect.height,
13203
- borderRadius: cutoutRadius,
13204
- pointerEvents: "none"
13323
+ cutoutRect ? renderHighlight(
13324
+ /* @__PURE__ */ jsx(
13325
+ "div",
13326
+ {
13327
+ "aria-hidden": "true",
13328
+ "data-testid": "onboarding-cutout-outline",
13329
+ className: cn(
13330
+ "issue-sticker-onboarding-highlight fixed",
13331
+ isTargetInteraction ? "border-2 border-brand" : "border border-white/90"
13332
+ ),
13333
+ style: {
13334
+ left: cutoutRect.left,
13335
+ top: cutoutRect.top,
13336
+ width: cutoutRect.width,
13337
+ height: cutoutRect.height,
13338
+ borderRadius: cutoutRadius,
13339
+ pointerEvents: "none",
13340
+ zIndex: elevateHighlight ? OVERLAY_LAYER_INDEX.runtimeHost : void 0
13341
+ }
13342
+ },
13343
+ step.id
13344
+ )
13345
+ ) : null,
13346
+ cutoutRect && isTargetInteraction ? renderHighlight(
13347
+ /* 클릭하는 커서. 테두리만으로는 "봐야 할 곳"까지만 전달되고 "클릭하라"가
13348
+ * 전달되지 않는다. 손 모양 반복 동작이 그 자리를 맡는다. */
13349
+ /* @__PURE__ */ jsxs(
13350
+ "div",
13351
+ {
13352
+ "aria-hidden": "true",
13353
+ "data-testid": "onboarding-cutout-cursor",
13354
+ className: "issue-sticker-onboarding-tap fixed",
13355
+ style: {
13356
+ left: cutoutRect.right - TAP_INSET,
13357
+ top: cutoutRect.bottom - TAP_INSET,
13358
+ pointerEvents: "none",
13359
+ zIndex: elevateHighlight ? OVERLAY_LAYER_INDEX.runtimeHost : void 0
13360
+ },
13361
+ children: [
13362
+ /* @__PURE__ */ jsx(
13363
+ "span",
13364
+ {
13365
+ className: "issue-sticker-onboarding-tap-ripple absolute rounded-full bg-brand",
13366
+ style: { left: 0, top: 0, width: TAP_RIPPLE_SIZE, height: TAP_RIPPLE_SIZE }
13367
+ }
13368
+ ),
13369
+ /* @__PURE__ */ jsx(
13370
+ MousePointer2,
13371
+ {
13372
+ className: "relative h-8 w-8",
13373
+ style: { fill: "#ffffff", stroke: "rgba(12, 18, 15, 0.7)" },
13374
+ strokeWidth: 1.4
13375
+ }
13376
+ )
13377
+ ]
13205
13378
  }
13206
- }
13379
+ )
13207
13380
  ) : null,
13208
13381
  isSkipConfirmOpen ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "fixed inset-0 pointer-events-auto" }) : null,
13209
- /* @__PURE__ */ jsx(
13210
- "div",
13211
- {
13212
- role: "dialog",
13213
- "aria-modal": "false",
13214
- ...{ [ONBOARDING_POPOVER_DATA_ATTR]: "floating" },
13215
- className: "issue-sticker-onboarding-popover fixed w-[21rem] max-w-[calc(100vw-24px)] rounded-xl border border-line-subtle bg-surface-0 p-[18px] text-text-primary shadow-lg pointer-events-auto",
13216
- style: {
13217
- left: shouldCenterPopover ? "50%" : popoverLeft,
13218
- top: shouldCenterPopover ? "50%" : popoverTop,
13219
- opacity: isPopoverVisible ? 1 : 0,
13220
- transform: shouldCenterPopover ? "translate(-50%, -50%)" : void 0,
13221
- zIndex: OVERLAY_LAYER_INDEX.tourPopover
13222
- },
13223
- children: /* @__PURE__ */ jsx(
13224
- OnboardingPopoverContent,
13225
- {
13226
- step,
13227
- clickHint,
13228
- labels,
13229
- canGoPrevious,
13230
- isLast,
13231
- isVisible: isPopoverVisible,
13232
- isSkipConfirmOpen,
13233
- onRequestSkip: () => setIsSkipConfirmOpen(true),
13234
- onCancelSkip: () => setIsSkipConfirmOpen(false),
13235
- onConfirmSkip: onSkip,
13236
- onNext,
13237
- onPrevious
13238
- }
13239
- )
13240
- }
13382
+ renderHighlight(
13383
+ /* @__PURE__ */ jsx(
13384
+ "div",
13385
+ {
13386
+ ref: popoverRef,
13387
+ role: "dialog",
13388
+ "aria-modal": "false",
13389
+ ...{ [ONBOARDING_POPOVER_DATA_ATTR]: "floating" },
13390
+ className: "issue-sticker-onboarding-popover fixed w-[22.5rem] max-w-[calc(100vw-24px)] break-keep [overflow-wrap:anywhere] rounded-xl border border-line-subtle bg-surface-0 p-[18px] text-text-primary shadow-lg pointer-events-auto",
13391
+ style: {
13392
+ left: shouldCenterPopover ? "50%" : popoverLeft,
13393
+ top: shouldCenterPopover ? "50%" : popoverTop,
13394
+ opacity: isPopoverVisible ? 1 : 0,
13395
+ transform: shouldCenterPopover ? "translate(-50%, -50%)" : void 0,
13396
+ zIndex: elevateHighlight ? OVERLAY_LAYER_INDEX.runtimeHost : OVERLAY_LAYER_INDEX.tourPopover
13397
+ },
13398
+ children: /* @__PURE__ */ jsx(
13399
+ OnboardingPopoverContent,
13400
+ {
13401
+ allowSkip,
13402
+ step,
13403
+ clickHint,
13404
+ labels,
13405
+ canGoPrevious,
13406
+ isLast,
13407
+ isVisible: isPopoverVisible,
13408
+ isSkipConfirmOpen,
13409
+ onRequestSkip: () => setIsSkipConfirmOpen(true),
13410
+ onCancelSkip: () => setIsSkipConfirmOpen(false),
13411
+ onConfirmSkip: onSkip,
13412
+ onNext,
13413
+ onPrevious
13414
+ }
13415
+ )
13416
+ }
13417
+ )
13241
13418
  )
13242
13419
  ]
13243
13420
  }
@@ -13330,7 +13507,9 @@ function OnboardingProvider({
13330
13507
  className,
13331
13508
  labels: labelOverrides,
13332
13509
  storage: storageOverride,
13333
- suppressed = false
13510
+ suppressed = false,
13511
+ elevateHighlight = false,
13512
+ allowSkip = true
13334
13513
  }) {
13335
13514
  const labels = React.useMemo(() => ({ ...DEFAULT_LABELS, ...labelOverrides }), [labelOverrides]);
13336
13515
  const storage = React.useMemo(
@@ -13612,6 +13791,7 @@ function OnboardingProvider({
13612
13791
  const allTargetsResolved = activeTargetIds.length > 0 && resolvedTargetRects.every((rect) => rect !== null);
13613
13792
  const targetRect = isOpen && isActiveRoute && allTargetsResolved ? unionRect(resolvedTargetRects.filter((r2) => r2 !== null)) : null;
13614
13793
  const targetRadius = activeTargetIds.length === 1 ? getElementRadius(targetMapRef.current.get(activeTargetIds[0]) ?? null) : null;
13794
+ const avoidRects = ((activeStep == null ? void 0 : activeStep.avoidTargetIds) ?? []).map((id) => getElementRect(targetMapRef.current.get(id) ?? null)).filter((rect) => rect !== null);
13615
13795
  const shouldRenderOverlay = isOpen && activeStep && isActiveRoute && !suppressed && !activeStep.silent && (!stepHasTarget || targetRect);
13616
13796
  const isTargetReady = !stepHasTarget || targetRect !== null;
13617
13797
  React.useEffect(() => {
@@ -13663,6 +13843,9 @@ function OnboardingProvider({
13663
13843
  step: activeStep,
13664
13844
  targetRect,
13665
13845
  targetRadius,
13846
+ avoidRects,
13847
+ elevateHighlight,
13848
+ allowSkip,
13666
13849
  className,
13667
13850
  labels,
13668
13851
  isPopoverVisible,
@@ -13890,66 +14073,59 @@ function buildExternalUserPayload(user) {
13890
14073
  };
13891
14074
  }
13892
14075
  export {
13893
- X as $,
13894
- trimLogBackupPayload as A,
13895
- createSafeLogBackup as B,
14076
+ DatePicker as $,
14077
+ createSafeLogBackup as A,
14078
+ getLogBackupSessionId as B,
13896
14079
  ConsoleCollector as C,
13897
- getLogBackupSessionId as D,
13898
- createIndexedDbLogBackup as E,
13899
- PopoverTrigger as F,
13900
- Input as G,
13901
- PopoverContent as H,
14080
+ createIndexedDbLogBackup as D,
14081
+ PopoverTrigger as E,
14082
+ Input as F,
14083
+ PopoverContent as G,
14084
+ Select$1 as H,
13902
14085
  ISSUE_STICKER_INTERNAL_LOG_PREFIX as I,
13903
- Select$1 as J,
13904
- SelectTrigger as K,
14086
+ SelectTrigger as J,
14087
+ SelectValue as K,
13905
14088
  Label as L,
13906
- SelectValue as M,
14089
+ SelectContent as M,
13907
14090
  NetworkCollector as N,
13908
- SelectContent as O,
14091
+ SelectItem as O,
13909
14092
  Popover as P,
13910
- SelectItem as Q,
13911
- getJiraMetadata as R,
14093
+ getJiraMetadata as Q,
14094
+ buildJiraIssueFields as R,
13912
14095
  ScreenRecordingController as S,
13913
- buildJiraIssueFields as T,
13914
- Button$1 as U,
13915
- Root2 as V,
13916
- Trigger as W,
13917
- ChevronDown as X,
13918
- Portal as Y,
13919
- Content2 as Z,
13920
- Check as _,
14096
+ Button$1 as T,
14097
+ Root2 as U,
14098
+ Trigger as V,
14099
+ ChevronDown as W,
14100
+ Portal as X,
14101
+ Content2 as Y,
14102
+ Check as Z,
14103
+ X as _,
13921
14104
  uploadSignedMediaWithApi as a,
13922
- DatePicker as a0,
13923
- getNotionPageMetadata as a1,
13924
- buildNotionFieldViews as a2,
13925
- DirectUploadError as a3,
13926
- toast as a4,
13927
- getSnapshot as a5,
13928
- subscribe as a6,
13929
- getRegisteredUploadId as a7,
13930
- ChevronRight as a8,
13931
- ReactRemoveScroll as a9,
13932
- hideOthers as aa,
13933
- useFocusGuards as ab,
13934
- FocusScope as ac,
13935
- getExposedJiraFields as ad,
13936
- isJiraFieldExposed as ae,
13937
- getPrimaryJiraMapping as af,
13938
- createCollection as ag,
13939
- useDirection as ah,
13940
- getPrimaryNotionMapping as ai,
13941
- buttonVariants as aj,
13942
- findElementBySelector as ak,
13943
- findElementByXPath as al,
13944
- OnboardingProvider as am,
13945
- useOnboardingTarget as an,
13946
- useOnboardingFlow as ao,
13947
- ISSUE_STICKER_HOST_ELEMENT_ID as ap,
13948
- getIssueStickerRuntimeOwner as aq,
13949
- ISSUE_STICKER_RUNTIME_OWNER as ar,
13950
- createRuntimeHostElement as as,
13951
- attachHostTypingGuard as at,
13952
- ISSUE_STICKER_EXTENSION_RUNTIME_READY_EVENT as au,
14105
+ getNotionPageMetadata as a0,
14106
+ buildNotionFieldViews as a1,
14107
+ DirectUploadError as a2,
14108
+ toast as a3,
14109
+ getSnapshot as a4,
14110
+ subscribe as a5,
14111
+ getRegisteredUploadId as a6,
14112
+ ChevronRight as a7,
14113
+ ReactRemoveScroll as a8,
14114
+ hideOthers as a9,
14115
+ useFocusGuards as aa,
14116
+ FocusScope as ab,
14117
+ getExposedJiraFields as ac,
14118
+ isJiraFieldExposed as ad,
14119
+ getPrimaryJiraMapping as ae,
14120
+ createCollection as af,
14121
+ useDirection as ag,
14122
+ getPrimaryNotionMapping as ah,
14123
+ buttonVariants as ai,
14124
+ findElementBySelector as aj,
14125
+ findElementByXPath as ak,
14126
+ OnboardingProvider as al,
14127
+ useOnboardingTarget as am,
14128
+ useOnboardingFlow as an,
13953
14129
  normalizeJiraFieldId as b,
13954
14130
  captureWithModernScreenshot as c,
13955
14131
  directUploadToSignedUrl as d,
@@ -13974,5 +14150,5 @@ export {
13974
14150
  getCurrentPageUrl as w,
13975
14151
  SessionReplayCaptureController as x,
13976
14152
  preloadModernScreenshot as y,
13977
- isIssueStickerExtensionRuntimeActive as z
14153
+ trimLogBackupPayload as z
13978
14154
  };