issue-sticker 0.6.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() {
@@ -3949,15 +3874,59 @@ class ConsoleCollector {
3949
3874
  this.logs = [];
3950
3875
  this.originals = {};
3951
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 = {};
3952
3891
  this.onChange = (options == null ? void 0 : options.onChange) ?? null;
3953
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
+ }
3954
3910
  start() {
3955
3911
  if (this.running) return;
3956
3912
  this.running = true;
3913
+ if (typeof window !== "undefined") {
3914
+ window.addEventListener("error", this.handleWindowError);
3915
+ window.addEventListener("unhandledrejection", this.handleUnhandledRejection);
3916
+ }
3957
3917
  for (const method of CONSOLE_METHODS) {
3958
- this.originals[method] = console[method];
3959
- console[method] = (...args) => {
3960
- 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
+ }
3961
3930
  this.addLog({
3962
3931
  type: methodToLogType(method),
3963
3932
  message: serializeArgs(args),
@@ -3965,19 +3934,26 @@ class ConsoleCollector {
3965
3934
  url: getCurrentPageUrl() || void 0,
3966
3935
  stackTrace: redactSensitiveText(captureConsoleStackTrace(args))
3967
3936
  });
3968
- (_a = this.originals[method]) == null ? void 0 : _a.apply(console, args);
3937
+ original.apply(console, args);
3969
3938
  };
3939
+ this.wrappers[method] = wrapper;
3940
+ console[method] = wrapper;
3970
3941
  }
3971
3942
  }
3972
3943
  stop() {
3973
3944
  if (!this.running) return;
3974
3945
  this.running = false;
3946
+ if (typeof window !== "undefined") {
3947
+ window.removeEventListener("error", this.handleWindowError);
3948
+ window.removeEventListener("unhandledrejection", this.handleUnhandledRejection);
3949
+ }
3975
3950
  for (const method of CONSOLE_METHODS) {
3976
- if (this.originals[method]) {
3951
+ if (this.originals[method] && console[method] === this.wrappers[method]) {
3977
3952
  console[method] = this.originals[method];
3978
3953
  }
3979
3954
  }
3980
3955
  this.originals = {};
3956
+ this.wrappers = {};
3981
3957
  }
3982
3958
  /** 리로드 이전 로그를 버퍼 앞에 되돌린다. */
3983
3959
  restore(logs) {
@@ -3999,6 +3975,7 @@ class ConsoleCollector {
3999
3975
  }
4000
3976
  clear() {
4001
3977
  this.logs = [];
3978
+ this.seenErrors = /* @__PURE__ */ new WeakSet();
4002
3979
  }
4003
3980
  addLog(log) {
4004
3981
  var _a;
@@ -12810,7 +12787,7 @@ const DEFAULT_LABELS = {
12810
12787
  close: "가이드 닫기",
12811
12788
  skip: "건너뛰기",
12812
12789
  skipConfirmTitle: "가이드를 건너뛸까요?",
12813
- // 표면마다 진입점이 다르다(admin=헤더 아이콘, extension=더보기 메뉴, sdk=FAB 메뉴).
12790
+ // 채널마다 진입점이 다르다(admin=헤더 아이콘, extension=더보기 메뉴, sdk=FAB 메뉴).
12814
12791
  // 위치를 특정하면 어느 한쪽에서는 틀린 안내가 되므로 라벨만 가리킨다.
12815
12792
  skipConfirmBody: "'가이드 다시 보기'를 클릭하면 언제든 다시 열 수 있습니다.",
12816
12793
  skipConfirmCancel: "계속 보기",
@@ -12822,8 +12799,9 @@ const DEFAULT_LABELS = {
12822
12799
  };
12823
12800
  const MASK_PADDING = 6;
12824
12801
  const CUTOUT_RADIUS = 12;
12825
- const POPOVER_WIDTH = 336;
12802
+ const POPOVER_WIDTH = 360;
12826
12803
  const POPOVER_FALLBACK_HEIGHT = 190;
12804
+ const DEFAULT_POPOVER_SIZE = { width: POPOVER_WIDTH, height: POPOVER_FALLBACK_HEIGHT };
12827
12805
  const POPOVER_GAP = 12;
12828
12806
  function clamp(value, min2, max2) {
12829
12807
  return Math.min(Math.max(value, min2), max2);
@@ -12889,12 +12867,14 @@ function areRectsEqual(previous, next) {
12889
12867
  if (!previous || !next) return previous === next;
12890
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;
12891
12869
  }
12892
- function getPopoverCoordinates(position, targetRect) {
12870
+ function getPopoverCoordinates(position, targetRect, size = {
12871
+ width: POPOVER_WIDTH,
12872
+ height: POPOVER_FALLBACK_HEIGHT
12873
+ }) {
12893
12874
  if (Array.isArray(position)) return position;
12894
12875
  const viewportWidth = window.innerWidth;
12895
12876
  const viewportHeight = window.innerHeight;
12896
- const width = POPOVER_WIDTH;
12897
- const height = POPOVER_FALLBACK_HEIGHT;
12877
+ const { width, height } = size;
12898
12878
  const maxX = Math.max(POPOVER_GAP, viewportWidth - width - POPOVER_GAP);
12899
12879
  const maxY = Math.max(POPOVER_GAP, viewportHeight - height - POPOVER_GAP);
12900
12880
  const xOnTarget = targetRect.left + targetRect.width / 2 - width / 2;
@@ -12928,6 +12908,49 @@ function getPopoverCoordinates(position, targetRect) {
12928
12908
  ];
12929
12909
  }
12930
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
+ }
12931
12954
  function getViewportRect() {
12932
12955
  return {
12933
12956
  top: 0,
@@ -12938,6 +12961,9 @@ function getViewportRect() {
12938
12961
  height: window.innerHeight
12939
12962
  };
12940
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";
12941
12967
  function shouldRenderNextButton(step) {
12942
12968
  if (step.advanceOnTargetClick) return false;
12943
12969
  if (step.advanceOn && !step.allowManualAdvance) return false;
@@ -12955,18 +12981,67 @@ function OnboardingStyleTag() {
12955
12981
  transition: opacity ${POPOVER_FADE_MS}ms ease-out;
12956
12982
  }
12957
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
+
12958
13030
  @media (prefers-reduced-motion: reduce) {
12959
13031
  .issue-sticker-onboarding-popover {
12960
13032
  transition-duration: 1ms !important;
12961
13033
  }
12962
13034
 
12963
- .issue-sticker-onboarding-highlight {
13035
+ .issue-sticker-onboarding-highlight,
13036
+ .issue-sticker-onboarding-tap,
13037
+ .issue-sticker-onboarding-tap-ripple {
12964
13038
  animation: none !important;
12965
13039
  }
12966
13040
  }
12967
13041
  ` });
12968
13042
  }
12969
13043
  function OnboardingPopoverContent({
13044
+ allowSkip,
12970
13045
  step,
12971
13046
  clickHint,
12972
13047
  labels,
@@ -13031,26 +13106,53 @@ function OnboardingPopoverContent({
13031
13106
  }
13032
13107
  );
13033
13108
  }
13109
+ const hasNextButton = shouldRenderNextButton(step);
13034
13110
  return /* @__PURE__ */ jsxs("div", { children: [
13035
13111
  /* @__PURE__ */ jsx("h2", { className: "text-sm font-semibold leading-5 text-text-primary", children: step.title }),
13036
- /* @__PURE__ */ jsx("p", { className: "mt-2 whitespace-pre-line text-sm leading-5 text-text-secondary", children: step.body }),
13037
- clickHint ? /* @__PURE__ */ jsxs("p", { className: "mt-2 flex items-center gap-1.5 text-xs font-semibold leading-5 text-brand", children: [
13038
- /* @__PURE__ */ jsx(MousePointerClick, { "aria-hidden": "true", className: "h-3.5 w-3.5 shrink-0" }),
13039
- clickHint
13040
- ] }) : null,
13041
- /* @__PURE__ */ jsxs("div", { className: "mt-4 flex items-center justify-between gap-2", children: [
13042
- /* @__PURE__ */ jsx(
13043
- "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",
13044
13119
  {
13045
- type: "button",
13046
- onClick: onRequestSkip,
13047
- disabled: !isVisible,
13048
- 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",
13049
- 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
+ ]
13050
13128
  }
13051
- ),
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
+ ] }),
13052
13154
  /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
13053
- canGoPrevious ? /* @__PURE__ */ jsx(
13155
+ canGoPrevious && hasNextButton ? /* @__PURE__ */ jsx(
13054
13156
  "button",
13055
13157
  {
13056
13158
  type: "button",
@@ -13060,7 +13162,7 @@ function OnboardingPopoverContent({
13060
13162
  children: labels.previous
13061
13163
  }
13062
13164
  ) : null,
13063
- shouldRenderNextButton(step) ? /* @__PURE__ */ jsx(
13165
+ hasNextButton ? /* @__PURE__ */ jsx(
13064
13166
  "button",
13065
13167
  {
13066
13168
  type: "button",
@@ -13078,6 +13180,9 @@ function OnboardingOverlay({
13078
13180
  step,
13079
13181
  targetRect,
13080
13182
  targetRadius,
13183
+ avoidRects,
13184
+ elevateHighlight,
13185
+ allowSkip = true,
13081
13186
  className,
13082
13187
  labels,
13083
13188
  isPopoverVisible,
@@ -13088,6 +13193,7 @@ function OnboardingOverlay({
13088
13193
  onPrevious
13089
13194
  }) {
13090
13195
  const maskId = React.useId().replace(/:/g, "");
13196
+ const renderHighlight = (node) => elevateHighlight && typeof document !== "undefined" ? ReactDOM.createPortal(node, document.body) : node;
13091
13197
  const [isSkipConfirmOpen, setIsSkipConfirmOpen] = React.useState(false);
13092
13198
  const cutoutRect = targetRect ? padRect(targetRect, MASK_PADDING) : null;
13093
13199
  const cutoutRadius = cutoutRect ? Math.min(
@@ -13095,12 +13201,35 @@ function OnboardingOverlay({
13095
13201
  cutoutRect.width / 2,
13096
13202
  cutoutRect.height / 2
13097
13203
  ) : 0;
13098
- 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(
13099
13225
  step.position ?? (cutoutRect ? "bottom" : "center"),
13100
- cutoutRect ?? getViewportRect()
13226
+ cutoutRect ?? getViewportRect(),
13227
+ effectiveSize,
13228
+ cutoutRect ? avoidRects ?? [] : []
13101
13229
  );
13230
+ const { left: popoverLeft, top: popoverTop } = placement;
13102
13231
  const shouldCenterPopover = !cutoutRect && (step.position ?? "center") === "center";
13103
- 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)";
13104
13233
  const isPassthrough = !!step.advanceOn;
13105
13234
  const isTargetInteraction = isTargetInteractionStep(step);
13106
13235
  const clickHint = isTargetInteraction ? step.clickHint ?? labels.clickHint : null;
@@ -13191,58 +13320,101 @@ function OnboardingOverlay({
13191
13320
  }
13192
13321
  }
13193
13322
  ) : null,
13194
- cutoutRect ? /* @__PURE__ */ jsx(
13195
- "div",
13196
- {
13197
- "aria-hidden": "true",
13198
- "data-testid": "onboarding-cutout-outline",
13199
- className: cn(
13200
- "fixed",
13201
- isTargetInteraction ? "issue-sticker-onboarding-highlight animate-pulse-ring border-2 border-brand" : "border border-white/90"
13202
- ),
13203
- style: {
13204
- left: cutoutRect.left,
13205
- top: cutoutRect.top,
13206
- width: cutoutRect.width,
13207
- height: cutoutRect.height,
13208
- borderRadius: cutoutRadius,
13209
- 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
+ ]
13210
13378
  }
13211
- }
13379
+ )
13212
13380
  ) : null,
13213
13381
  isSkipConfirmOpen ? /* @__PURE__ */ jsx("div", { "aria-hidden": "true", className: "fixed inset-0 pointer-events-auto" }) : null,
13214
- /* @__PURE__ */ jsx(
13215
- "div",
13216
- {
13217
- role: "dialog",
13218
- "aria-modal": "false",
13219
- ...{ [ONBOARDING_POPOVER_DATA_ATTR]: "floating" },
13220
- 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",
13221
- style: {
13222
- left: shouldCenterPopover ? "50%" : popoverLeft,
13223
- top: shouldCenterPopover ? "50%" : popoverTop,
13224
- opacity: isPopoverVisible ? 1 : 0,
13225
- transform: shouldCenterPopover ? "translate(-50%, -50%)" : void 0,
13226
- zIndex: OVERLAY_LAYER_INDEX.tourPopover
13227
- },
13228
- children: /* @__PURE__ */ jsx(
13229
- OnboardingPopoverContent,
13230
- {
13231
- step,
13232
- clickHint,
13233
- labels,
13234
- canGoPrevious,
13235
- isLast,
13236
- isVisible: isPopoverVisible,
13237
- isSkipConfirmOpen,
13238
- onRequestSkip: () => setIsSkipConfirmOpen(true),
13239
- onCancelSkip: () => setIsSkipConfirmOpen(false),
13240
- onConfirmSkip: onSkip,
13241
- onNext,
13242
- onPrevious
13243
- }
13244
- )
13245
- }
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
+ )
13246
13418
  )
13247
13419
  ]
13248
13420
  }
@@ -13335,7 +13507,9 @@ function OnboardingProvider({
13335
13507
  className,
13336
13508
  labels: labelOverrides,
13337
13509
  storage: storageOverride,
13338
- suppressed = false
13510
+ suppressed = false,
13511
+ elevateHighlight = false,
13512
+ allowSkip = true
13339
13513
  }) {
13340
13514
  const labels = React.useMemo(() => ({ ...DEFAULT_LABELS, ...labelOverrides }), [labelOverrides]);
13341
13515
  const storage = React.useMemo(
@@ -13617,6 +13791,7 @@ function OnboardingProvider({
13617
13791
  const allTargetsResolved = activeTargetIds.length > 0 && resolvedTargetRects.every((rect) => rect !== null);
13618
13792
  const targetRect = isOpen && isActiveRoute && allTargetsResolved ? unionRect(resolvedTargetRects.filter((r2) => r2 !== null)) : null;
13619
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);
13620
13795
  const shouldRenderOverlay = isOpen && activeStep && isActiveRoute && !suppressed && !activeStep.silent && (!stepHasTarget || targetRect);
13621
13796
  const isTargetReady = !stepHasTarget || targetRect !== null;
13622
13797
  React.useEffect(() => {
@@ -13668,6 +13843,9 @@ function OnboardingProvider({
13668
13843
  step: activeStep,
13669
13844
  targetRect,
13670
13845
  targetRadius,
13846
+ avoidRects,
13847
+ elevateHighlight,
13848
+ allowSkip,
13671
13849
  className,
13672
13850
  labels,
13673
13851
  isPopoverVisible,
@@ -13895,66 +14073,59 @@ function buildExternalUserPayload(user) {
13895
14073
  };
13896
14074
  }
13897
14075
  export {
13898
- X as $,
13899
- trimLogBackupPayload as A,
13900
- createSafeLogBackup as B,
14076
+ DatePicker as $,
14077
+ createSafeLogBackup as A,
14078
+ getLogBackupSessionId as B,
13901
14079
  ConsoleCollector as C,
13902
- getLogBackupSessionId as D,
13903
- createIndexedDbLogBackup as E,
13904
- PopoverTrigger as F,
13905
- Input as G,
13906
- PopoverContent as H,
14080
+ createIndexedDbLogBackup as D,
14081
+ PopoverTrigger as E,
14082
+ Input as F,
14083
+ PopoverContent as G,
14084
+ Select$1 as H,
13907
14085
  ISSUE_STICKER_INTERNAL_LOG_PREFIX as I,
13908
- Select$1 as J,
13909
- SelectTrigger as K,
14086
+ SelectTrigger as J,
14087
+ SelectValue as K,
13910
14088
  Label as L,
13911
- SelectValue as M,
14089
+ SelectContent as M,
13912
14090
  NetworkCollector as N,
13913
- SelectContent as O,
14091
+ SelectItem as O,
13914
14092
  Popover as P,
13915
- SelectItem as Q,
13916
- getJiraMetadata as R,
14093
+ getJiraMetadata as Q,
14094
+ buildJiraIssueFields as R,
13917
14095
  ScreenRecordingController as S,
13918
- buildJiraIssueFields as T,
13919
- Button$1 as U,
13920
- Root2 as V,
13921
- Trigger as W,
13922
- ChevronDown as X,
13923
- Portal as Y,
13924
- Content2 as Z,
13925
- 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 _,
13926
14104
  uploadSignedMediaWithApi as a,
13927
- DatePicker as a0,
13928
- getNotionPageMetadata as a1,
13929
- buildNotionFieldViews as a2,
13930
- DirectUploadError as a3,
13931
- toast as a4,
13932
- getSnapshot as a5,
13933
- subscribe as a6,
13934
- getRegisteredUploadId as a7,
13935
- ChevronRight as a8,
13936
- ReactRemoveScroll as a9,
13937
- hideOthers as aa,
13938
- useFocusGuards as ab,
13939
- FocusScope as ac,
13940
- getExposedJiraFields as ad,
13941
- isJiraFieldExposed as ae,
13942
- getPrimaryJiraMapping as af,
13943
- createCollection as ag,
13944
- useDirection as ah,
13945
- getPrimaryNotionMapping as ai,
13946
- buttonVariants as aj,
13947
- findElementBySelector as ak,
13948
- findElementByXPath as al,
13949
- OnboardingProvider as am,
13950
- useOnboardingTarget as an,
13951
- useOnboardingFlow as ao,
13952
- ISSUE_STICKER_HOST_ELEMENT_ID as ap,
13953
- getIssueStickerRuntimeOwner as aq,
13954
- ISSUE_STICKER_RUNTIME_OWNER as ar,
13955
- createRuntimeHostElement as as,
13956
- attachHostTypingGuard as at,
13957
- 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,
13958
14129
  normalizeJiraFieldId as b,
13959
14130
  captureWithModernScreenshot as c,
13960
14131
  directUploadToSignedUrl as d,
@@ -13979,5 +14150,5 @@ export {
13979
14150
  getCurrentPageUrl as w,
13980
14151
  SessionReplayCaptureController as x,
13981
14152
  preloadModernScreenshot as y,
13982
- isIssueStickerExtensionRuntimeActive as z
14153
+ trimLogBackupPayload as z
13983
14154
  };