@clue-ai/browser-sdk 0.0.1
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.
- package/README.md +100 -0
- package/dist/authoring/overlay.d.ts +12 -0
- package/dist/authoring/overlay.js +468 -0
- package/dist/authoring/recording.d.ts +125 -0
- package/dist/authoring/recording.js +481 -0
- package/dist/authoring/service-logo.d.ts +1 -0
- package/dist/authoring/service-logo.generated.d.ts +1 -0
- package/dist/authoring/service-logo.generated.js +3 -0
- package/dist/authoring/service-logo.js +1 -0
- package/dist/authoring/session.d.ts +23 -0
- package/dist/authoring/session.js +127 -0
- package/dist/authoring/surface.d.ts +11 -0
- package/dist/authoring/surface.js +63 -0
- package/dist/authoring/toolbar-constants.d.ts +23 -0
- package/dist/authoring/toolbar-constants.js +42 -0
- package/dist/authoring/toolbar-drag.d.ts +29 -0
- package/dist/authoring/toolbar-drag.js +270 -0
- package/dist/authoring/toolbar-view.d.ts +21 -0
- package/dist/authoring/toolbar-view.js +2584 -0
- package/dist/capture/action.d.ts +2 -0
- package/dist/capture/action.js +62 -0
- package/dist/capture/dom.d.ts +23 -0
- package/dist/capture/dom.js +329 -0
- package/dist/capture/drag.d.ts +2 -0
- package/dist/capture/drag.js +75 -0
- package/dist/capture/error.d.ts +2 -0
- package/dist/capture/error.js +193 -0
- package/dist/capture/form.d.ts +2 -0
- package/dist/capture/form.js +137 -0
- package/dist/capture/frustration.d.ts +2 -0
- package/dist/capture/frustration.js +171 -0
- package/dist/capture/input.d.ts +2 -0
- package/dist/capture/input.js +109 -0
- package/dist/capture/location.d.ts +10 -0
- package/dist/capture/location.js +42 -0
- package/dist/capture/navigation.d.ts +2 -0
- package/dist/capture/navigation.js +100 -0
- package/dist/capture/network.d.ts +13 -0
- package/dist/capture/network.js +903 -0
- package/dist/capture/page.d.ts +2 -0
- package/dist/capture/page.js +78 -0
- package/dist/capture/performance.d.ts +2 -0
- package/dist/capture/performance.js +268 -0
- package/dist/context/account.d.ts +12 -0
- package/dist/context/account.js +129 -0
- package/dist/context/environment.d.ts +42 -0
- package/dist/context/environment.js +208 -0
- package/dist/context/identity.d.ts +14 -0
- package/dist/context/identity.js +123 -0
- package/dist/context/session.d.ts +28 -0
- package/dist/context/session.js +155 -0
- package/dist/context/tab.d.ts +22 -0
- package/dist/context/tab.js +142 -0
- package/dist/context/trace.d.ts +32 -0
- package/dist/context/trace.js +65 -0
- package/dist/core/config.d.ts +4 -0
- package/dist/core/config.js +199 -0
- package/dist/core/constants.d.ts +43 -0
- package/dist/core/constants.js +109 -0
- package/dist/core/contracts.d.ts +58 -0
- package/dist/core/contracts.js +53 -0
- package/dist/core/sdk.d.ts +2 -0
- package/dist/core/sdk.js +831 -0
- package/dist/core/types.d.ts +413 -0
- package/dist/core/types.js +1 -0
- package/dist/core/usage-governor.d.ts +7 -0
- package/dist/core/usage-governor.js +127 -0
- package/dist/index.d.ts +17 -0
- package/dist/index.js +36 -0
- package/dist/integrations/next-router.d.ts +16 -0
- package/dist/integrations/next-router.js +18 -0
- package/dist/integrations/react-router.d.ts +7 -0
- package/dist/integrations/react-router.js +37 -0
- package/dist/internal/metrics.d.ts +9 -0
- package/dist/internal/metrics.js +38 -0
- package/dist/normalize/builders.d.ts +15 -0
- package/dist/normalize/builders.js +786 -0
- package/dist/normalize/canonical.d.ts +13 -0
- package/dist/normalize/canonical.js +77 -0
- package/dist/normalize/event-id.d.ts +8 -0
- package/dist/normalize/event-id.js +39 -0
- package/dist/normalize/path-template.d.ts +1 -0
- package/dist/normalize/path-template.js +33 -0
- package/dist/privacy/local-minimization.d.ts +29 -0
- package/dist/privacy/local-minimization.js +88 -0
- package/dist/privacy/mask.d.ts +7 -0
- package/dist/privacy/mask.js +60 -0
- package/dist/privacy/parameter-snapshot.d.ts +14 -0
- package/dist/privacy/parameter-snapshot.js +206 -0
- package/dist/privacy/sanitize.d.ts +11 -0
- package/dist/privacy/sanitize.js +145 -0
- package/dist/privacy/schema-evidence.d.ts +20 -0
- package/dist/privacy/schema-evidence.js +238 -0
- package/dist/transport/batch.d.ts +37 -0
- package/dist/transport/batch.js +182 -0
- package/dist/transport/client.d.ts +61 -0
- package/dist/transport/client.js +267 -0
- package/dist/transport/queue.d.ts +22 -0
- package/dist/transport/queue.js +56 -0
- package/dist/transport/retry.d.ts +14 -0
- package/dist/transport/retry.js +46 -0
- package/package.json +38 -0
|
@@ -0,0 +1,2584 @@
|
|
|
1
|
+
import { ChevronDown, ChevronUp, PauseCircle } from "lucide";
|
|
2
|
+
import { SERVICE_LOGO_URL } from "./service-logo";
|
|
3
|
+
import { AUTHORING_ROOT_ATTRIBUTE, AUTHORING_ROOT_ATTRIBUTE_VALUE, markAuthoringRoot, } from "./surface";
|
|
4
|
+
import { AUTHORING_ACTIONS, TOOLBAR_ACTION_BUTTON_SIZE, TOOLBAR_BASE_BUTTON_EDGE_OFFSET, TOOLBAR_BASE_BUTTON_SIZE, TOOLBAR_COLLAPSED_HEIGHT, TOOLBAR_COLLAPSED_WIDTH, TOOLBAR_DOCK_GAP, TOOLBAR_INITIAL_TOP, TOOLBAR_PANEL_HEIGHT, TOOLBAR_PANEL_WIDTH, TOOLBAR_ROOT_ID, TOOLBAR_VIEWPORT_GUTTER, } from "./toolbar-constants";
|
|
5
|
+
import { applyToolbarPosition, clampToolbarPosition, getViewportSize, installToolbarDrag, measureToolbar, } from "./toolbar-drag";
|
|
6
|
+
const toolbarDisposers = new WeakMap();
|
|
7
|
+
const AUTHORING_GLASS_BACKGROUND = "rgba(255,255,255,0.16)";
|
|
8
|
+
const AUTHORING_GLASS_BACKGROUND_STRONG = "rgba(255,253,249,0.2)";
|
|
9
|
+
const AUTHORING_GLASS_BACKGROUND_SOFT = "rgba(255,255,255,0.12)";
|
|
10
|
+
const AUTHORING_GLASS_BORDER = "1px solid rgba(255,255,255,0.24)";
|
|
11
|
+
const AUTHORING_GLASS_BLUR = "saturate(145%) blur(8px)";
|
|
12
|
+
function applyGlassSurfaceStyles(element, variant = "default") {
|
|
13
|
+
element.style.background =
|
|
14
|
+
variant === "strong"
|
|
15
|
+
? AUTHORING_GLASS_BACKGROUND_STRONG
|
|
16
|
+
: variant === "soft"
|
|
17
|
+
? AUTHORING_GLASS_BACKGROUND_SOFT
|
|
18
|
+
: AUTHORING_GLASS_BACKGROUND;
|
|
19
|
+
element.style.border = AUTHORING_GLASS_BORDER;
|
|
20
|
+
element.style.backdropFilter = AUTHORING_GLASS_BLUR;
|
|
21
|
+
element.style.setProperty("-webkit-backdrop-filter", AUTHORING_GLASS_BLUR);
|
|
22
|
+
}
|
|
23
|
+
function createSvgIcon(markup, size) {
|
|
24
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
25
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
26
|
+
svg.setAttribute("width", String(size));
|
|
27
|
+
svg.setAttribute("height", String(size));
|
|
28
|
+
svg.setAttribute("aria-hidden", "true");
|
|
29
|
+
svg.style.display = "block";
|
|
30
|
+
svg.innerHTML = markup;
|
|
31
|
+
return svg;
|
|
32
|
+
}
|
|
33
|
+
function createLucideIcon(icon, size) {
|
|
34
|
+
const svg = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
35
|
+
svg.setAttribute("viewBox", "0 0 24 24");
|
|
36
|
+
svg.setAttribute("width", String(size));
|
|
37
|
+
svg.setAttribute("height", String(size));
|
|
38
|
+
svg.setAttribute("fill", "none");
|
|
39
|
+
svg.setAttribute("stroke", "currentColor");
|
|
40
|
+
svg.setAttribute("stroke-width", "1.9");
|
|
41
|
+
svg.setAttribute("stroke-linecap", "round");
|
|
42
|
+
svg.setAttribute("stroke-linejoin", "round");
|
|
43
|
+
svg.setAttribute("aria-hidden", "true");
|
|
44
|
+
svg.style.display = "block";
|
|
45
|
+
for (const [tagName, attrs] of icon) {
|
|
46
|
+
const node = document.createElementNS("http://www.w3.org/2000/svg", tagName);
|
|
47
|
+
for (const [name, value] of Object.entries(attrs)) {
|
|
48
|
+
if (value === undefined) {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
node.setAttribute(name, String(value));
|
|
52
|
+
}
|
|
53
|
+
svg.append(node);
|
|
54
|
+
}
|
|
55
|
+
return svg;
|
|
56
|
+
}
|
|
57
|
+
function createFallbackLogo(size) {
|
|
58
|
+
return createSvgIcon([
|
|
59
|
+
'<path d="M4.6 6.2c0-.66.54-1.2 1.2-1.2h11.7c.97 0 1.45 1.18.77 1.86l-4.58 4.58a1.2 1.2 0 0 1-1.7 0L7.06 6.9A1.2 1.2 0 0 0 4.6 6.2Z" fill="currentColor"/>',
|
|
60
|
+
'<path d="M19.4 17.8c0 .66-.54 1.2-1.2 1.2H6.5c-.97 0-1.45-1.18-.77-1.86l4.58-4.58a1.2 1.2 0 0 1 1.7 0l4.93 4.56a1.2 1.2 0 0 0 2.46.68Z" fill="currentColor"/>',
|
|
61
|
+
].join(""), size);
|
|
62
|
+
}
|
|
63
|
+
function createServiceLogo(size) {
|
|
64
|
+
const image = document.createElement("img");
|
|
65
|
+
image.src = SERVICE_LOGO_URL;
|
|
66
|
+
image.alt = "";
|
|
67
|
+
image.width = size;
|
|
68
|
+
image.height = size;
|
|
69
|
+
image.decoding = "async";
|
|
70
|
+
image.setAttribute("aria-hidden", "true");
|
|
71
|
+
image.style.display = "block";
|
|
72
|
+
image.style.width = `${size}px`;
|
|
73
|
+
image.style.height = `${size}px`;
|
|
74
|
+
image.style.objectFit = "contain";
|
|
75
|
+
image.style.objectPosition = "center";
|
|
76
|
+
image.style.flexShrink = "0";
|
|
77
|
+
image.addEventListener("error", () => {
|
|
78
|
+
image.replaceWith(createFallbackLogo(size));
|
|
79
|
+
}, { once: true });
|
|
80
|
+
return image;
|
|
81
|
+
}
|
|
82
|
+
function escapeAttributeValue(value) {
|
|
83
|
+
return value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
|
|
84
|
+
}
|
|
85
|
+
function escapeCssIdentifier(value) {
|
|
86
|
+
if (typeof CSS !== "undefined" && typeof CSS.escape === "function") {
|
|
87
|
+
return CSS.escape(value);
|
|
88
|
+
}
|
|
89
|
+
return value.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
|
|
90
|
+
}
|
|
91
|
+
function buildStructuralPosition(element) {
|
|
92
|
+
const parent = element.parentElement;
|
|
93
|
+
if (!parent) {
|
|
94
|
+
return "root";
|
|
95
|
+
}
|
|
96
|
+
const siblings = Array.from(parent.children);
|
|
97
|
+
const sameTagSiblings = siblings.filter((child) => child.tagName === element.tagName);
|
|
98
|
+
return `p${siblings.indexOf(element) + 1}-t${sameTagSiblings.indexOf(element) + 1}`;
|
|
99
|
+
}
|
|
100
|
+
function matchesStructuralStableKey(element, stableKey) {
|
|
101
|
+
const match = /^struct:([^:]+):([^:]+):([^:]+):(.+)$/.exec(stableKey);
|
|
102
|
+
if (!match) {
|
|
103
|
+
return false;
|
|
104
|
+
}
|
|
105
|
+
const [, tag, role, inputType, position] = match;
|
|
106
|
+
if (element.tagName.toLowerCase() !== tag.toLowerCase()) {
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
109
|
+
const elementRole = (element.getAttribute("role") ?? "none").toLowerCase();
|
|
110
|
+
if (elementRole !== role.toLowerCase()) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
const elementInputType = element instanceof HTMLInputElement
|
|
114
|
+
? element.type || "na"
|
|
115
|
+
: element instanceof HTMLTextAreaElement
|
|
116
|
+
? "textarea"
|
|
117
|
+
: element instanceof HTMLSelectElement
|
|
118
|
+
? "select"
|
|
119
|
+
: element instanceof HTMLElement && element.isContentEditable
|
|
120
|
+
? "contenteditable"
|
|
121
|
+
: "na";
|
|
122
|
+
if (elementInputType.toLowerCase() !== inputType.toLowerCase()) {
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
return buildStructuralPosition(element) === position;
|
|
126
|
+
}
|
|
127
|
+
function resolveUiTargetIdTargets(uiTargetId) {
|
|
128
|
+
if (!uiTargetId) {
|
|
129
|
+
return [];
|
|
130
|
+
}
|
|
131
|
+
const escapedId = escapeCssIdentifier(uiTargetId);
|
|
132
|
+
const escapedValue = escapeAttributeValue(uiTargetId);
|
|
133
|
+
return Array.from(new Set([
|
|
134
|
+
...Array.from(document.querySelectorAll(`#${escapedId}`)),
|
|
135
|
+
...Array.from(document.querySelectorAll(`[data-testid="${escapedValue}"]`)),
|
|
136
|
+
...Array.from(document.querySelectorAll(`[data-qa="${escapedValue}"]`)),
|
|
137
|
+
].filter((candidate) => candidate instanceof HTMLElement)));
|
|
138
|
+
}
|
|
139
|
+
function resolveStableKeyTargets(stableKey) {
|
|
140
|
+
if (!stableKey) {
|
|
141
|
+
return [];
|
|
142
|
+
}
|
|
143
|
+
const [prefix, ...rest] = stableKey.split(":");
|
|
144
|
+
const value = rest.join(":");
|
|
145
|
+
if (!value) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
const escapedValue = escapeAttributeValue(value);
|
|
149
|
+
switch (prefix) {
|
|
150
|
+
case "dtid":
|
|
151
|
+
return Array.from(document.querySelectorAll(`[data-testid="${escapedValue}"]`));
|
|
152
|
+
case "dqa":
|
|
153
|
+
return Array.from(document.querySelectorAll(`[data-qa="${escapedValue}"]`));
|
|
154
|
+
case "name":
|
|
155
|
+
return Array.from(document.querySelectorAll(`[name="${escapedValue}"]`));
|
|
156
|
+
case "aria":
|
|
157
|
+
return Array.from(document.querySelectorAll(`[aria-label="${escapedValue}"]`));
|
|
158
|
+
case "struct":
|
|
159
|
+
return Array.from(document.querySelectorAll(value.split(":")[0] || "*")).filter((element) => matchesStructuralStableKey(element, stableKey));
|
|
160
|
+
default:
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function getEntryAnchorAtom(entry) {
|
|
165
|
+
return (entry.actionUnit.atoms.find((atom) => atom.event_role === "anchor" && (atom.stable_key || atom.ui_target_id)) ??
|
|
166
|
+
entry.actionUnit.atoms.find((atom) => atom.stable_key || atom.ui_target_id) ??
|
|
167
|
+
null);
|
|
168
|
+
}
|
|
169
|
+
function readAtomRawProperties(record) {
|
|
170
|
+
return record && typeof record === "object" && !Array.isArray(record)
|
|
171
|
+
? record
|
|
172
|
+
: null;
|
|
173
|
+
}
|
|
174
|
+
function readStringAttribute(record, keys) {
|
|
175
|
+
for (const key of keys) {
|
|
176
|
+
const value = record?.[key];
|
|
177
|
+
if (typeof value === "string" && value.trim().length > 0) {
|
|
178
|
+
return value.trim();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
return null;
|
|
182
|
+
}
|
|
183
|
+
function normalizeComparableLabel(value) {
|
|
184
|
+
if (!value) {
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
const normalized = value
|
|
188
|
+
.trim()
|
|
189
|
+
.replace(/\s+/g, " ")
|
|
190
|
+
.replace(/[·•]/g, " ")
|
|
191
|
+
.toLowerCase();
|
|
192
|
+
return normalized.length > 0 ? normalized : null;
|
|
193
|
+
}
|
|
194
|
+
function readElementComparableLabel(element) {
|
|
195
|
+
return (normalizeComparableLabel(element.getAttribute("aria-label")) ??
|
|
196
|
+
normalizeComparableLabel(element instanceof HTMLInputElement ||
|
|
197
|
+
element instanceof HTMLTextAreaElement
|
|
198
|
+
? element.placeholder
|
|
199
|
+
: null) ??
|
|
200
|
+
normalizeComparableLabel(element.textContent));
|
|
201
|
+
}
|
|
202
|
+
function buildEntryTargetSignature(entry) {
|
|
203
|
+
const anchorAtom = getEntryAnchorAtom(entry);
|
|
204
|
+
const attributes = readAtomRawProperties(anchorAtom?.raw_properties_json);
|
|
205
|
+
const targetRecord = attributes?.target && typeof attributes.target === "object" && !Array.isArray(attributes.target)
|
|
206
|
+
? attributes.target
|
|
207
|
+
: null;
|
|
208
|
+
return {
|
|
209
|
+
uiTargetId: entry.actionUnit.primary_ui_target_id ?? anchorAtom?.ui_target_id ?? null,
|
|
210
|
+
stableKey: anchorAtom?.stable_key ?? null,
|
|
211
|
+
tag: readStringAttribute(attributes, ["element_tag", "tag"]) ??
|
|
212
|
+
readStringAttribute(targetRecord, ["element_tag", "tag"]),
|
|
213
|
+
role: readStringAttribute(attributes, ["element_role", "role"]) ??
|
|
214
|
+
readStringAttribute(targetRecord, ["element_role", "role"]),
|
|
215
|
+
label: normalizeComparableLabel(readStringAttribute(attributes, [
|
|
216
|
+
"target_label",
|
|
217
|
+
"ui_target_display_name",
|
|
218
|
+
"field_label",
|
|
219
|
+
"button_text",
|
|
220
|
+
"visible_text",
|
|
221
|
+
"aria_label",
|
|
222
|
+
"placeholder",
|
|
223
|
+
]) ??
|
|
224
|
+
readStringAttribute(targetRecord, [
|
|
225
|
+
"target_label",
|
|
226
|
+
"ui_target_display_name",
|
|
227
|
+
"field_label",
|
|
228
|
+
"button_text",
|
|
229
|
+
"label",
|
|
230
|
+
"text",
|
|
231
|
+
"aria_label",
|
|
232
|
+
"placeholder",
|
|
233
|
+
])),
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function queryLabelTargets(label) {
|
|
237
|
+
if (!label) {
|
|
238
|
+
return [];
|
|
239
|
+
}
|
|
240
|
+
return Array.from(document.querySelectorAll('button, a, input, textarea, select, [role="button"], [aria-label], [placeholder]')).filter((element) => readElementComparableLabel(element) === label);
|
|
241
|
+
}
|
|
242
|
+
function scoreTargetCandidate(candidate, signature) {
|
|
243
|
+
let score = 0;
|
|
244
|
+
if (signature.uiTargetId) {
|
|
245
|
+
if (candidate.id === signature.uiTargetId) {
|
|
246
|
+
score += 8;
|
|
247
|
+
}
|
|
248
|
+
if (candidate.getAttribute("data-testid") === signature.uiTargetId ||
|
|
249
|
+
candidate.getAttribute("data-qa") === signature.uiTargetId) {
|
|
250
|
+
score += 8;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
if (signature.stableKey?.startsWith("name:")) {
|
|
254
|
+
const nameValue = signature.stableKey.slice("name:".length);
|
|
255
|
+
if (candidate.getAttribute("name") === nameValue) {
|
|
256
|
+
score += 6;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
if (signature.stableKey?.startsWith("aria:")) {
|
|
260
|
+
const ariaValue = signature.stableKey.slice("aria:".length);
|
|
261
|
+
if (candidate.getAttribute("aria-label") === ariaValue) {
|
|
262
|
+
score += 6;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
if (signature.tag && candidate.tagName.toLowerCase() === signature.tag.toLowerCase()) {
|
|
266
|
+
score += 2;
|
|
267
|
+
}
|
|
268
|
+
if (signature.role && (candidate.getAttribute("role") ?? "").toLowerCase() === signature.role.toLowerCase()) {
|
|
269
|
+
score += 2;
|
|
270
|
+
}
|
|
271
|
+
if (signature.label && readElementComparableLabel(candidate) === signature.label) {
|
|
272
|
+
score += 4;
|
|
273
|
+
}
|
|
274
|
+
const rect = candidate.getBoundingClientRect();
|
|
275
|
+
if (rect.width > 0 && rect.height > 0) {
|
|
276
|
+
score += 1;
|
|
277
|
+
}
|
|
278
|
+
return score;
|
|
279
|
+
}
|
|
280
|
+
function normalizeComparablePath(value) {
|
|
281
|
+
if (!value) {
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
const trimmed = value.trim();
|
|
285
|
+
if (!trimmed) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
let pathname = trimmed;
|
|
289
|
+
try {
|
|
290
|
+
pathname = new URL(trimmed, window.location.origin).pathname;
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
pathname = trimmed.split("#")[0]?.split("?")[0] ?? trimmed;
|
|
294
|
+
}
|
|
295
|
+
if (!pathname.startsWith("/")) {
|
|
296
|
+
return null;
|
|
297
|
+
}
|
|
298
|
+
if (pathname.length > 1 && pathname.endsWith("/")) {
|
|
299
|
+
return pathname.slice(0, -1);
|
|
300
|
+
}
|
|
301
|
+
return pathname || "/";
|
|
302
|
+
}
|
|
303
|
+
function entryMatchesCurrentScreen(entry) {
|
|
304
|
+
const anchorAtom = getEntryAnchorAtom(entry);
|
|
305
|
+
const currentPath = normalizeComparablePath(window.location.pathname);
|
|
306
|
+
if (!currentPath) {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
const screenCandidates = [
|
|
310
|
+
anchorAtom?.screen_key ?? null,
|
|
311
|
+
anchorAtom?.url_canonical ?? null,
|
|
312
|
+
entry.actionUnit.route_from ?? null,
|
|
313
|
+
]
|
|
314
|
+
.map((candidate) => normalizeComparablePath(candidate))
|
|
315
|
+
.filter((candidate) => candidate !== null);
|
|
316
|
+
if (screenCandidates.length === 0) {
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
return screenCandidates.includes(currentPath);
|
|
320
|
+
}
|
|
321
|
+
function resolveEntryTarget(entry) {
|
|
322
|
+
if (!entryMatchesCurrentScreen(entry)) {
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
const anchorAtom = getEntryAnchorAtom(entry);
|
|
326
|
+
const signature = buildEntryTargetSignature(entry);
|
|
327
|
+
const primaryUiTargets = resolveUiTargetIdTargets(entry.actionUnit.primary_ui_target_id);
|
|
328
|
+
const anchorUiTargets = resolveUiTargetIdTargets(anchorAtom?.ui_target_id);
|
|
329
|
+
const stableKeyTargets = resolveStableKeyTargets(anchorAtom?.stable_key ?? null);
|
|
330
|
+
const labelTargets = queryLabelTargets(signature.label);
|
|
331
|
+
const uniqueUiTarget = primaryUiTargets.length === 1
|
|
332
|
+
? primaryUiTargets[0]
|
|
333
|
+
: anchorUiTargets.length === 1
|
|
334
|
+
? anchorUiTargets[0]
|
|
335
|
+
: null;
|
|
336
|
+
const uniqueStableTarget = stableKeyTargets.length === 1 &&
|
|
337
|
+
stableKeyTargets[0] instanceof HTMLElement
|
|
338
|
+
? stableKeyTargets[0]
|
|
339
|
+
: null;
|
|
340
|
+
if (uniqueUiTarget && uniqueStableTarget && uniqueUiTarget !== uniqueStableTarget) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
if (uniqueUiTarget) {
|
|
344
|
+
return uniqueUiTarget;
|
|
345
|
+
}
|
|
346
|
+
if (uniqueStableTarget) {
|
|
347
|
+
return uniqueStableTarget;
|
|
348
|
+
}
|
|
349
|
+
const candidatePool = Array.from(new Set([
|
|
350
|
+
...primaryUiTargets.filter((candidate) => candidate instanceof HTMLElement),
|
|
351
|
+
...anchorUiTargets.filter((candidate) => candidate instanceof HTMLElement),
|
|
352
|
+
...stableKeyTargets.filter((candidate) => candidate instanceof HTMLElement),
|
|
353
|
+
...labelTargets,
|
|
354
|
+
]));
|
|
355
|
+
if (candidatePool.length === 0) {
|
|
356
|
+
return null;
|
|
357
|
+
}
|
|
358
|
+
const scored = candidatePool
|
|
359
|
+
.map((candidate) => ({
|
|
360
|
+
candidate,
|
|
361
|
+
score: scoreTargetCandidate(candidate, signature),
|
|
362
|
+
}))
|
|
363
|
+
.filter((entryScore) => entryScore.score >= 6)
|
|
364
|
+
.sort((left, right) => right.score - left.score);
|
|
365
|
+
const best = scored[0] ?? null;
|
|
366
|
+
const second = scored[1] ?? null;
|
|
367
|
+
if (!best) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
if (second && second.score === best.score) {
|
|
371
|
+
return null;
|
|
372
|
+
}
|
|
373
|
+
return best.candidate;
|
|
374
|
+
}
|
|
375
|
+
function parseNumericZIndex(value) {
|
|
376
|
+
if (!value || value === "auto") {
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
const parsed = Number.parseInt(value, 10);
|
|
380
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
381
|
+
}
|
|
382
|
+
function readEffectiveZIndex(element) {
|
|
383
|
+
let maxValue = 0;
|
|
384
|
+
let current = element;
|
|
385
|
+
while (current) {
|
|
386
|
+
const computed = window.getComputedStyle(current);
|
|
387
|
+
const zIndex = parseNumericZIndex(computed.zIndex);
|
|
388
|
+
if (zIndex !== null) {
|
|
389
|
+
maxValue = Math.max(maxValue, zIndex);
|
|
390
|
+
}
|
|
391
|
+
current = current.parentElement;
|
|
392
|
+
}
|
|
393
|
+
return maxValue;
|
|
394
|
+
}
|
|
395
|
+
function readElementSurfaceTitle(root) {
|
|
396
|
+
const directTitle = root.getAttribute("aria-label")?.trim() ||
|
|
397
|
+
root.getAttribute("title")?.trim() ||
|
|
398
|
+
root.getAttribute("data-title")?.trim();
|
|
399
|
+
if (directTitle) {
|
|
400
|
+
return directTitle;
|
|
401
|
+
}
|
|
402
|
+
const labelledBy = root.getAttribute("aria-labelledby");
|
|
403
|
+
if (labelledBy) {
|
|
404
|
+
for (const id of labelledBy.split(/\s+/).filter(Boolean)) {
|
|
405
|
+
const labelledElement = document.getElementById(id);
|
|
406
|
+
if (labelledElement?.textContent?.trim()) {
|
|
407
|
+
return labelledElement.textContent.trim();
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
const titleCandidate = root.querySelector('h1, h2, h3, h4, h5, h6, [aria-current="page"], [aria-selected="true"], [data-title]');
|
|
412
|
+
return titleCandidate?.textContent?.trim() || null;
|
|
413
|
+
}
|
|
414
|
+
function inferSurfaceContextKind(root) {
|
|
415
|
+
const role = (root.getAttribute("role") ?? "").trim().toLowerCase();
|
|
416
|
+
const dataSide = root.getAttribute("data-side")?.trim().toLowerCase() ?? null;
|
|
417
|
+
const computed = window.getComputedStyle(root);
|
|
418
|
+
const isPortalSurface = root.parentElement === document.body || root.parentElement === document.documentElement;
|
|
419
|
+
const isFloating = (computed.position === "fixed" || computed.position === "absolute") &&
|
|
420
|
+
readEffectiveZIndex(root) > 0;
|
|
421
|
+
if (role === "dialog" || role === "alertdialog" || root.getAttribute("aria-modal") === "true") {
|
|
422
|
+
if (dataSide === "left" || dataSide === "right") {
|
|
423
|
+
return "drawer";
|
|
424
|
+
}
|
|
425
|
+
if (dataSide === "top" || dataSide === "bottom") {
|
|
426
|
+
return "sheet";
|
|
427
|
+
}
|
|
428
|
+
return "dialog";
|
|
429
|
+
}
|
|
430
|
+
if (role === "menu" || role === "listbox") {
|
|
431
|
+
return "dropdown";
|
|
432
|
+
}
|
|
433
|
+
if (root.getAttribute("popover") !== null || role === "tooltip") {
|
|
434
|
+
return "popover";
|
|
435
|
+
}
|
|
436
|
+
if (isFloating && isPortalSurface) {
|
|
437
|
+
const rect = root.getBoundingClientRect();
|
|
438
|
+
if (rect.width >= window.innerWidth * 0.9 && rect.height >= window.innerHeight * 0.9) {
|
|
439
|
+
return "overlay";
|
|
440
|
+
}
|
|
441
|
+
return "portal";
|
|
442
|
+
}
|
|
443
|
+
return "unknown";
|
|
444
|
+
}
|
|
445
|
+
const GENERATED_SURFACE_IDS = new WeakMap();
|
|
446
|
+
let generatedSurfaceId = 0;
|
|
447
|
+
function resolveGeneratedSurfaceId(root) {
|
|
448
|
+
const existing = GENERATED_SURFACE_IDS.get(root);
|
|
449
|
+
if (existing) {
|
|
450
|
+
return existing;
|
|
451
|
+
}
|
|
452
|
+
generatedSurfaceId += 1;
|
|
453
|
+
const next = `surface-${generatedSurfaceId}`;
|
|
454
|
+
GENERATED_SURFACE_IDS.set(root, next);
|
|
455
|
+
return next;
|
|
456
|
+
}
|
|
457
|
+
function isHighlightCandidateElement(element) {
|
|
458
|
+
if (element.closest(`[${AUTHORING_ROOT_ATTRIBUTE}="${AUTHORING_ROOT_ATTRIBUTE_VALUE}"]`)) {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
const computed = window.getComputedStyle(element);
|
|
462
|
+
if (computed.display === "none" || computed.visibility === "hidden") {
|
|
463
|
+
return false;
|
|
464
|
+
}
|
|
465
|
+
const rect = element.getBoundingClientRect();
|
|
466
|
+
return rect.width > 0 && rect.height > 0;
|
|
467
|
+
}
|
|
468
|
+
function resolveSurfaceContext(element) {
|
|
469
|
+
const surfaces = [];
|
|
470
|
+
let current = element;
|
|
471
|
+
while (current) {
|
|
472
|
+
const kind = inferSurfaceContextKind(current);
|
|
473
|
+
if (kind !== "unknown") {
|
|
474
|
+
surfaces.push(current);
|
|
475
|
+
}
|
|
476
|
+
current = current.parentElement;
|
|
477
|
+
}
|
|
478
|
+
const nearest = surfaces[0] ?? null;
|
|
479
|
+
if (!nearest) {
|
|
480
|
+
return {
|
|
481
|
+
kind: "page",
|
|
482
|
+
root: null,
|
|
483
|
+
rootId: `page:${normalizeComparablePath(window.location.pathname) ?? "/"}`,
|
|
484
|
+
depth: 0,
|
|
485
|
+
title: document.title || null,
|
|
486
|
+
isPortalSurface: false,
|
|
487
|
+
effectiveZIndex: 0,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
const kind = inferSurfaceContextKind(nearest);
|
|
491
|
+
const title = readElementSurfaceTitle(nearest);
|
|
492
|
+
const directId = nearest.id.trim() ||
|
|
493
|
+
nearest.getAttribute("data-surface-id")?.trim() ||
|
|
494
|
+
nearest.getAttribute("aria-label")?.trim() ||
|
|
495
|
+
title ||
|
|
496
|
+
resolveGeneratedSurfaceId(nearest);
|
|
497
|
+
return {
|
|
498
|
+
kind,
|
|
499
|
+
root: nearest,
|
|
500
|
+
rootId: `${kind}:${normalizeComparableLabel(directId) ?? directId}`,
|
|
501
|
+
depth: surfaces.length,
|
|
502
|
+
title,
|
|
503
|
+
isPortalSurface: nearest.parentElement === document.body ||
|
|
504
|
+
nearest.parentElement === document.documentElement,
|
|
505
|
+
effectiveZIndex: readEffectiveZIndex(nearest),
|
|
506
|
+
};
|
|
507
|
+
}
|
|
508
|
+
function readStableAttributesFromElement(element) {
|
|
509
|
+
const attributes = [];
|
|
510
|
+
const specs = [
|
|
511
|
+
["id", element.id || null],
|
|
512
|
+
["name", element.getAttribute("name")],
|
|
513
|
+
["aria-label", element.getAttribute("aria-label")],
|
|
514
|
+
["placeholder", element.getAttribute("placeholder")],
|
|
515
|
+
["data-testid", element.getAttribute("data-testid")],
|
|
516
|
+
["data-qa", element.getAttribute("data-qa")],
|
|
517
|
+
];
|
|
518
|
+
for (const [kind, value] of specs) {
|
|
519
|
+
if (value && value.trim()) {
|
|
520
|
+
attributes.push({ kind, value: value.trim() });
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
return attributes;
|
|
524
|
+
}
|
|
525
|
+
function buildContainerSignature(element) {
|
|
526
|
+
const tokens = [];
|
|
527
|
+
let current = element.parentElement;
|
|
528
|
+
while (current && tokens.length < 3) {
|
|
529
|
+
if (current.closest(`[${AUTHORING_ROOT_ATTRIBUTE}="${AUTHORING_ROOT_ATTRIBUTE_VALUE}"]`)) {
|
|
530
|
+
break;
|
|
531
|
+
}
|
|
532
|
+
const tag = current.tagName.toLowerCase();
|
|
533
|
+
const role = (current.getAttribute("role") ?? "").trim().toLowerCase();
|
|
534
|
+
tokens.push(role ? `${tag}:${role}` : tag);
|
|
535
|
+
current = current.parentElement;
|
|
536
|
+
}
|
|
537
|
+
return tokens.length > 0 ? tokens.join(">") : null;
|
|
538
|
+
}
|
|
539
|
+
function buildHighlightCandidate(element) {
|
|
540
|
+
const surface = resolveSurfaceContext(element);
|
|
541
|
+
return {
|
|
542
|
+
element,
|
|
543
|
+
surfaceContextKind: surface.kind,
|
|
544
|
+
surfaceRootId: surface.rootId,
|
|
545
|
+
surfaceDepth: surface.depth,
|
|
546
|
+
surfaceTitle: surface.title,
|
|
547
|
+
isPortalSurface: surface.isPortalSurface,
|
|
548
|
+
tagName: element.tagName.toLowerCase(),
|
|
549
|
+
role: (element.getAttribute("role") ?? "").trim().toLowerCase() || null,
|
|
550
|
+
uiTargetId: element.id ||
|
|
551
|
+
element.getAttribute("data-testid") ||
|
|
552
|
+
element.getAttribute("data-qa"),
|
|
553
|
+
stableKey: null,
|
|
554
|
+
stableAttributes: readStableAttributesFromElement(element),
|
|
555
|
+
normalizedText: readElementComparableLabel(element),
|
|
556
|
+
ariaLabel: normalizeComparableLabel(element.getAttribute("aria-label")),
|
|
557
|
+
placeholder: normalizeComparableLabel(element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement
|
|
558
|
+
? element.placeholder
|
|
559
|
+
: null),
|
|
560
|
+
containerSignature: buildContainerSignature(element),
|
|
561
|
+
effectiveZIndex: readEffectiveZIndex(element),
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
function collectHighlightCandidates() {
|
|
565
|
+
return Array.from(document.querySelectorAll('button, a, input, textarea, select, [role], [tabindex], [contenteditable="true"], summary'))
|
|
566
|
+
.filter((element) => isHighlightCandidateElement(element))
|
|
567
|
+
.map(buildHighlightCandidate);
|
|
568
|
+
}
|
|
569
|
+
function buildSurfaceAwareSignature(entry) {
|
|
570
|
+
const baseSignature = buildEntryTargetSignature(entry);
|
|
571
|
+
const anchorAtom = getEntryAnchorAtom(entry);
|
|
572
|
+
const attributes = readAtomRawProperties(anchorAtom?.raw_properties_json);
|
|
573
|
+
const stableAttributes = [];
|
|
574
|
+
const pushAttribute = (kind, value) => {
|
|
575
|
+
if (value && value.trim()) {
|
|
576
|
+
stableAttributes.push({ kind, value: value.trim() });
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
pushAttribute("name", anchorAtom?.stable_key?.startsWith("name:")
|
|
580
|
+
? anchorAtom.stable_key.slice("name:".length)
|
|
581
|
+
: null);
|
|
582
|
+
pushAttribute("aria-label", anchorAtom?.stable_key?.startsWith("aria:")
|
|
583
|
+
? anchorAtom.stable_key.slice("aria:".length)
|
|
584
|
+
: null);
|
|
585
|
+
pushAttribute("name", readStringAttribute(attributes, ["name"]));
|
|
586
|
+
pushAttribute("aria-label", readStringAttribute(attributes, ["aria_label", "accessibility_label"]));
|
|
587
|
+
pushAttribute("placeholder", readStringAttribute(attributes, ["placeholder", "placeholder_text"]));
|
|
588
|
+
pushAttribute("data-testid", readStringAttribute(attributes, ["data_testid", "test_id"]));
|
|
589
|
+
pushAttribute("data-qa", readStringAttribute(attributes, ["data_qa", "qa_key"]));
|
|
590
|
+
return {
|
|
591
|
+
...baseSignature,
|
|
592
|
+
stableAttributes,
|
|
593
|
+
containerSignature: readStringAttribute(attributes, ["container_signature"]) ?? null,
|
|
594
|
+
ariaLabel: normalizeComparableLabel(readStringAttribute(attributes, ["aria_label", "accessibility_label"])),
|
|
595
|
+
placeholder: normalizeComparableLabel(readStringAttribute(attributes, ["placeholder", "placeholder_text"])),
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
function matchStableAttribute(candidate, attribute) {
|
|
599
|
+
return candidate.stableAttributes.some((candidateAttribute) => candidateAttribute.kind === attribute.kind &&
|
|
600
|
+
normalizeComparableLabel(candidateAttribute.value) ===
|
|
601
|
+
normalizeComparableLabel(attribute.value));
|
|
602
|
+
}
|
|
603
|
+
function deriveExpectedSurfaceContext(candidates) {
|
|
604
|
+
const first = candidates[0] ?? null;
|
|
605
|
+
if (!first) {
|
|
606
|
+
return null;
|
|
607
|
+
}
|
|
608
|
+
if (candidates.every((candidate) => candidate.surfaceRootId === first.surfaceRootId)) {
|
|
609
|
+
return {
|
|
610
|
+
rootId: first.surfaceRootId,
|
|
611
|
+
kind: first.surfaceContextKind,
|
|
612
|
+
title: first.surfaceTitle,
|
|
613
|
+
effectiveZIndex: first.effectiveZIndex,
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
if (candidates.every((candidate) => candidate.surfaceContextKind === first.surfaceContextKind &&
|
|
617
|
+
candidate.surfaceTitle === first.surfaceTitle)) {
|
|
618
|
+
return {
|
|
619
|
+
rootId: null,
|
|
620
|
+
kind: first.surfaceContextKind,
|
|
621
|
+
title: first.surfaceTitle,
|
|
622
|
+
effectiveZIndex: first.effectiveZIndex,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
625
|
+
if (candidates.every((candidate) => candidate.surfaceContextKind === first.surfaceContextKind)) {
|
|
626
|
+
return {
|
|
627
|
+
rootId: null,
|
|
628
|
+
kind: first.surfaceContextKind,
|
|
629
|
+
title: null,
|
|
630
|
+
effectiveZIndex: first.effectiveZIndex,
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
return null;
|
|
634
|
+
}
|
|
635
|
+
function scoreSurfaceAwareCandidate(candidate, signature, expectedSurface) {
|
|
636
|
+
let score = scoreTargetCandidate(candidate.element, signature);
|
|
637
|
+
if (signature.uiTargetId && candidate.uiTargetId === signature.uiTargetId) {
|
|
638
|
+
score += 12;
|
|
639
|
+
}
|
|
640
|
+
for (const attribute of signature.stableAttributes) {
|
|
641
|
+
if (matchStableAttribute(candidate, attribute)) {
|
|
642
|
+
score += 10;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
if (signature.tag && candidate.tagName === signature.tag.toLowerCase()) {
|
|
646
|
+
score += 3;
|
|
647
|
+
}
|
|
648
|
+
if (signature.role && candidate.role === signature.role.toLowerCase()) {
|
|
649
|
+
score += 3;
|
|
650
|
+
}
|
|
651
|
+
if (signature.label && candidate.normalizedText === signature.label) {
|
|
652
|
+
score += 6;
|
|
653
|
+
}
|
|
654
|
+
if (signature.ariaLabel && candidate.ariaLabel === signature.ariaLabel) {
|
|
655
|
+
score += 4;
|
|
656
|
+
}
|
|
657
|
+
if (signature.placeholder && candidate.placeholder === signature.placeholder) {
|
|
658
|
+
score += 4;
|
|
659
|
+
}
|
|
660
|
+
if (signature.containerSignature &&
|
|
661
|
+
candidate.containerSignature === signature.containerSignature) {
|
|
662
|
+
score += 4;
|
|
663
|
+
}
|
|
664
|
+
if (expectedSurface?.title &&
|
|
665
|
+
candidate.surfaceTitle &&
|
|
666
|
+
normalizeComparableLabel(expectedSurface.title) ===
|
|
667
|
+
normalizeComparableLabel(candidate.surfaceTitle)) {
|
|
668
|
+
score += 4;
|
|
669
|
+
}
|
|
670
|
+
if (expectedSurface && candidate.surfaceContextKind === expectedSurface.kind) {
|
|
671
|
+
score += 3;
|
|
672
|
+
}
|
|
673
|
+
if (expectedSurface && candidate.surfaceDepth === 1) {
|
|
674
|
+
score += 1;
|
|
675
|
+
}
|
|
676
|
+
return score;
|
|
677
|
+
}
|
|
678
|
+
function resolveEntryTargetWithSurfaceContext(entry) {
|
|
679
|
+
const baseTrace = {
|
|
680
|
+
highlightSurfaceContextKind: "unknown",
|
|
681
|
+
highlightSurfaceRootId: null,
|
|
682
|
+
highlightSurfaceTitle: null,
|
|
683
|
+
candidateCountTotal: 0,
|
|
684
|
+
candidateCountAfterContextFilter: 0,
|
|
685
|
+
highlightMatchStage: "init",
|
|
686
|
+
highlightRenderMode: "none",
|
|
687
|
+
highlightRejectReason: null,
|
|
688
|
+
top1Score: null,
|
|
689
|
+
top2Score: null,
|
|
690
|
+
usedSameSurfaceFilter: false,
|
|
691
|
+
overlayZIndex: null,
|
|
692
|
+
targetEffectiveZIndex: null,
|
|
693
|
+
surfaceEffectiveZIndex: null,
|
|
694
|
+
};
|
|
695
|
+
if (!entryMatchesCurrentScreen(entry)) {
|
|
696
|
+
return {
|
|
697
|
+
candidate: null,
|
|
698
|
+
trace: {
|
|
699
|
+
...baseTrace,
|
|
700
|
+
highlightMatchStage: "context_filter_screen",
|
|
701
|
+
highlightRejectReason: "screen_mismatch",
|
|
702
|
+
},
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
const anchorAtom = getEntryAnchorAtom(entry);
|
|
706
|
+
const signature = buildSurfaceAwareSignature(entry);
|
|
707
|
+
const strongCandidates = [
|
|
708
|
+
...resolveUiTargetIdTargets(entry.actionUnit.primary_ui_target_id),
|
|
709
|
+
...resolveUiTargetIdTargets(anchorAtom?.ui_target_id),
|
|
710
|
+
...resolveStableKeyTargets(anchorAtom?.stable_key ?? null),
|
|
711
|
+
]
|
|
712
|
+
.filter((candidate, index, all) => all.indexOf(candidate) === index)
|
|
713
|
+
.filter((candidate) => candidate instanceof HTMLElement)
|
|
714
|
+
.map(buildHighlightCandidate);
|
|
715
|
+
const expectedSurface = deriveExpectedSurfaceContext(strongCandidates);
|
|
716
|
+
const totalCandidates = collectHighlightCandidates();
|
|
717
|
+
baseTrace.candidateCountTotal = totalCandidates.length;
|
|
718
|
+
let filteredCandidates = totalCandidates;
|
|
719
|
+
if (expectedSurface?.rootId) {
|
|
720
|
+
const next = totalCandidates.filter((candidate) => candidate.surfaceRootId === expectedSurface.rootId);
|
|
721
|
+
if (next.length > 0) {
|
|
722
|
+
filteredCandidates = next;
|
|
723
|
+
baseTrace.usedSameSurfaceFilter = true;
|
|
724
|
+
baseTrace.highlightMatchStage = "context_filter_surface_root";
|
|
725
|
+
}
|
|
726
|
+
}
|
|
727
|
+
else if (expectedSurface?.title) {
|
|
728
|
+
const next = totalCandidates.filter((candidate) => candidate.surfaceContextKind === expectedSurface.kind &&
|
|
729
|
+
normalizeComparableLabel(candidate.surfaceTitle) ===
|
|
730
|
+
normalizeComparableLabel(expectedSurface.title));
|
|
731
|
+
if (next.length > 0) {
|
|
732
|
+
filteredCandidates = next;
|
|
733
|
+
baseTrace.usedSameSurfaceFilter = true;
|
|
734
|
+
baseTrace.highlightMatchStage = "context_filter_kind_title";
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
else if (expectedSurface) {
|
|
738
|
+
const next = totalCandidates.filter((candidate) => candidate.surfaceContextKind === expectedSurface.kind);
|
|
739
|
+
if (next.length > 0) {
|
|
740
|
+
filteredCandidates = next;
|
|
741
|
+
baseTrace.usedSameSurfaceFilter = true;
|
|
742
|
+
baseTrace.highlightMatchStage = "context_filter_kind";
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
baseTrace.candidateCountAfterContextFilter = filteredCandidates.length;
|
|
746
|
+
if (filteredCandidates.length === 0) {
|
|
747
|
+
return {
|
|
748
|
+
candidate: null,
|
|
749
|
+
trace: {
|
|
750
|
+
...baseTrace,
|
|
751
|
+
highlightRejectReason: "candidate_not_found",
|
|
752
|
+
},
|
|
753
|
+
};
|
|
754
|
+
}
|
|
755
|
+
const strictUiMatches = filteredCandidates.filter((candidate) => signature.uiTargetId && candidate.uiTargetId === signature.uiTargetId);
|
|
756
|
+
if (strictUiMatches.length === 1) {
|
|
757
|
+
const candidate = strictUiMatches[0];
|
|
758
|
+
return {
|
|
759
|
+
candidate,
|
|
760
|
+
trace: {
|
|
761
|
+
...baseTrace,
|
|
762
|
+
highlightMatchStage: "strict_ui_target_id",
|
|
763
|
+
highlightSurfaceContextKind: candidate.surfaceContextKind,
|
|
764
|
+
highlightSurfaceRootId: candidate.surfaceRootId,
|
|
765
|
+
highlightSurfaceTitle: candidate.surfaceTitle,
|
|
766
|
+
},
|
|
767
|
+
};
|
|
768
|
+
}
|
|
769
|
+
if (strictUiMatches.length > 1) {
|
|
770
|
+
return {
|
|
771
|
+
candidate: null,
|
|
772
|
+
trace: {
|
|
773
|
+
...baseTrace,
|
|
774
|
+
highlightMatchStage: "strict_ui_target_id",
|
|
775
|
+
highlightRejectReason: "ambiguous_ui_target_id",
|
|
776
|
+
},
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
const strictStableMatches = filteredCandidates.filter((candidate) => signature.stableAttributes.some((attribute) => matchStableAttribute(candidate, attribute)));
|
|
780
|
+
if (strictStableMatches.length === 1) {
|
|
781
|
+
const candidate = strictStableMatches[0];
|
|
782
|
+
return {
|
|
783
|
+
candidate,
|
|
784
|
+
trace: {
|
|
785
|
+
...baseTrace,
|
|
786
|
+
highlightMatchStage: "strict_stable_attributes",
|
|
787
|
+
highlightSurfaceContextKind: candidate.surfaceContextKind,
|
|
788
|
+
highlightSurfaceRootId: candidate.surfaceRootId,
|
|
789
|
+
highlightSurfaceTitle: candidate.surfaceTitle,
|
|
790
|
+
},
|
|
791
|
+
};
|
|
792
|
+
}
|
|
793
|
+
if (strictStableMatches.length > 1) {
|
|
794
|
+
return {
|
|
795
|
+
candidate: null,
|
|
796
|
+
trace: {
|
|
797
|
+
...baseTrace,
|
|
798
|
+
highlightMatchStage: "strict_stable_attributes",
|
|
799
|
+
highlightRejectReason: "ambiguous_stable_attributes",
|
|
800
|
+
},
|
|
801
|
+
};
|
|
802
|
+
}
|
|
803
|
+
const scored = filteredCandidates
|
|
804
|
+
.map((candidate) => ({
|
|
805
|
+
candidate,
|
|
806
|
+
score: scoreSurfaceAwareCandidate(candidate, signature, expectedSurface),
|
|
807
|
+
}))
|
|
808
|
+
.sort((left, right) => right.score - left.score);
|
|
809
|
+
const top1 = scored[0] ?? null;
|
|
810
|
+
const top2 = scored[1] ?? null;
|
|
811
|
+
baseTrace.top1Score = top1?.score ?? null;
|
|
812
|
+
baseTrace.top2Score = top2?.score ?? null;
|
|
813
|
+
if (!top1) {
|
|
814
|
+
return {
|
|
815
|
+
candidate: null,
|
|
816
|
+
trace: {
|
|
817
|
+
...baseTrace,
|
|
818
|
+
highlightMatchStage: "scored_match",
|
|
819
|
+
highlightRejectReason: "candidate_not_found",
|
|
820
|
+
},
|
|
821
|
+
};
|
|
822
|
+
}
|
|
823
|
+
if (top1.score < 14) {
|
|
824
|
+
return {
|
|
825
|
+
candidate: null,
|
|
826
|
+
trace: {
|
|
827
|
+
...baseTrace,
|
|
828
|
+
highlightMatchStage: "scored_match",
|
|
829
|
+
highlightRejectReason: "below_threshold",
|
|
830
|
+
},
|
|
831
|
+
};
|
|
832
|
+
}
|
|
833
|
+
if (top2 && top1.score - top2.score < 3) {
|
|
834
|
+
return {
|
|
835
|
+
candidate: null,
|
|
836
|
+
trace: {
|
|
837
|
+
...baseTrace,
|
|
838
|
+
highlightMatchStage: "scored_match",
|
|
839
|
+
highlightRejectReason: top1.score === top2.score ? "score_tie" : "score_margin_too_small",
|
|
840
|
+
},
|
|
841
|
+
};
|
|
842
|
+
}
|
|
843
|
+
return {
|
|
844
|
+
candidate: top1.candidate,
|
|
845
|
+
trace: {
|
|
846
|
+
...baseTrace,
|
|
847
|
+
highlightMatchStage: "scored_match",
|
|
848
|
+
highlightSurfaceContextKind: top1.candidate.surfaceContextKind,
|
|
849
|
+
highlightSurfaceRootId: top1.candidate.surfaceRootId,
|
|
850
|
+
highlightSurfaceTitle: top1.candidate.surfaceTitle,
|
|
851
|
+
},
|
|
852
|
+
};
|
|
853
|
+
}
|
|
854
|
+
function createTargetSpotlight() {
|
|
855
|
+
const ring = document.createElement("div");
|
|
856
|
+
markAuthoringRoot(ring);
|
|
857
|
+
ring.dataset.clueAuthoringTargetHighlight = "true";
|
|
858
|
+
ring.style.position = "fixed";
|
|
859
|
+
ring.style.left = "0px";
|
|
860
|
+
ring.style.top = "0px";
|
|
861
|
+
ring.style.width = "0px";
|
|
862
|
+
ring.style.height = "0px";
|
|
863
|
+
ring.style.borderRadius = "16px";
|
|
864
|
+
ring.style.border = "1.5px solid rgba(244,150,39,0.95)";
|
|
865
|
+
ring.style.background = "rgba(255,250,244,0.12)";
|
|
866
|
+
ring.style.boxShadow =
|
|
867
|
+
"rgba(255,255,255,0.96) 0px 1px 0px inset, rgba(244,150,39,0.18) 0px 0px 0px 1px, rgba(244,150,39,0.24) 0px 10px 30px";
|
|
868
|
+
ring.style.pointerEvents = "none";
|
|
869
|
+
ring.style.opacity = "0";
|
|
870
|
+
ring.style.transform = "scale(0.98)";
|
|
871
|
+
ring.style.transition =
|
|
872
|
+
"opacity 180ms ease, transform 220ms cubic-bezier(0.22, 1, 0.36, 1)";
|
|
873
|
+
ring.style.zIndex = "2147483300";
|
|
874
|
+
const badge = document.createElement("div");
|
|
875
|
+
markAuthoringRoot(badge);
|
|
876
|
+
badge.style.position = "fixed";
|
|
877
|
+
badge.style.left = "0px";
|
|
878
|
+
badge.style.top = "0px";
|
|
879
|
+
badge.style.display = "inline-flex";
|
|
880
|
+
badge.style.alignItems = "center";
|
|
881
|
+
badge.style.padding = "6px 10px";
|
|
882
|
+
badge.style.borderRadius = "9999px";
|
|
883
|
+
badge.style.background = "rgba(255,253,249,0.98)";
|
|
884
|
+
badge.style.border = "1px solid rgba(244,150,39,0.22)";
|
|
885
|
+
badge.style.boxShadow =
|
|
886
|
+
"rgba(255,255,255,0.96) 0px 1px 0px inset, rgba(17,24,39,0.06) 0px 10px 24px";
|
|
887
|
+
badge.style.fontSize = "11px";
|
|
888
|
+
badge.style.fontWeight = "600";
|
|
889
|
+
badge.style.letterSpacing = "0.08em";
|
|
890
|
+
badge.style.textTransform = "uppercase";
|
|
891
|
+
badge.style.color = "#8f5b2f";
|
|
892
|
+
badge.style.pointerEvents = "none";
|
|
893
|
+
badge.style.opacity = "0";
|
|
894
|
+
badge.style.transform = "translateY(4px)";
|
|
895
|
+
badge.style.transition =
|
|
896
|
+
"opacity 180ms ease, transform 220ms cubic-bezier(0.22, 1, 0.36, 1)";
|
|
897
|
+
badge.style.zIndex = "2147483301";
|
|
898
|
+
badge.textContent = "対象要素";
|
|
899
|
+
(document.body ?? document.documentElement).append(ring, badge);
|
|
900
|
+
let activeElement = null;
|
|
901
|
+
let activeTrace = null;
|
|
902
|
+
let activeInlineElement = null;
|
|
903
|
+
const inlineRestoreState = new WeakMap();
|
|
904
|
+
const hide = () => {
|
|
905
|
+
ring.style.opacity = "0";
|
|
906
|
+
ring.style.transform = "scale(0.98)";
|
|
907
|
+
badge.style.opacity = "0";
|
|
908
|
+
badge.style.transform = "translateY(4px)";
|
|
909
|
+
};
|
|
910
|
+
const clearInlineOutline = () => {
|
|
911
|
+
if (!activeInlineElement) {
|
|
912
|
+
return;
|
|
913
|
+
}
|
|
914
|
+
const previous = inlineRestoreState.get(activeInlineElement);
|
|
915
|
+
if (previous) {
|
|
916
|
+
activeInlineElement.style.outline = previous.outline;
|
|
917
|
+
activeInlineElement.style.outlineOffset = previous.outlineOffset;
|
|
918
|
+
activeInlineElement.style.boxShadow = previous.boxShadow;
|
|
919
|
+
}
|
|
920
|
+
else {
|
|
921
|
+
activeInlineElement.style.outline = "";
|
|
922
|
+
activeInlineElement.style.outlineOffset = "";
|
|
923
|
+
activeInlineElement.style.boxShadow = "";
|
|
924
|
+
}
|
|
925
|
+
inlineRestoreState.delete(activeInlineElement);
|
|
926
|
+
activeInlineElement = null;
|
|
927
|
+
};
|
|
928
|
+
const hasProblematicHighlightAncestors = (element) => {
|
|
929
|
+
const hasMeaningfulComputedValue = (value) => {
|
|
930
|
+
const normalized = (value ?? "").trim().toLowerCase();
|
|
931
|
+
return normalized.length > 0 && normalized !== "none" && normalized !== "auto";
|
|
932
|
+
};
|
|
933
|
+
let current = element.parentElement;
|
|
934
|
+
while (current) {
|
|
935
|
+
const computed = window.getComputedStyle(current);
|
|
936
|
+
if (computed.overflowX === "hidden" ||
|
|
937
|
+
computed.overflowY === "hidden" ||
|
|
938
|
+
computed.overflowX === "clip" ||
|
|
939
|
+
computed.overflowY === "clip" ||
|
|
940
|
+
hasMeaningfulComputedValue(computed.transform) ||
|
|
941
|
+
hasMeaningfulComputedValue(computed.filter) ||
|
|
942
|
+
hasMeaningfulComputedValue(computed.backdropFilter) ||
|
|
943
|
+
hasMeaningfulComputedValue(computed.perspective) ||
|
|
944
|
+
hasMeaningfulComputedValue(computed.clipPath) ||
|
|
945
|
+
computed.isolation === "isolate") {
|
|
946
|
+
return true;
|
|
947
|
+
}
|
|
948
|
+
current = current.parentElement;
|
|
949
|
+
}
|
|
950
|
+
return false;
|
|
951
|
+
};
|
|
952
|
+
const applyInlineOutline = (element) => {
|
|
953
|
+
clearInlineOutline();
|
|
954
|
+
inlineRestoreState.set(element, {
|
|
955
|
+
outline: element.style.outline,
|
|
956
|
+
outlineOffset: element.style.outlineOffset,
|
|
957
|
+
boxShadow: element.style.boxShadow,
|
|
958
|
+
});
|
|
959
|
+
element.style.outline = "2px solid rgba(244,150,39,0.96)";
|
|
960
|
+
element.style.outlineOffset = "3px";
|
|
961
|
+
element.style.boxShadow =
|
|
962
|
+
"rgba(244,150,39,0.22) 0px 0px 0px 4px, rgba(255,255,255,0.78) 0px 0px 0px 1px";
|
|
963
|
+
activeInlineElement = element;
|
|
964
|
+
};
|
|
965
|
+
const update = () => {
|
|
966
|
+
if (!activeElement || !activeElement.isConnected) {
|
|
967
|
+
hide();
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
const rect = activeElement.getBoundingClientRect();
|
|
971
|
+
if (rect.width <= 0 || rect.height <= 0) {
|
|
972
|
+
hide();
|
|
973
|
+
return;
|
|
974
|
+
}
|
|
975
|
+
const ringInset = 8;
|
|
976
|
+
const left = Math.max(8, rect.left - ringInset);
|
|
977
|
+
const top = Math.max(8, rect.top - ringInset);
|
|
978
|
+
const width = rect.width + ringInset * 2;
|
|
979
|
+
const height = rect.height + ringInset * 2;
|
|
980
|
+
const badgeTop = top > 42 ? top - 36 : top + height + 8;
|
|
981
|
+
const badgeLeft = Math.max(8, Math.min(left, window.innerWidth - 120));
|
|
982
|
+
const surface = resolveSurfaceContext(activeElement);
|
|
983
|
+
const targetEffectiveZIndex = readEffectiveZIndex(activeElement);
|
|
984
|
+
const surfaceEffectiveZIndex = surface.effectiveZIndex;
|
|
985
|
+
const overlayZIndex = Math.max(2147483300, targetEffectiveZIndex, surfaceEffectiveZIndex) + 2;
|
|
986
|
+
ring.style.left = `${Math.round(left)}px`;
|
|
987
|
+
ring.style.top = `${Math.round(top)}px`;
|
|
988
|
+
ring.style.width = `${Math.round(width)}px`;
|
|
989
|
+
ring.style.height = `${Math.round(height)}px`;
|
|
990
|
+
ring.style.borderRadius = `${Math.max(12, Math.min(20, height / 4))}px`;
|
|
991
|
+
ring.style.opacity = "1";
|
|
992
|
+
ring.style.transform = "scale(1)";
|
|
993
|
+
ring.style.zIndex = String(overlayZIndex);
|
|
994
|
+
badge.style.left = `${Math.round(badgeLeft)}px`;
|
|
995
|
+
badge.style.top = `${Math.round(badgeTop)}px`;
|
|
996
|
+
badge.style.opacity = "1";
|
|
997
|
+
badge.style.transform = "translateY(0px)";
|
|
998
|
+
badge.style.zIndex = String(overlayZIndex + 1);
|
|
999
|
+
if (activeTrace) {
|
|
1000
|
+
activeTrace.highlightRenderMode = "overlay_box";
|
|
1001
|
+
activeTrace.overlayZIndex = overlayZIndex;
|
|
1002
|
+
activeTrace.targetEffectiveZIndex = targetEffectiveZIndex;
|
|
1003
|
+
activeTrace.surfaceEffectiveZIndex = surfaceEffectiveZIndex;
|
|
1004
|
+
}
|
|
1005
|
+
};
|
|
1006
|
+
const handleViewportChange = () => {
|
|
1007
|
+
window.requestAnimationFrame(update);
|
|
1008
|
+
};
|
|
1009
|
+
window.addEventListener("scroll", handleViewportChange, true);
|
|
1010
|
+
window.addEventListener("resize", handleViewportChange);
|
|
1011
|
+
return {
|
|
1012
|
+
clear() {
|
|
1013
|
+
activeElement = null;
|
|
1014
|
+
activeTrace = null;
|
|
1015
|
+
clearInlineOutline();
|
|
1016
|
+
hide();
|
|
1017
|
+
},
|
|
1018
|
+
focusEntry(entry) {
|
|
1019
|
+
clearInlineOutline();
|
|
1020
|
+
const match = resolveEntryTargetWithSurfaceContext(entry);
|
|
1021
|
+
activeTrace = match.trace;
|
|
1022
|
+
if (!(match.candidate?.element instanceof HTMLElement)) {
|
|
1023
|
+
activeElement = null;
|
|
1024
|
+
hide();
|
|
1025
|
+
return match.trace;
|
|
1026
|
+
}
|
|
1027
|
+
const target = match.candidate.element;
|
|
1028
|
+
activeElement = target;
|
|
1029
|
+
if (typeof target.scrollIntoView === "function") {
|
|
1030
|
+
target.scrollIntoView({
|
|
1031
|
+
block: "center",
|
|
1032
|
+
inline: "center",
|
|
1033
|
+
behavior: "smooth",
|
|
1034
|
+
});
|
|
1035
|
+
}
|
|
1036
|
+
if (hasProblematicHighlightAncestors(target)) {
|
|
1037
|
+
applyInlineOutline(target);
|
|
1038
|
+
hide();
|
|
1039
|
+
match.trace.highlightRenderMode = "inline_outline";
|
|
1040
|
+
match.trace.highlightSurfaceContextKind = match.candidate.surfaceContextKind;
|
|
1041
|
+
match.trace.highlightSurfaceRootId = match.candidate.surfaceRootId;
|
|
1042
|
+
match.trace.highlightSurfaceTitle = match.candidate.surfaceTitle;
|
|
1043
|
+
match.trace.targetEffectiveZIndex = match.candidate.effectiveZIndex;
|
|
1044
|
+
match.trace.surfaceEffectiveZIndex = resolveSurfaceContext(target).effectiveZIndex;
|
|
1045
|
+
match.trace.overlayZIndex = null;
|
|
1046
|
+
return match.trace;
|
|
1047
|
+
}
|
|
1048
|
+
const surface = resolveSurfaceContext(target);
|
|
1049
|
+
const targetEffectiveZIndex = readEffectiveZIndex(target);
|
|
1050
|
+
const surfaceEffectiveZIndex = surface.effectiveZIndex;
|
|
1051
|
+
const overlayZIndex = Math.max(2147483300, targetEffectiveZIndex, surfaceEffectiveZIndex) + 2;
|
|
1052
|
+
match.trace.highlightRenderMode = "overlay_box";
|
|
1053
|
+
match.trace.overlayZIndex = overlayZIndex;
|
|
1054
|
+
match.trace.targetEffectiveZIndex = targetEffectiveZIndex;
|
|
1055
|
+
match.trace.surfaceEffectiveZIndex = surfaceEffectiveZIndex;
|
|
1056
|
+
update();
|
|
1057
|
+
window.requestAnimationFrame(() => {
|
|
1058
|
+
update();
|
|
1059
|
+
window.requestAnimationFrame(update);
|
|
1060
|
+
});
|
|
1061
|
+
return match.trace;
|
|
1062
|
+
},
|
|
1063
|
+
dispose() {
|
|
1064
|
+
clearInlineOutline();
|
|
1065
|
+
window.removeEventListener("scroll", handleViewportChange, true);
|
|
1066
|
+
window.removeEventListener("resize", handleViewportChange);
|
|
1067
|
+
ring.remove();
|
|
1068
|
+
badge.remove();
|
|
1069
|
+
},
|
|
1070
|
+
};
|
|
1071
|
+
}
|
|
1072
|
+
function getTooltipPlacement(x, y) {
|
|
1073
|
+
if (Math.abs(x) < 4) {
|
|
1074
|
+
return "right";
|
|
1075
|
+
}
|
|
1076
|
+
if (Math.abs(y) > Math.abs(x)) {
|
|
1077
|
+
return y < 0 ? "top" : "bottom";
|
|
1078
|
+
}
|
|
1079
|
+
return x < 0 ? "left" : "right";
|
|
1080
|
+
}
|
|
1081
|
+
function createActionTooltip(label, placement) {
|
|
1082
|
+
const tooltip = document.createElement("div");
|
|
1083
|
+
tooltip.dataset.clueAuthoringActionTooltip = "true";
|
|
1084
|
+
tooltip.textContent = label;
|
|
1085
|
+
tooltip.setAttribute("aria-hidden", "true");
|
|
1086
|
+
tooltip.style.position = "absolute";
|
|
1087
|
+
tooltip.style.display = "inline-flex";
|
|
1088
|
+
tooltip.style.alignItems = "center";
|
|
1089
|
+
tooltip.style.justifyContent = "center";
|
|
1090
|
+
tooltip.style.width = "max-content";
|
|
1091
|
+
tooltip.style.maxWidth = "176px";
|
|
1092
|
+
tooltip.style.padding = "8px 10px";
|
|
1093
|
+
tooltip.style.borderRadius = "12px";
|
|
1094
|
+
tooltip.style.background = "rgba(255,255,255,0.98)";
|
|
1095
|
+
tooltip.style.border = "1px solid rgba(17,24,39,0.12)";
|
|
1096
|
+
tooltip.style.boxShadow =
|
|
1097
|
+
"rgba(255,255,255,0.96) 0px 1px 0px inset, rgba(17,24,39,0.08) 0px 0px 0px 1px, rgba(17,24,39,0.05) 0px 10px 22px";
|
|
1098
|
+
tooltip.style.fontSize = "12px";
|
|
1099
|
+
tooltip.style.fontWeight = "500";
|
|
1100
|
+
tooltip.style.lineHeight = "1.35";
|
|
1101
|
+
tooltip.style.letterSpacing = "0.12px";
|
|
1102
|
+
tooltip.style.color = "#111827";
|
|
1103
|
+
tooltip.style.whiteSpace = "nowrap";
|
|
1104
|
+
tooltip.style.pointerEvents = "none";
|
|
1105
|
+
tooltip.style.opacity = "0";
|
|
1106
|
+
tooltip.style.transition =
|
|
1107
|
+
"opacity 180ms ease, transform 220ms cubic-bezier(0.22, 1, 0.36, 1)";
|
|
1108
|
+
tooltip.style.zIndex = "40";
|
|
1109
|
+
switch (placement) {
|
|
1110
|
+
case "top":
|
|
1111
|
+
tooltip.style.left = "50%";
|
|
1112
|
+
tooltip.style.bottom = "calc(100% + 12px)";
|
|
1113
|
+
tooltip.style.transform = "translate(-50%, 8px) scale(0.96)";
|
|
1114
|
+
break;
|
|
1115
|
+
case "bottom":
|
|
1116
|
+
tooltip.style.left = "50%";
|
|
1117
|
+
tooltip.style.top = "calc(100% + 12px)";
|
|
1118
|
+
tooltip.style.transform = "translate(-50%, -8px) scale(0.96)";
|
|
1119
|
+
break;
|
|
1120
|
+
case "left":
|
|
1121
|
+
tooltip.style.right = "calc(100% + 12px)";
|
|
1122
|
+
tooltip.style.top = "50%";
|
|
1123
|
+
tooltip.style.transform = "translate(8px, -50%) scale(0.96)";
|
|
1124
|
+
break;
|
|
1125
|
+
case "right":
|
|
1126
|
+
tooltip.style.left = "calc(100% + 12px)";
|
|
1127
|
+
tooltip.style.top = "50%";
|
|
1128
|
+
tooltip.style.transform = "translate(-8px, -50%) scale(0.96)";
|
|
1129
|
+
break;
|
|
1130
|
+
}
|
|
1131
|
+
return tooltip;
|
|
1132
|
+
}
|
|
1133
|
+
function setTooltipVisibility(tooltip, placement, visible) {
|
|
1134
|
+
tooltip.style.opacity = visible ? "1" : "0";
|
|
1135
|
+
switch (placement) {
|
|
1136
|
+
case "top":
|
|
1137
|
+
tooltip.style.transform = visible
|
|
1138
|
+
? "translate(-50%, 0px) scale(1)"
|
|
1139
|
+
: "translate(-50%, 8px) scale(0.96)";
|
|
1140
|
+
break;
|
|
1141
|
+
case "bottom":
|
|
1142
|
+
tooltip.style.transform = visible
|
|
1143
|
+
? "translate(-50%, 0px) scale(1)"
|
|
1144
|
+
: "translate(-50%, -8px) scale(0.96)";
|
|
1145
|
+
break;
|
|
1146
|
+
case "left":
|
|
1147
|
+
tooltip.style.transform = visible
|
|
1148
|
+
? "translate(0px, -50%) scale(1)"
|
|
1149
|
+
: "translate(8px, -50%) scale(0.96)";
|
|
1150
|
+
break;
|
|
1151
|
+
case "right":
|
|
1152
|
+
tooltip.style.transform = visible
|
|
1153
|
+
? "translate(0px, -50%) scale(1)"
|
|
1154
|
+
: "translate(-8px, -50%) scale(0.96)";
|
|
1155
|
+
break;
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
function createActionButton(label, x, y) {
|
|
1159
|
+
const button = document.createElement("button");
|
|
1160
|
+
const placement = getTooltipPlacement(x, y);
|
|
1161
|
+
const tooltip = createActionTooltip(label, placement);
|
|
1162
|
+
const actionIconSize = Math.round(TOOLBAR_ACTION_BUTTON_SIZE * 0.41);
|
|
1163
|
+
const actionAnchor = TOOLBAR_BASE_BUTTON_EDGE_OFFSET + TOOLBAR_BASE_BUTTON_SIZE / 2;
|
|
1164
|
+
button.type = "button";
|
|
1165
|
+
button.setAttribute("aria-label", label);
|
|
1166
|
+
button.style.display = "grid";
|
|
1167
|
+
button.style.placeItems = "center";
|
|
1168
|
+
button.style.width = `${TOOLBAR_ACTION_BUTTON_SIZE}px`;
|
|
1169
|
+
button.style.height = `${TOOLBAR_ACTION_BUTTON_SIZE}px`;
|
|
1170
|
+
button.style.border = "none";
|
|
1171
|
+
button.style.borderRadius = "9999px";
|
|
1172
|
+
button.style.padding = "0";
|
|
1173
|
+
button.style.color = "#111827";
|
|
1174
|
+
button.style.cursor = "pointer";
|
|
1175
|
+
button.style.position = "absolute";
|
|
1176
|
+
button.style.left = `${actionAnchor}px`;
|
|
1177
|
+
button.style.top = `${actionAnchor}px`;
|
|
1178
|
+
button.style.marginLeft = `${-TOOLBAR_ACTION_BUTTON_SIZE / 2}px`;
|
|
1179
|
+
button.style.marginTop = `${-TOOLBAR_ACTION_BUTTON_SIZE / 2}px`;
|
|
1180
|
+
button.style.opacity = "0";
|
|
1181
|
+
button.style.transform = "translate(0px, 0px) scale(0.72)";
|
|
1182
|
+
button.style.transformOrigin = "center";
|
|
1183
|
+
button.style.transition =
|
|
1184
|
+
"transform 320ms cubic-bezier(0.22, 1, 0.36, 1), opacity 220ms ease, box-shadow 220ms ease";
|
|
1185
|
+
button.style.boxShadow =
|
|
1186
|
+
"rgba(17,24,39,0.08) 0px 10px 24px, rgba(17,24,39,0.06) 0px 0px 0px 1px";
|
|
1187
|
+
button.style.pointerEvents = "none";
|
|
1188
|
+
button.style.willChange = "transform, opacity";
|
|
1189
|
+
button.style.overflow = "visible";
|
|
1190
|
+
button.style.zIndex = "30";
|
|
1191
|
+
applyGlassSurfaceStyles(button);
|
|
1192
|
+
const showTooltip = () => setTooltipVisibility(tooltip, placement, true);
|
|
1193
|
+
const hideTooltip = () => setTooltipVisibility(tooltip, placement, false);
|
|
1194
|
+
button.addEventListener("mouseenter", showTooltip);
|
|
1195
|
+
button.addEventListener("mouseleave", hideTooltip);
|
|
1196
|
+
button.addEventListener("focus", showTooltip);
|
|
1197
|
+
button.addEventListener("blur", hideTooltip);
|
|
1198
|
+
button.append(tooltip);
|
|
1199
|
+
return button;
|
|
1200
|
+
}
|
|
1201
|
+
function setActionButtonVisual(button, label, icon) {
|
|
1202
|
+
const actionIconSize = Math.round(TOOLBAR_ACTION_BUTTON_SIZE * 0.41);
|
|
1203
|
+
button.setAttribute("aria-label", label);
|
|
1204
|
+
const tooltip = button.querySelector("[data-clue-authoring-action-tooltip='true']");
|
|
1205
|
+
if (tooltip) {
|
|
1206
|
+
tooltip.textContent = label;
|
|
1207
|
+
}
|
|
1208
|
+
const existingIcon = button.querySelector("svg");
|
|
1209
|
+
if (existingIcon) {
|
|
1210
|
+
existingIcon.remove();
|
|
1211
|
+
}
|
|
1212
|
+
button.append(createLucideIcon(icon, actionIconSize));
|
|
1213
|
+
}
|
|
1214
|
+
function createRecordedEventCard(entry, options) {
|
|
1215
|
+
const card = document.createElement("div");
|
|
1216
|
+
card.dataset.clueAuthoringEventId = entry.id;
|
|
1217
|
+
if (entry.highlightTrace) {
|
|
1218
|
+
card.dataset.clueAuthoringHighlightTrace = JSON.stringify(entry.highlightTrace);
|
|
1219
|
+
}
|
|
1220
|
+
card.style.position = "relative";
|
|
1221
|
+
card.style.display = "grid";
|
|
1222
|
+
card.style.width = "100%";
|
|
1223
|
+
card.style.gap = "10px";
|
|
1224
|
+
card.style.padding = "14px";
|
|
1225
|
+
card.style.borderRadius = "14px";
|
|
1226
|
+
card.style.border = entry.selected
|
|
1227
|
+
? "1.5px solid rgba(244,150,39,0.9)"
|
|
1228
|
+
: "1px solid rgba(17,24,39,0.08)";
|
|
1229
|
+
card.style.boxShadow = entry.selected
|
|
1230
|
+
? "rgba(244,150,39,0.12) 0px 14px 30px, rgba(244,150,39,0.08) 0px 0px 0px 1px"
|
|
1231
|
+
: "rgba(17,24,39,0.08) 0px 10px 24px, rgba(17,24,39,0.05) 0px 0px 0px 1px";
|
|
1232
|
+
card.style.textAlign = "left";
|
|
1233
|
+
card.style.cursor = "pointer";
|
|
1234
|
+
card.style.transition =
|
|
1235
|
+
"border-color 180ms ease, background 180ms ease, box-shadow 220ms ease, transform 220ms ease";
|
|
1236
|
+
card.style.color = "#111827";
|
|
1237
|
+
applyGlassSurfaceStyles(card, entry.selected ? "strong" : "default");
|
|
1238
|
+
if (entry.selected) {
|
|
1239
|
+
card.style.border = "1.5px solid rgba(244,150,39,0.58)";
|
|
1240
|
+
}
|
|
1241
|
+
if (entry.pendingRemoval) {
|
|
1242
|
+
card.style.opacity = "0.56";
|
|
1243
|
+
card.style.transform = "translateY(0px)";
|
|
1244
|
+
}
|
|
1245
|
+
const cardButton = document.createElement("button");
|
|
1246
|
+
cardButton.type = "button";
|
|
1247
|
+
cardButton.style.display = "grid";
|
|
1248
|
+
cardButton.style.gap = "10px";
|
|
1249
|
+
cardButton.style.padding = "0";
|
|
1250
|
+
cardButton.style.margin = "0";
|
|
1251
|
+
cardButton.style.border = "none";
|
|
1252
|
+
cardButton.style.background = "transparent";
|
|
1253
|
+
cardButton.style.textAlign = "left";
|
|
1254
|
+
cardButton.style.cursor = "pointer";
|
|
1255
|
+
cardButton.style.color = "inherit";
|
|
1256
|
+
const topRow = document.createElement("div");
|
|
1257
|
+
topRow.style.display = "flex";
|
|
1258
|
+
topRow.style.alignItems = "center";
|
|
1259
|
+
topRow.style.justifyContent = "space-between";
|
|
1260
|
+
topRow.style.gap = "12px";
|
|
1261
|
+
const title = document.createElement("p");
|
|
1262
|
+
title.textContent = entry.title;
|
|
1263
|
+
title.style.margin = "0";
|
|
1264
|
+
title.style.fontSize = "14px";
|
|
1265
|
+
title.style.fontWeight = "600";
|
|
1266
|
+
title.style.lineHeight = "1.5";
|
|
1267
|
+
const screenLabel = document.createElement("p");
|
|
1268
|
+
screenLabel.textContent = entry.screenLabel;
|
|
1269
|
+
screenLabel.style.margin = "0";
|
|
1270
|
+
screenLabel.style.fontSize = "11px";
|
|
1271
|
+
screenLabel.style.fontWeight = "600";
|
|
1272
|
+
screenLabel.style.lineHeight = "1.4";
|
|
1273
|
+
screenLabel.style.letterSpacing = "0.02em";
|
|
1274
|
+
screenLabel.style.color = "#777169";
|
|
1275
|
+
const time = document.createElement("span");
|
|
1276
|
+
time.textContent = entry.occurredAtLabel;
|
|
1277
|
+
time.style.fontSize = "12px";
|
|
1278
|
+
time.style.fontWeight = "500";
|
|
1279
|
+
time.style.color = "#777169";
|
|
1280
|
+
time.style.flexShrink = "0";
|
|
1281
|
+
const actionMeta = document.createElement("div");
|
|
1282
|
+
actionMeta.style.display = "inline-flex";
|
|
1283
|
+
actionMeta.style.alignItems = "center";
|
|
1284
|
+
actionMeta.style.gap = "8px";
|
|
1285
|
+
const removeButton = document.createElement("button");
|
|
1286
|
+
removeButton.dataset.clueAuthoringRemoveAction = entry.id;
|
|
1287
|
+
removeButton.type = "button";
|
|
1288
|
+
removeButton.textContent = "削除";
|
|
1289
|
+
removeButton.style.display = entry.pendingRemoval ? "none" : "inline-flex";
|
|
1290
|
+
removeButton.style.alignItems = "center";
|
|
1291
|
+
removeButton.style.justifyContent = "center";
|
|
1292
|
+
removeButton.style.padding = "4px 8px";
|
|
1293
|
+
removeButton.style.borderRadius = "9999px";
|
|
1294
|
+
removeButton.style.border = "1px solid rgba(17,24,39,0.08)";
|
|
1295
|
+
removeButton.style.background = "rgba(255,255,255,0.34)";
|
|
1296
|
+
removeButton.style.fontSize = "11px";
|
|
1297
|
+
removeButton.style.fontWeight = "600";
|
|
1298
|
+
removeButton.style.color = "#8d3d2e";
|
|
1299
|
+
removeButton.style.cursor = "pointer";
|
|
1300
|
+
actionMeta.append(removeButton, time);
|
|
1301
|
+
const badge = document.createElement("span");
|
|
1302
|
+
badge.textContent = entry.badgeLabel;
|
|
1303
|
+
badge.style.display = "inline-flex";
|
|
1304
|
+
badge.style.alignItems = "center";
|
|
1305
|
+
badge.style.width = "fit-content";
|
|
1306
|
+
badge.style.padding = "3px 8px";
|
|
1307
|
+
badge.style.borderRadius = "9999px";
|
|
1308
|
+
badge.style.background = entry.selected
|
|
1309
|
+
? "rgba(244,150,39,0.12)"
|
|
1310
|
+
: "rgba(244,244,244,0.96)";
|
|
1311
|
+
badge.style.border = "1px solid rgba(17,24,39,0.06)";
|
|
1312
|
+
badge.style.fontSize = "11px";
|
|
1313
|
+
badge.style.lineHeight = "1.2";
|
|
1314
|
+
badge.style.fontWeight = "500";
|
|
1315
|
+
badge.style.color = "#777169";
|
|
1316
|
+
const relatedDisclosure = document.createElement("div");
|
|
1317
|
+
relatedDisclosure.style.display = "grid";
|
|
1318
|
+
relatedDisclosure.style.gap = "0";
|
|
1319
|
+
relatedDisclosure.style.borderRadius = "10px";
|
|
1320
|
+
relatedDisclosure.style.overflow = "hidden";
|
|
1321
|
+
applyGlassSurfaceStyles(relatedDisclosure, "soft");
|
|
1322
|
+
const relatedButton = document.createElement("button");
|
|
1323
|
+
relatedButton.type = "button";
|
|
1324
|
+
relatedButton.style.display = "flex";
|
|
1325
|
+
relatedButton.style.alignItems = "center";
|
|
1326
|
+
relatedButton.style.justifyContent = "space-between";
|
|
1327
|
+
relatedButton.style.width = "100%";
|
|
1328
|
+
relatedButton.style.gap = "12px";
|
|
1329
|
+
relatedButton.style.padding = "10px 12px";
|
|
1330
|
+
relatedButton.style.margin = "0";
|
|
1331
|
+
relatedButton.style.border = "none";
|
|
1332
|
+
relatedButton.style.background = "transparent";
|
|
1333
|
+
relatedButton.style.cursor = "pointer";
|
|
1334
|
+
relatedButton.style.color = "inherit";
|
|
1335
|
+
relatedButton.style.textAlign = "left";
|
|
1336
|
+
const relatedLabel = document.createElement("span");
|
|
1337
|
+
relatedLabel.textContent = "関連イベント";
|
|
1338
|
+
relatedLabel.style.fontSize = "11px";
|
|
1339
|
+
relatedLabel.style.fontWeight = "600";
|
|
1340
|
+
relatedLabel.style.color = "#111827";
|
|
1341
|
+
const relatedMeta = document.createElement("div");
|
|
1342
|
+
relatedMeta.style.display = "inline-flex";
|
|
1343
|
+
relatedMeta.style.alignItems = "center";
|
|
1344
|
+
relatedMeta.style.gap = "8px";
|
|
1345
|
+
const relatedCount = document.createElement("span");
|
|
1346
|
+
relatedCount.textContent = `${entry.actionUnit.atom_count}件`;
|
|
1347
|
+
relatedCount.style.fontSize = "11px";
|
|
1348
|
+
relatedCount.style.fontWeight = "600";
|
|
1349
|
+
relatedCount.style.color = "#111827";
|
|
1350
|
+
const relatedChevron = createLucideIcon(options.expanded ? ChevronUp : ChevronDown, 16);
|
|
1351
|
+
relatedChevron.style.color = "rgba(119,113,105,0.7)";
|
|
1352
|
+
relatedMeta.append(relatedCount, relatedChevron);
|
|
1353
|
+
relatedButton.append(relatedLabel, relatedMeta);
|
|
1354
|
+
const relatedContentWrap = document.createElement("div");
|
|
1355
|
+
relatedContentWrap.style.display = "grid";
|
|
1356
|
+
relatedContentWrap.style.overflow = "hidden";
|
|
1357
|
+
relatedContentWrap.style.maxHeight = "0px";
|
|
1358
|
+
relatedContentWrap.style.opacity = "0";
|
|
1359
|
+
relatedContentWrap.style.transition =
|
|
1360
|
+
"max-height 280ms cubic-bezier(0.22, 1, 0.36, 1), opacity 180ms ease";
|
|
1361
|
+
const evidence = document.createElement("div");
|
|
1362
|
+
evidence.style.display = "grid";
|
|
1363
|
+
evidence.style.gap = "10px";
|
|
1364
|
+
evidence.style.padding = "10px 12px 12px";
|
|
1365
|
+
evidence.style.borderTop = "1px solid rgba(17,24,39,0.08)";
|
|
1366
|
+
applyGlassSurfaceStyles(evidence, "soft");
|
|
1367
|
+
topRow.append(badge, actionMeta);
|
|
1368
|
+
cardButton.append(topRow);
|
|
1369
|
+
if (entry.screenLabel) {
|
|
1370
|
+
cardButton.append(screenLabel);
|
|
1371
|
+
}
|
|
1372
|
+
cardButton.append(title);
|
|
1373
|
+
relatedDisclosure.append(relatedButton, relatedContentWrap);
|
|
1374
|
+
card.append(cardButton, relatedDisclosure);
|
|
1375
|
+
if (entry.atoms.length > 0) {
|
|
1376
|
+
const atomSection = document.createElement("div");
|
|
1377
|
+
atomSection.style.display = "grid";
|
|
1378
|
+
atomSection.style.gap = "8px";
|
|
1379
|
+
const atomHeading = document.createElement("p");
|
|
1380
|
+
atomHeading.textContent = "関連イベント詳細";
|
|
1381
|
+
atomHeading.style.margin = "0";
|
|
1382
|
+
atomHeading.style.fontSize = "11px";
|
|
1383
|
+
atomHeading.style.fontWeight = "700";
|
|
1384
|
+
atomHeading.style.letterSpacing = "0.08em";
|
|
1385
|
+
atomHeading.style.textTransform = "uppercase";
|
|
1386
|
+
atomHeading.style.color = "#777169";
|
|
1387
|
+
atomSection.append(atomHeading);
|
|
1388
|
+
for (const atom of entry.atoms) {
|
|
1389
|
+
const atomRow = document.createElement("div");
|
|
1390
|
+
atomRow.style.display = "grid";
|
|
1391
|
+
atomRow.style.gap = "4px";
|
|
1392
|
+
atomRow.style.padding = "10px 12px";
|
|
1393
|
+
atomRow.style.borderRadius = "12px";
|
|
1394
|
+
applyGlassSurfaceStyles(atomRow, "soft");
|
|
1395
|
+
const atomTop = document.createElement("div");
|
|
1396
|
+
atomTop.style.display = "flex";
|
|
1397
|
+
atomTop.style.alignItems = "center";
|
|
1398
|
+
atomTop.style.justifyContent = "space-between";
|
|
1399
|
+
atomTop.style.gap = "12px";
|
|
1400
|
+
const atomTitle = document.createElement("p");
|
|
1401
|
+
atomTitle.textContent = atom.title;
|
|
1402
|
+
atomTitle.style.margin = "0";
|
|
1403
|
+
atomTitle.style.fontSize = "12px";
|
|
1404
|
+
atomTitle.style.fontWeight = "600";
|
|
1405
|
+
atomTitle.style.color = "#111827";
|
|
1406
|
+
const atomTime = document.createElement("span");
|
|
1407
|
+
atomTime.textContent = atom.occurredAtLabel;
|
|
1408
|
+
atomTime.style.fontSize = "11px";
|
|
1409
|
+
atomTime.style.color = "#777169";
|
|
1410
|
+
const atomMeta = document.createElement("p");
|
|
1411
|
+
atomMeta.textContent = atom.detailLabel ?? atom.subtitle ?? atom.badgeLabel;
|
|
1412
|
+
atomMeta.style.margin = "0";
|
|
1413
|
+
atomMeta.style.fontSize = "11px";
|
|
1414
|
+
atomMeta.style.lineHeight = "1.5";
|
|
1415
|
+
atomMeta.style.color = "#4e4e4e";
|
|
1416
|
+
atomMeta.style.wordBreak = "break-word";
|
|
1417
|
+
atomTop.append(atomTitle, atomTime);
|
|
1418
|
+
atomRow.append(atomTop, atomMeta);
|
|
1419
|
+
atomSection.append(atomRow);
|
|
1420
|
+
}
|
|
1421
|
+
evidence.append(atomSection);
|
|
1422
|
+
}
|
|
1423
|
+
relatedContentWrap.append(evidence);
|
|
1424
|
+
let isExpanded = options.expanded;
|
|
1425
|
+
const setExpanded = (nextExpanded, immediate = false) => {
|
|
1426
|
+
isExpanded = nextExpanded;
|
|
1427
|
+
relatedChevron.replaceWith(createLucideIcon(nextExpanded ? ChevronUp : ChevronDown, 16));
|
|
1428
|
+
const nextChevron = relatedMeta.querySelector("svg");
|
|
1429
|
+
if (nextChevron instanceof SVGElement) {
|
|
1430
|
+
nextChevron.style.display = "block";
|
|
1431
|
+
nextChevron.style.color = "rgba(119,113,105,0.7)";
|
|
1432
|
+
}
|
|
1433
|
+
if (immediate) {
|
|
1434
|
+
relatedContentWrap.style.transition = "none";
|
|
1435
|
+
}
|
|
1436
|
+
else {
|
|
1437
|
+
relatedContentWrap.style.transition =
|
|
1438
|
+
"max-height 280ms cubic-bezier(0.22, 1, 0.36, 1), opacity 180ms ease";
|
|
1439
|
+
}
|
|
1440
|
+
if (nextExpanded) {
|
|
1441
|
+
relatedContentWrap.style.display = "grid";
|
|
1442
|
+
relatedContentWrap.style.maxHeight = "0px";
|
|
1443
|
+
relatedContentWrap.style.opacity = "0";
|
|
1444
|
+
window.requestAnimationFrame(() => {
|
|
1445
|
+
relatedContentWrap.style.maxHeight = `${evidence.scrollHeight}px`;
|
|
1446
|
+
relatedContentWrap.style.opacity = "1";
|
|
1447
|
+
});
|
|
1448
|
+
}
|
|
1449
|
+
else {
|
|
1450
|
+
const currentHeight = evidence.scrollHeight;
|
|
1451
|
+
relatedContentWrap.style.maxHeight = `${currentHeight}px`;
|
|
1452
|
+
relatedContentWrap.style.opacity = "1";
|
|
1453
|
+
window.requestAnimationFrame(() => {
|
|
1454
|
+
relatedContentWrap.style.maxHeight = "0px";
|
|
1455
|
+
relatedContentWrap.style.opacity = "0";
|
|
1456
|
+
});
|
|
1457
|
+
}
|
|
1458
|
+
if (immediate) {
|
|
1459
|
+
window.requestAnimationFrame(() => {
|
|
1460
|
+
relatedContentWrap.style.transition =
|
|
1461
|
+
"max-height 280ms cubic-bezier(0.22, 1, 0.36, 1), opacity 180ms ease";
|
|
1462
|
+
});
|
|
1463
|
+
}
|
|
1464
|
+
};
|
|
1465
|
+
relatedContentWrap.addEventListener("transitionend", (event) => {
|
|
1466
|
+
if (event.propertyName !== "max-height") {
|
|
1467
|
+
return;
|
|
1468
|
+
}
|
|
1469
|
+
if (isExpanded) {
|
|
1470
|
+
relatedContentWrap.style.maxHeight = "none";
|
|
1471
|
+
relatedContentWrap.style.opacity = "1";
|
|
1472
|
+
}
|
|
1473
|
+
});
|
|
1474
|
+
setExpanded(options.expanded, true);
|
|
1475
|
+
cardButton.addEventListener("click", () => {
|
|
1476
|
+
if (entry.pendingRemoval) {
|
|
1477
|
+
return;
|
|
1478
|
+
}
|
|
1479
|
+
options.onSelect();
|
|
1480
|
+
});
|
|
1481
|
+
relatedButton.addEventListener("click", () => {
|
|
1482
|
+
const nextExpanded = !isExpanded;
|
|
1483
|
+
setExpanded(nextExpanded);
|
|
1484
|
+
options.onToggleExpanded();
|
|
1485
|
+
});
|
|
1486
|
+
removeButton.addEventListener("click", (event) => {
|
|
1487
|
+
event.stopPropagation();
|
|
1488
|
+
options.onMarkPendingRemoval();
|
|
1489
|
+
});
|
|
1490
|
+
if (entry.pendingRemoval) {
|
|
1491
|
+
const pendingOverlay = document.createElement("div");
|
|
1492
|
+
pendingOverlay.dataset.clueAuthoringPendingRemoval = entry.id;
|
|
1493
|
+
pendingOverlay.style.position = "absolute";
|
|
1494
|
+
pendingOverlay.style.inset = "0";
|
|
1495
|
+
pendingOverlay.style.display = "grid";
|
|
1496
|
+
pendingOverlay.style.alignContent = "center";
|
|
1497
|
+
pendingOverlay.style.justifyItems = "center";
|
|
1498
|
+
pendingOverlay.style.gap = "10px";
|
|
1499
|
+
pendingOverlay.style.borderRadius = "14px";
|
|
1500
|
+
pendingOverlay.style.background = "rgba(255,253,249,0.46)";
|
|
1501
|
+
pendingOverlay.style.backdropFilter = "blur(6px)";
|
|
1502
|
+
pendingOverlay.style.setProperty("-webkit-backdrop-filter", "blur(6px)");
|
|
1503
|
+
pendingOverlay.style.pointerEvents = "auto";
|
|
1504
|
+
const pendingBadge = document.createElement("div");
|
|
1505
|
+
pendingBadge.textContent = "削除予定";
|
|
1506
|
+
pendingBadge.style.display = "inline-flex";
|
|
1507
|
+
pendingBadge.style.alignItems = "center";
|
|
1508
|
+
pendingBadge.style.justifyContent = "center";
|
|
1509
|
+
pendingBadge.style.padding = "7px 12px";
|
|
1510
|
+
pendingBadge.style.borderRadius = "9999px";
|
|
1511
|
+
pendingBadge.style.background = "rgba(255,255,255,0.78)";
|
|
1512
|
+
pendingBadge.style.border = "1px solid rgba(17,24,39,0.08)";
|
|
1513
|
+
pendingBadge.style.fontSize = "12px";
|
|
1514
|
+
pendingBadge.style.fontWeight = "700";
|
|
1515
|
+
pendingBadge.style.color = "#7a5540";
|
|
1516
|
+
const restoreButton = document.createElement("button");
|
|
1517
|
+
restoreButton.dataset.clueAuthoringRestoreAction = entry.id;
|
|
1518
|
+
restoreButton.type = "button";
|
|
1519
|
+
restoreButton.textContent = "元に戻す";
|
|
1520
|
+
restoreButton.style.display = "inline-flex";
|
|
1521
|
+
restoreButton.style.alignItems = "center";
|
|
1522
|
+
restoreButton.style.justifyContent = "center";
|
|
1523
|
+
restoreButton.style.padding = "8px 12px";
|
|
1524
|
+
restoreButton.style.borderRadius = "10px";
|
|
1525
|
+
restoreButton.style.border = "1px solid rgba(17,24,39,0.08)";
|
|
1526
|
+
restoreButton.style.background = "rgba(255,255,255,0.86)";
|
|
1527
|
+
restoreButton.style.fontSize = "12px";
|
|
1528
|
+
restoreButton.style.fontWeight = "600";
|
|
1529
|
+
restoreButton.style.color = "#111827";
|
|
1530
|
+
restoreButton.style.cursor = "pointer";
|
|
1531
|
+
restoreButton.addEventListener("click", (event) => {
|
|
1532
|
+
event.stopPropagation();
|
|
1533
|
+
options.onRestorePendingRemoval();
|
|
1534
|
+
});
|
|
1535
|
+
pendingOverlay.append(pendingBadge, restoreButton);
|
|
1536
|
+
card.append(pendingOverlay);
|
|
1537
|
+
}
|
|
1538
|
+
card.addEventListener("mouseenter", () => {
|
|
1539
|
+
if (entry.pendingRemoval) {
|
|
1540
|
+
return;
|
|
1541
|
+
}
|
|
1542
|
+
card.style.transform = "translateY(-1px)";
|
|
1543
|
+
});
|
|
1544
|
+
card.addEventListener("mouseleave", () => {
|
|
1545
|
+
card.style.transform = "translateY(0px)";
|
|
1546
|
+
});
|
|
1547
|
+
return card;
|
|
1548
|
+
}
|
|
1549
|
+
function createEntrySection(titleText, entries, options) {
|
|
1550
|
+
const section = document.createElement("section");
|
|
1551
|
+
section.style.display = "grid";
|
|
1552
|
+
section.style.gap = "10px";
|
|
1553
|
+
const heading = document.createElement("h3");
|
|
1554
|
+
heading.textContent = titleText;
|
|
1555
|
+
heading.style.margin = "0";
|
|
1556
|
+
heading.style.fontSize = "13px";
|
|
1557
|
+
heading.style.fontWeight = "700";
|
|
1558
|
+
heading.style.lineHeight = "1.4";
|
|
1559
|
+
heading.style.color = "#111827";
|
|
1560
|
+
const cards = document.createElement("div");
|
|
1561
|
+
cards.style.display = "grid";
|
|
1562
|
+
cards.style.gap = "10px";
|
|
1563
|
+
cards.append(...entries.map((entry) => createRecordedEventCard(entry, {
|
|
1564
|
+
expanded: options.isExpanded(entry.id),
|
|
1565
|
+
onSelect: () => options.onSelect(entry.id),
|
|
1566
|
+
onToggleExpanded: () => options.onToggleExpanded(entry.id),
|
|
1567
|
+
onMarkPendingRemoval: () => options.onMarkPendingRemoval(entry.id),
|
|
1568
|
+
onRestorePendingRemoval: () => options.onRestorePendingRemoval(entry.id),
|
|
1569
|
+
})));
|
|
1570
|
+
section.append(heading, cards);
|
|
1571
|
+
return section;
|
|
1572
|
+
}
|
|
1573
|
+
function createPanel(session, recording, options) {
|
|
1574
|
+
const targetSpotlight = createTargetSpotlight();
|
|
1575
|
+
const panelWrap = document.createElement("div");
|
|
1576
|
+
panelWrap.style.position = "absolute";
|
|
1577
|
+
panelWrap.style.left = "0px";
|
|
1578
|
+
panelWrap.style.top = "0px";
|
|
1579
|
+
panelWrap.style.boxSizing = "border-box";
|
|
1580
|
+
panelWrap.style.width = "0";
|
|
1581
|
+
panelWrap.style.height = `${TOOLBAR_PANEL_HEIGHT}px`;
|
|
1582
|
+
panelWrap.style.opacity = "0";
|
|
1583
|
+
panelWrap.style.transform = "translateX(-26px) scale(0.96)";
|
|
1584
|
+
panelWrap.style.transformOrigin = "left center";
|
|
1585
|
+
panelWrap.style.overflow = "hidden";
|
|
1586
|
+
panelWrap.style.borderRadius = "16px";
|
|
1587
|
+
panelWrap.style.boxShadow = "rgba(17,24,39,0.08) 0px 16px 40px";
|
|
1588
|
+
panelWrap.style.zIndex = "10";
|
|
1589
|
+
panelWrap.style.transition =
|
|
1590
|
+
"width 320ms cubic-bezier(0.22, 1, 0.36, 1), opacity 240ms ease, transform 320ms cubic-bezier(0.22, 1, 0.36, 1)";
|
|
1591
|
+
panelWrap.style.pointerEvents = "none";
|
|
1592
|
+
panelWrap.style.willChange = "width, transform, opacity";
|
|
1593
|
+
applyGlassSurfaceStyles(panelWrap, "strong");
|
|
1594
|
+
const panel = document.createElement("div");
|
|
1595
|
+
panel.dataset.clueAuthoringPanel = "true";
|
|
1596
|
+
panel.style.boxSizing = "border-box";
|
|
1597
|
+
panel.style.width = `${TOOLBAR_PANEL_WIDTH}px`;
|
|
1598
|
+
panel.style.minWidth = `${TOOLBAR_PANEL_WIDTH}px`;
|
|
1599
|
+
panel.style.height = "100%";
|
|
1600
|
+
panel.style.padding = "10px";
|
|
1601
|
+
panel.style.borderRadius = "16px";
|
|
1602
|
+
panel.style.display = "grid";
|
|
1603
|
+
panel.style.gridTemplateRows = "auto 1fr";
|
|
1604
|
+
panel.style.gap = "12px";
|
|
1605
|
+
panel.style.pointerEvents = "auto";
|
|
1606
|
+
panel.style.background = "transparent";
|
|
1607
|
+
panel.style.border = "none";
|
|
1608
|
+
panel.style.backdropFilter = "none";
|
|
1609
|
+
panel.style.setProperty("-webkit-backdrop-filter", "none");
|
|
1610
|
+
const panelHeader = document.createElement("div");
|
|
1611
|
+
panelHeader.style.display = "grid";
|
|
1612
|
+
panelHeader.style.gap = "0";
|
|
1613
|
+
const eventList = document.createElement("div");
|
|
1614
|
+
eventList.dataset.clueAuthoringEventList = "true";
|
|
1615
|
+
eventList.style.display = "grid";
|
|
1616
|
+
eventList.style.width = "100%";
|
|
1617
|
+
eventList.style.minWidth = "0";
|
|
1618
|
+
eventList.style.alignContent = "start";
|
|
1619
|
+
eventList.style.gap = "10px";
|
|
1620
|
+
eventList.style.padding = "0";
|
|
1621
|
+
eventList.style.minHeight = "0";
|
|
1622
|
+
eventList.style.background = "rgba(255,255,255,0.08)";
|
|
1623
|
+
eventList.style.border = "1px solid rgba(255,255,255,0.14)";
|
|
1624
|
+
eventList.style.borderRadius = "14px";
|
|
1625
|
+
eventList.style.overflowY = "auto";
|
|
1626
|
+
const statusRow = document.createElement("div");
|
|
1627
|
+
statusRow.style.display = "flex";
|
|
1628
|
+
statusRow.style.alignItems = "center";
|
|
1629
|
+
statusRow.style.justifyContent = "space-between";
|
|
1630
|
+
statusRow.style.gap = "12px";
|
|
1631
|
+
statusRow.style.padding = "4px 4px 12px";
|
|
1632
|
+
statusRow.style.borderBottom = "1px solid rgba(17,24,39,0.08)";
|
|
1633
|
+
const statusPill = document.createElement("span");
|
|
1634
|
+
statusPill.style.display = "inline-flex";
|
|
1635
|
+
statusPill.style.alignItems = "center";
|
|
1636
|
+
statusPill.style.padding = "6px 10px";
|
|
1637
|
+
statusPill.style.borderRadius = "9999px";
|
|
1638
|
+
statusPill.style.fontSize = "10px";
|
|
1639
|
+
statusPill.style.fontWeight = "600";
|
|
1640
|
+
statusPill.style.letterSpacing = "0.22em";
|
|
1641
|
+
statusPill.style.textTransform = "uppercase";
|
|
1642
|
+
applyGlassSurfaceStyles(statusPill, "soft");
|
|
1643
|
+
const statusSummary = document.createElement("p");
|
|
1644
|
+
statusSummary.style.margin = "0";
|
|
1645
|
+
statusSummary.style.fontSize = "11px";
|
|
1646
|
+
statusSummary.style.color = "#777169";
|
|
1647
|
+
statusSummary.style.flexShrink = "0";
|
|
1648
|
+
const headerActions = document.createElement("div");
|
|
1649
|
+
headerActions.style.display = "inline-flex";
|
|
1650
|
+
headerActions.style.alignItems = "center";
|
|
1651
|
+
headerActions.style.gap = "8px";
|
|
1652
|
+
const resetButton = document.createElement("button");
|
|
1653
|
+
resetButton.type = "button";
|
|
1654
|
+
resetButton.dataset.clueAuthoringResetButton = "true";
|
|
1655
|
+
resetButton.textContent = "リセット";
|
|
1656
|
+
resetButton.style.display = "inline-flex";
|
|
1657
|
+
resetButton.style.alignItems = "center";
|
|
1658
|
+
resetButton.style.justifyContent = "center";
|
|
1659
|
+
resetButton.style.padding = "6px 10px";
|
|
1660
|
+
resetButton.style.borderRadius = "9999px";
|
|
1661
|
+
resetButton.style.border = "1px solid rgba(255,255,255,0.2)";
|
|
1662
|
+
resetButton.style.background = "rgba(255,255,255,0.18)";
|
|
1663
|
+
resetButton.style.fontSize = "11px";
|
|
1664
|
+
resetButton.style.fontWeight = "600";
|
|
1665
|
+
resetButton.style.letterSpacing = "0.02em";
|
|
1666
|
+
resetButton.style.color = "#777169";
|
|
1667
|
+
resetButton.style.cursor = "pointer";
|
|
1668
|
+
resetButton.style.backdropFilter = AUTHORING_GLASS_BLUR;
|
|
1669
|
+
resetButton.style.setProperty("-webkit-backdrop-filter", AUTHORING_GLASS_BLUR);
|
|
1670
|
+
resetButton.addEventListener("click", () => {
|
|
1671
|
+
void options.onResetRequested();
|
|
1672
|
+
});
|
|
1673
|
+
const emptyState = document.createElement("div");
|
|
1674
|
+
emptyState.style.minHeight = "260px";
|
|
1675
|
+
emptyState.style.borderRadius = "0";
|
|
1676
|
+
emptyState.style.border = "none";
|
|
1677
|
+
emptyState.style.background = "transparent";
|
|
1678
|
+
emptyState.style.display = "grid";
|
|
1679
|
+
emptyState.style.alignContent = "center";
|
|
1680
|
+
emptyState.style.justifyItems = "center";
|
|
1681
|
+
emptyState.style.gap = "12px";
|
|
1682
|
+
emptyState.style.padding = "12px 12px 18px";
|
|
1683
|
+
emptyState.style.textAlign = "center";
|
|
1684
|
+
const emptyIconWrap = document.createElement("div");
|
|
1685
|
+
emptyIconWrap.style.display = "grid";
|
|
1686
|
+
emptyIconWrap.style.placeItems = "center";
|
|
1687
|
+
emptyIconWrap.style.width = "44px";
|
|
1688
|
+
emptyIconWrap.style.height = "44px";
|
|
1689
|
+
emptyIconWrap.style.borderRadius = "12px";
|
|
1690
|
+
emptyIconWrap.style.boxShadow = "rgba(17,24,39,0.05) 0px 6px 16px";
|
|
1691
|
+
emptyIconWrap.style.color = "#777169";
|
|
1692
|
+
applyGlassSurfaceStyles(emptyIconWrap, "soft");
|
|
1693
|
+
emptyIconWrap.append(createLucideIcon(AUTHORING_ACTIONS[0]?.icon ?? [], 22));
|
|
1694
|
+
const emptyCopy = document.createElement("p");
|
|
1695
|
+
emptyCopy.style.margin = "0";
|
|
1696
|
+
emptyCopy.style.fontSize = "13px";
|
|
1697
|
+
emptyCopy.style.lineHeight = "1.6";
|
|
1698
|
+
emptyCopy.style.letterSpacing = "0.12px";
|
|
1699
|
+
emptyCopy.style.color = "#777169";
|
|
1700
|
+
const entriesList = document.createElement("div");
|
|
1701
|
+
entriesList.style.display = "grid";
|
|
1702
|
+
entriesList.style.width = "100%";
|
|
1703
|
+
entriesList.style.minWidth = "0";
|
|
1704
|
+
entriesList.style.gap = "18px";
|
|
1705
|
+
emptyState.append(emptyIconWrap, emptyCopy);
|
|
1706
|
+
headerActions.append(statusSummary, resetButton);
|
|
1707
|
+
statusRow.append(statusPill, headerActions);
|
|
1708
|
+
panelHeader.append(statusRow);
|
|
1709
|
+
eventList.append(emptyState, entriesList);
|
|
1710
|
+
panel.append(panelHeader, eventList);
|
|
1711
|
+
panelWrap.append(panel);
|
|
1712
|
+
let previousEntryCount = 0;
|
|
1713
|
+
let lastSnapshot = null;
|
|
1714
|
+
let spotlightEventId = null;
|
|
1715
|
+
const expandedEventIds = new Set();
|
|
1716
|
+
const renderSnapshot = (snapshot) => {
|
|
1717
|
+
lastSnapshot = snapshot;
|
|
1718
|
+
statusPill.textContent = snapshot.hasRecordedWindow
|
|
1719
|
+
? snapshot.isRecording
|
|
1720
|
+
? "Recording"
|
|
1721
|
+
: "Paused"
|
|
1722
|
+
: "Ready";
|
|
1723
|
+
statusPill.style.background = snapshot.hasRecordedWindow
|
|
1724
|
+
? snapshot.isRecording
|
|
1725
|
+
? "rgba(181,129,79,0.18)"
|
|
1726
|
+
: "rgba(245,242,239,0.5)"
|
|
1727
|
+
: "rgba(245,242,239,0.5)";
|
|
1728
|
+
statusPill.style.color =
|
|
1729
|
+
snapshot.hasRecordedWindow && snapshot.isRecording
|
|
1730
|
+
? "#8f5b2f"
|
|
1731
|
+
: "#777169";
|
|
1732
|
+
statusSummary.textContent = `${snapshot.entries.length}件記録`;
|
|
1733
|
+
emptyCopy.textContent = !snapshot.hasRecordedWindow
|
|
1734
|
+
? "イベントを記録するを押すと、その時点以降の action unit をここに表示します"
|
|
1735
|
+
: snapshot.isRecording
|
|
1736
|
+
? "このまま操作を続けると、記録開始以降の action unit が時系列でここに追加されます"
|
|
1737
|
+
: "記録は一時停止中です。再開すると、その時点以降の action unit がここに追加されます";
|
|
1738
|
+
const shouldAutoScroll = snapshot.entries.length > previousEntryCount &&
|
|
1739
|
+
eventList.scrollHeight - eventList.scrollTop - eventList.clientHeight < 120;
|
|
1740
|
+
const nextEntryIds = new Set(snapshot.entries.map((entry) => entry.id));
|
|
1741
|
+
for (const expandedId of expandedEventIds) {
|
|
1742
|
+
if (!nextEntryIds.has(expandedId)) {
|
|
1743
|
+
expandedEventIds.delete(expandedId);
|
|
1744
|
+
}
|
|
1745
|
+
}
|
|
1746
|
+
if (spotlightEventId && !nextEntryIds.has(spotlightEventId)) {
|
|
1747
|
+
spotlightEventId = null;
|
|
1748
|
+
targetSpotlight.clear();
|
|
1749
|
+
}
|
|
1750
|
+
if (spotlightEventId) {
|
|
1751
|
+
const spotlightEntry = snapshot.entries.find((entry) => entry.id === spotlightEventId) ?? null;
|
|
1752
|
+
if (!spotlightEntry?.selected) {
|
|
1753
|
+
spotlightEventId = null;
|
|
1754
|
+
targetSpotlight.clear();
|
|
1755
|
+
}
|
|
1756
|
+
}
|
|
1757
|
+
const toggleSelected = (eventId) => {
|
|
1758
|
+
const targetEntry = lastSnapshot?.entries.find((candidate) => candidate.id === eventId) ?? null;
|
|
1759
|
+
if (targetEntry?.pendingRemoval) {
|
|
1760
|
+
return;
|
|
1761
|
+
}
|
|
1762
|
+
const isCurrentlySelected = targetEntry?.selected ?? false;
|
|
1763
|
+
if (isCurrentlySelected) {
|
|
1764
|
+
recording.toggleSelected(eventId);
|
|
1765
|
+
if (spotlightEventId === eventId) {
|
|
1766
|
+
spotlightEventId = null;
|
|
1767
|
+
targetSpotlight.clear();
|
|
1768
|
+
}
|
|
1769
|
+
recording.setHighlightTrace(eventId, null);
|
|
1770
|
+
}
|
|
1771
|
+
else if (targetEntry) {
|
|
1772
|
+
recording.toggleSelected(eventId);
|
|
1773
|
+
if (spotlightEventId && spotlightEventId !== eventId) {
|
|
1774
|
+
recording.setHighlightTrace(spotlightEventId, null);
|
|
1775
|
+
}
|
|
1776
|
+
spotlightEventId = eventId;
|
|
1777
|
+
const trace = targetSpotlight.focusEntry(targetEntry);
|
|
1778
|
+
recording.setHighlightTrace(eventId, trace);
|
|
1779
|
+
}
|
|
1780
|
+
};
|
|
1781
|
+
const toggleExpanded = (eventId) => {
|
|
1782
|
+
if (expandedEventIds.has(eventId)) {
|
|
1783
|
+
expandedEventIds.delete(eventId);
|
|
1784
|
+
}
|
|
1785
|
+
else {
|
|
1786
|
+
expandedEventIds.add(eventId);
|
|
1787
|
+
}
|
|
1788
|
+
if (lastSnapshot) {
|
|
1789
|
+
renderSnapshot(lastSnapshot);
|
|
1790
|
+
}
|
|
1791
|
+
};
|
|
1792
|
+
const markPendingRemoval = (eventId) => {
|
|
1793
|
+
if (spotlightEventId === eventId) {
|
|
1794
|
+
recording.setHighlightTrace(eventId, null);
|
|
1795
|
+
spotlightEventId = null;
|
|
1796
|
+
targetSpotlight.clear();
|
|
1797
|
+
}
|
|
1798
|
+
recording.markPendingRemoval(eventId);
|
|
1799
|
+
};
|
|
1800
|
+
const restorePendingRemoval = (eventId) => {
|
|
1801
|
+
recording.restorePendingRemoval(eventId);
|
|
1802
|
+
};
|
|
1803
|
+
const groupedSections = [];
|
|
1804
|
+
for (const entry of snapshot.entries) {
|
|
1805
|
+
const sectionTitle = entry.screenLabel ?? "イベント";
|
|
1806
|
+
const previousSection = groupedSections[groupedSections.length - 1] ?? null;
|
|
1807
|
+
if (previousSection && previousSection.title === sectionTitle) {
|
|
1808
|
+
previousSection.entries.push(entry);
|
|
1809
|
+
continue;
|
|
1810
|
+
}
|
|
1811
|
+
groupedSections.push({
|
|
1812
|
+
title: sectionTitle,
|
|
1813
|
+
entries: [entry],
|
|
1814
|
+
});
|
|
1815
|
+
}
|
|
1816
|
+
const sectionNodes = groupedSections.map((section) => createEntrySection(section.title, section.entries, {
|
|
1817
|
+
isExpanded: (eventId) => expandedEventIds.has(eventId),
|
|
1818
|
+
onSelect: toggleSelected,
|
|
1819
|
+
onToggleExpanded: toggleExpanded,
|
|
1820
|
+
onMarkPendingRemoval: markPendingRemoval,
|
|
1821
|
+
onRestorePendingRemoval: restorePendingRemoval,
|
|
1822
|
+
}));
|
|
1823
|
+
entriesList.replaceChildren(...sectionNodes);
|
|
1824
|
+
emptyState.style.display = snapshot.entries.length === 0 ? "grid" : "none";
|
|
1825
|
+
previousEntryCount = snapshot.entries.length;
|
|
1826
|
+
if (shouldAutoScroll) {
|
|
1827
|
+
window.requestAnimationFrame(() => {
|
|
1828
|
+
eventList.scrollTop = eventList.scrollHeight;
|
|
1829
|
+
});
|
|
1830
|
+
}
|
|
1831
|
+
};
|
|
1832
|
+
const unsubscribe = recording.subscribe(renderSnapshot);
|
|
1833
|
+
return {
|
|
1834
|
+
panelWrap,
|
|
1835
|
+
dispose: () => {
|
|
1836
|
+
unsubscribe();
|
|
1837
|
+
targetSpotlight.dispose();
|
|
1838
|
+
},
|
|
1839
|
+
};
|
|
1840
|
+
}
|
|
1841
|
+
function createBaseButton() {
|
|
1842
|
+
const logoSlotSize = Math.round((TOOLBAR_BASE_BUTTON_SIZE * 2) / 3);
|
|
1843
|
+
const logoSize = Math.round(TOOLBAR_BASE_BUTTON_SIZE * 0.53);
|
|
1844
|
+
const baseButton = document.createElement("button");
|
|
1845
|
+
baseButton.type = "button";
|
|
1846
|
+
baseButton.dataset.clueAuthoringBaseButton = "true";
|
|
1847
|
+
baseButton.setAttribute("aria-label", "Authoring toolbar");
|
|
1848
|
+
baseButton.setAttribute("aria-expanded", "false");
|
|
1849
|
+
baseButton.style.position = "absolute";
|
|
1850
|
+
baseButton.style.left = `${TOOLBAR_BASE_BUTTON_EDGE_OFFSET}px`;
|
|
1851
|
+
baseButton.style.top = `${TOOLBAR_BASE_BUTTON_EDGE_OFFSET}px`;
|
|
1852
|
+
baseButton.style.width = `${TOOLBAR_BASE_BUTTON_SIZE}px`;
|
|
1853
|
+
baseButton.style.height = `${TOOLBAR_BASE_BUTTON_SIZE}px`;
|
|
1854
|
+
baseButton.style.marginLeft = "0px";
|
|
1855
|
+
baseButton.style.marginTop = "0px";
|
|
1856
|
+
baseButton.style.display = "grid";
|
|
1857
|
+
baseButton.style.placeItems = "center";
|
|
1858
|
+
baseButton.style.borderRadius = `${Math.round(TOOLBAR_BASE_BUTTON_SIZE * 0.31)}px`;
|
|
1859
|
+
baseButton.style.boxShadow =
|
|
1860
|
+
"rgba(17,24,39,0.1) 0px 12px 28px, rgba(17,24,39,0.08) 0px 0px 0px 1px";
|
|
1861
|
+
baseButton.style.color = "#050505";
|
|
1862
|
+
baseButton.style.cursor = "grab";
|
|
1863
|
+
baseButton.style.pointerEvents = "auto";
|
|
1864
|
+
baseButton.style.transition =
|
|
1865
|
+
"transform 260ms cubic-bezier(0.22, 1, 0.36, 1), box-shadow 240ms ease";
|
|
1866
|
+
baseButton.style.touchAction = "none";
|
|
1867
|
+
applyGlassSurfaceStyles(baseButton, "strong");
|
|
1868
|
+
const logoSlot = document.createElement("div");
|
|
1869
|
+
logoSlot.style.display = "grid";
|
|
1870
|
+
logoSlot.style.placeItems = "center";
|
|
1871
|
+
logoSlot.style.width = `${logoSlotSize}px`;
|
|
1872
|
+
logoSlot.style.height = `${logoSlotSize}px`;
|
|
1873
|
+
logoSlot.style.justifySelf = "center";
|
|
1874
|
+
logoSlot.style.alignSelf = "center";
|
|
1875
|
+
logoSlot.style.borderRadius = `${Math.round(logoSlotSize * 0.4)}px`;
|
|
1876
|
+
applyGlassSurfaceStyles(logoSlot);
|
|
1877
|
+
logoSlot.append(createServiceLogo(logoSize));
|
|
1878
|
+
baseButton.append(logoSlot);
|
|
1879
|
+
return baseButton;
|
|
1880
|
+
}
|
|
1881
|
+
function createSaveDialog(options) {
|
|
1882
|
+
const backdrop = document.createElement("div");
|
|
1883
|
+
backdrop.dataset.clueAuthoringSaveDialog = "true";
|
|
1884
|
+
markAuthoringRoot(backdrop);
|
|
1885
|
+
backdrop.style.position = "fixed";
|
|
1886
|
+
backdrop.style.inset = "0";
|
|
1887
|
+
backdrop.style.display = "none";
|
|
1888
|
+
backdrop.style.placeItems = "center";
|
|
1889
|
+
backdrop.style.padding = "24px";
|
|
1890
|
+
backdrop.style.background = "rgba(255,251,247,0.24)";
|
|
1891
|
+
backdrop.style.backdropFilter = "blur(8px)";
|
|
1892
|
+
backdrop.style.setProperty("-webkit-backdrop-filter", "blur(8px)");
|
|
1893
|
+
backdrop.style.zIndex = "2147483400";
|
|
1894
|
+
const dialog = document.createElement("div");
|
|
1895
|
+
markAuthoringRoot(dialog);
|
|
1896
|
+
dialog.style.width = "min(560px, 100%)";
|
|
1897
|
+
dialog.style.maxHeight = "min(680px, calc(100vh - 48px))";
|
|
1898
|
+
dialog.style.display = "grid";
|
|
1899
|
+
dialog.style.gridTemplateRows = "auto auto auto 1fr auto";
|
|
1900
|
+
dialog.style.gap = "14px";
|
|
1901
|
+
dialog.style.padding = "18px";
|
|
1902
|
+
dialog.style.borderRadius = "20px";
|
|
1903
|
+
dialog.style.overflow = "hidden";
|
|
1904
|
+
dialog.style.boxShadow = "rgba(17,24,39,0.12) 0px 22px 48px";
|
|
1905
|
+
dialog.style.color = "#111827";
|
|
1906
|
+
applyGlassSurfaceStyles(dialog, "strong");
|
|
1907
|
+
const heading = document.createElement("div");
|
|
1908
|
+
heading.style.display = "grid";
|
|
1909
|
+
heading.style.gap = "4px";
|
|
1910
|
+
const title = document.createElement("h3");
|
|
1911
|
+
title.textContent = "Business Event を保存";
|
|
1912
|
+
title.style.margin = "0";
|
|
1913
|
+
title.style.fontSize = "18px";
|
|
1914
|
+
title.style.fontWeight = "700";
|
|
1915
|
+
title.style.lineHeight = "1.4";
|
|
1916
|
+
const description = document.createElement("p");
|
|
1917
|
+
description.textContent = "保存対象の action_unit を確認して、下書きまたは公開を選択します。";
|
|
1918
|
+
description.style.margin = "0";
|
|
1919
|
+
description.style.fontSize = "12px";
|
|
1920
|
+
description.style.lineHeight = "1.6";
|
|
1921
|
+
description.style.color = "#777169";
|
|
1922
|
+
heading.append(title, description);
|
|
1923
|
+
const titleInput = document.createElement("input");
|
|
1924
|
+
titleInput.dataset.clueAuthoringSaveTitle = "true";
|
|
1925
|
+
titleInput.type = "text";
|
|
1926
|
+
titleInput.placeholder = "Business Event タイトル";
|
|
1927
|
+
titleInput.style.width = "100%";
|
|
1928
|
+
titleInput.style.borderRadius = "12px";
|
|
1929
|
+
titleInput.style.border = "1px solid rgba(17,24,39,0.08)";
|
|
1930
|
+
titleInput.style.padding = "10px 12px";
|
|
1931
|
+
titleInput.style.background = "rgba(255,255,255,0.48)";
|
|
1932
|
+
titleInput.style.fontSize = "14px";
|
|
1933
|
+
titleInput.style.color = "#111827";
|
|
1934
|
+
const descriptionInput = document.createElement("textarea");
|
|
1935
|
+
descriptionInput.dataset.clueAuthoringSaveDescription = "true";
|
|
1936
|
+
descriptionInput.placeholder = "Business Event 説明";
|
|
1937
|
+
descriptionInput.rows = 3;
|
|
1938
|
+
descriptionInput.style.width = "100%";
|
|
1939
|
+
descriptionInput.style.resize = "vertical";
|
|
1940
|
+
descriptionInput.style.borderRadius = "12px";
|
|
1941
|
+
descriptionInput.style.border = "1px solid rgba(17,24,39,0.08)";
|
|
1942
|
+
descriptionInput.style.padding = "10px 12px";
|
|
1943
|
+
descriptionInput.style.background = "rgba(255,255,255,0.42)";
|
|
1944
|
+
descriptionInput.style.fontSize = "13px";
|
|
1945
|
+
descriptionInput.style.color = "#111827";
|
|
1946
|
+
const modeRow = document.createElement("div");
|
|
1947
|
+
modeRow.style.display = "inline-flex";
|
|
1948
|
+
modeRow.style.alignItems = "center";
|
|
1949
|
+
modeRow.style.gap = "10px";
|
|
1950
|
+
const draftButton = document.createElement("button");
|
|
1951
|
+
draftButton.dataset.clueAuthoringSaveMode = "draft";
|
|
1952
|
+
draftButton.type = "button";
|
|
1953
|
+
draftButton.textContent = "下書き";
|
|
1954
|
+
draftButton.style.padding = "8px 12px";
|
|
1955
|
+
draftButton.style.borderRadius = "9999px";
|
|
1956
|
+
draftButton.style.border = "1px solid rgba(17,24,39,0.08)";
|
|
1957
|
+
draftButton.style.background = "rgba(255,255,255,0.56)";
|
|
1958
|
+
draftButton.style.fontSize = "12px";
|
|
1959
|
+
draftButton.style.fontWeight = "600";
|
|
1960
|
+
draftButton.style.cursor = "pointer";
|
|
1961
|
+
const publishButton = document.createElement("button");
|
|
1962
|
+
publishButton.dataset.clueAuthoringSaveMode = "publish";
|
|
1963
|
+
publishButton.type = "button";
|
|
1964
|
+
publishButton.textContent = "公開";
|
|
1965
|
+
publishButton.style.padding = "8px 12px";
|
|
1966
|
+
publishButton.style.borderRadius = "9999px";
|
|
1967
|
+
publishButton.style.border = "1px solid rgba(17,24,39,0.08)";
|
|
1968
|
+
publishButton.style.background = "rgba(255,255,255,0.32)";
|
|
1969
|
+
publishButton.style.fontSize = "12px";
|
|
1970
|
+
publishButton.style.fontWeight = "600";
|
|
1971
|
+
publishButton.style.cursor = "pointer";
|
|
1972
|
+
const listWrap = document.createElement("div");
|
|
1973
|
+
listWrap.dataset.clueAuthoringSaveSelectionList = "true";
|
|
1974
|
+
listWrap.style.display = "grid";
|
|
1975
|
+
listWrap.style.gap = "10px";
|
|
1976
|
+
listWrap.style.minHeight = "120px";
|
|
1977
|
+
listWrap.style.maxHeight = "240px";
|
|
1978
|
+
listWrap.style.overflow = "auto";
|
|
1979
|
+
listWrap.style.padding = "4px";
|
|
1980
|
+
const validation = document.createElement("p");
|
|
1981
|
+
validation.style.margin = "0";
|
|
1982
|
+
validation.style.fontSize = "12px";
|
|
1983
|
+
validation.style.color = "#8d3d2e";
|
|
1984
|
+
const footer = document.createElement("div");
|
|
1985
|
+
footer.style.display = "flex";
|
|
1986
|
+
footer.style.justifyContent = "flex-end";
|
|
1987
|
+
footer.style.gap = "10px";
|
|
1988
|
+
const cancelButton = document.createElement("button");
|
|
1989
|
+
cancelButton.dataset.clueAuthoringSaveCancel = "true";
|
|
1990
|
+
cancelButton.type = "button";
|
|
1991
|
+
cancelButton.textContent = "Cancel";
|
|
1992
|
+
cancelButton.style.padding = "9px 12px";
|
|
1993
|
+
cancelButton.style.borderRadius = "10px";
|
|
1994
|
+
cancelButton.style.border = "1px solid rgba(17,24,39,0.08)";
|
|
1995
|
+
cancelButton.style.background = "rgba(255,255,255,0.54)";
|
|
1996
|
+
cancelButton.style.fontSize = "12px";
|
|
1997
|
+
cancelButton.style.fontWeight = "600";
|
|
1998
|
+
cancelButton.style.cursor = "pointer";
|
|
1999
|
+
const confirmButton = document.createElement("button");
|
|
2000
|
+
confirmButton.dataset.clueAuthoringSaveConfirm = "true";
|
|
2001
|
+
confirmButton.type = "button";
|
|
2002
|
+
confirmButton.textContent = "保存";
|
|
2003
|
+
confirmButton.style.padding = "9px 14px";
|
|
2004
|
+
confirmButton.style.borderRadius = "10px";
|
|
2005
|
+
confirmButton.style.border = "1px solid rgba(181,129,79,0.14)";
|
|
2006
|
+
confirmButton.style.background = "rgba(181,129,79,0.16)";
|
|
2007
|
+
confirmButton.style.fontSize = "12px";
|
|
2008
|
+
confirmButton.style.fontWeight = "700";
|
|
2009
|
+
confirmButton.style.color = "#8f5b2f";
|
|
2010
|
+
confirmButton.style.cursor = "pointer";
|
|
2011
|
+
let saveMode = "draft";
|
|
2012
|
+
const resetForm = () => {
|
|
2013
|
+
titleInput.value = "";
|
|
2014
|
+
descriptionInput.value = "";
|
|
2015
|
+
saveMode = "draft";
|
|
2016
|
+
syncSaveMode();
|
|
2017
|
+
};
|
|
2018
|
+
const syncSaveMode = () => {
|
|
2019
|
+
draftButton.style.background =
|
|
2020
|
+
saveMode === "draft" ? "rgba(181,129,79,0.16)" : "rgba(255,255,255,0.56)";
|
|
2021
|
+
draftButton.style.color = saveMode === "draft" ? "#8f5b2f" : "#111827";
|
|
2022
|
+
publishButton.style.background =
|
|
2023
|
+
saveMode === "publish"
|
|
2024
|
+
? "rgba(181,129,79,0.16)"
|
|
2025
|
+
: "rgba(255,255,255,0.32)";
|
|
2026
|
+
publishButton.style.color = saveMode === "publish" ? "#8f5b2f" : "#111827";
|
|
2027
|
+
confirmButton.textContent = saveMode === "publish" ? "公開する" : "下書き保存";
|
|
2028
|
+
};
|
|
2029
|
+
const renderSelectionList = () => {
|
|
2030
|
+
const snapshot = options.recording.getSnapshot();
|
|
2031
|
+
const activeEntries = snapshot.entries.filter((entry) => !entry.pendingRemoval);
|
|
2032
|
+
listWrap.replaceChildren(...activeEntries.map((entry) => {
|
|
2033
|
+
const row = document.createElement("div");
|
|
2034
|
+
row.style.display = "grid";
|
|
2035
|
+
row.style.gap = "2px";
|
|
2036
|
+
row.style.padding = "10px 12px";
|
|
2037
|
+
row.style.borderRadius = "12px";
|
|
2038
|
+
row.style.background = "rgba(255,255,255,0.36)";
|
|
2039
|
+
row.style.border = "1px solid rgba(17,24,39,0.06)";
|
|
2040
|
+
const rowTitle = document.createElement("p");
|
|
2041
|
+
rowTitle.textContent = entry.title;
|
|
2042
|
+
rowTitle.style.margin = "0";
|
|
2043
|
+
rowTitle.style.fontSize = "13px";
|
|
2044
|
+
rowTitle.style.fontWeight = "600";
|
|
2045
|
+
const rowMeta = document.createElement("p");
|
|
2046
|
+
rowMeta.textContent = entry.screenLabel ?? entry.badgeLabel;
|
|
2047
|
+
rowMeta.style.margin = "0";
|
|
2048
|
+
rowMeta.style.fontSize = "11px";
|
|
2049
|
+
rowMeta.style.color = "#777169";
|
|
2050
|
+
row.append(rowTitle, rowMeta);
|
|
2051
|
+
return row;
|
|
2052
|
+
}));
|
|
2053
|
+
validation.textContent =
|
|
2054
|
+
activeEntries.length === 0
|
|
2055
|
+
? "保存対象の action_unit がありません。削除予定を戻してください。"
|
|
2056
|
+
: titleInput.value.trim().length === 0
|
|
2057
|
+
? "タイトルを入力してください。"
|
|
2058
|
+
: "";
|
|
2059
|
+
confirmButton.disabled =
|
|
2060
|
+
activeEntries.length === 0 || titleInput.value.trim().length === 0;
|
|
2061
|
+
confirmButton.style.opacity = confirmButton.disabled ? "0.5" : "1";
|
|
2062
|
+
};
|
|
2063
|
+
titleInput.addEventListener("input", renderSelectionList);
|
|
2064
|
+
draftButton.addEventListener("click", () => {
|
|
2065
|
+
saveMode = "draft";
|
|
2066
|
+
syncSaveMode();
|
|
2067
|
+
});
|
|
2068
|
+
publishButton.addEventListener("click", () => {
|
|
2069
|
+
saveMode = "publish";
|
|
2070
|
+
syncSaveMode();
|
|
2071
|
+
});
|
|
2072
|
+
cancelButton.addEventListener("click", () => {
|
|
2073
|
+
backdrop.style.display = "none";
|
|
2074
|
+
});
|
|
2075
|
+
confirmButton.addEventListener("click", async () => {
|
|
2076
|
+
const snapshot = options.recording.getSnapshot();
|
|
2077
|
+
const activeEntries = snapshot.entries.filter((entry) => !entry.pendingRemoval);
|
|
2078
|
+
if (activeEntries.length === 0 || titleInput.value.trim().length === 0) {
|
|
2079
|
+
renderSelectionList();
|
|
2080
|
+
return;
|
|
2081
|
+
}
|
|
2082
|
+
confirmButton.disabled = true;
|
|
2083
|
+
try {
|
|
2084
|
+
await options.onSave({
|
|
2085
|
+
title: titleInput.value.trim(),
|
|
2086
|
+
description: descriptionInput.value.trim(),
|
|
2087
|
+
saveMode,
|
|
2088
|
+
selectedActionUnitIds: activeEntries.map((entry) => entry.id),
|
|
2089
|
+
});
|
|
2090
|
+
backdrop.style.display = "none";
|
|
2091
|
+
await options.onSaveCompleted({ saveMode });
|
|
2092
|
+
resetForm();
|
|
2093
|
+
}
|
|
2094
|
+
finally {
|
|
2095
|
+
confirmButton.disabled = false;
|
|
2096
|
+
renderSelectionList();
|
|
2097
|
+
}
|
|
2098
|
+
});
|
|
2099
|
+
backdrop.addEventListener("click", (event) => {
|
|
2100
|
+
if (event.target === backdrop) {
|
|
2101
|
+
backdrop.style.display = "none";
|
|
2102
|
+
}
|
|
2103
|
+
});
|
|
2104
|
+
modeRow.append(draftButton, publishButton);
|
|
2105
|
+
footer.append(cancelButton, confirmButton);
|
|
2106
|
+
dialog.append(heading, titleInput, descriptionInput, modeRow, listWrap, validation, footer);
|
|
2107
|
+
backdrop.append(dialog);
|
|
2108
|
+
syncSaveMode();
|
|
2109
|
+
return {
|
|
2110
|
+
root: backdrop,
|
|
2111
|
+
open() {
|
|
2112
|
+
backdrop.style.display = "grid";
|
|
2113
|
+
renderSelectionList();
|
|
2114
|
+
window.requestAnimationFrame(() => {
|
|
2115
|
+
titleInput.focus();
|
|
2116
|
+
});
|
|
2117
|
+
},
|
|
2118
|
+
close() {
|
|
2119
|
+
backdrop.style.display = "none";
|
|
2120
|
+
},
|
|
2121
|
+
dispose() {
|
|
2122
|
+
backdrop.remove();
|
|
2123
|
+
},
|
|
2124
|
+
};
|
|
2125
|
+
}
|
|
2126
|
+
function createSaveCompleteDialog(options) {
|
|
2127
|
+
const backdrop = document.createElement("div");
|
|
2128
|
+
backdrop.dataset.clueAuthoringSaveCompleteDialog = "true";
|
|
2129
|
+
markAuthoringRoot(backdrop);
|
|
2130
|
+
backdrop.style.position = "fixed";
|
|
2131
|
+
backdrop.style.inset = "0";
|
|
2132
|
+
backdrop.style.display = "none";
|
|
2133
|
+
backdrop.style.placeItems = "center";
|
|
2134
|
+
backdrop.style.padding = "24px";
|
|
2135
|
+
backdrop.style.background = "rgba(255,251,247,0.22)";
|
|
2136
|
+
backdrop.style.backdropFilter = "blur(8px)";
|
|
2137
|
+
backdrop.style.setProperty("-webkit-backdrop-filter", "blur(8px)");
|
|
2138
|
+
backdrop.style.zIndex = "2147483410";
|
|
2139
|
+
const dialog = document.createElement("div");
|
|
2140
|
+
markAuthoringRoot(dialog);
|
|
2141
|
+
dialog.style.width = "min(420px, 100%)";
|
|
2142
|
+
dialog.style.display = "grid";
|
|
2143
|
+
dialog.style.gap = "14px";
|
|
2144
|
+
dialog.style.padding = "18px";
|
|
2145
|
+
dialog.style.borderRadius = "18px";
|
|
2146
|
+
dialog.style.boxShadow = "rgba(17,24,39,0.12) 0px 22px 48px";
|
|
2147
|
+
dialog.style.color = "#111827";
|
|
2148
|
+
applyGlassSurfaceStyles(dialog, "strong");
|
|
2149
|
+
const title = document.createElement("h3");
|
|
2150
|
+
title.style.margin = "0";
|
|
2151
|
+
title.style.fontSize = "18px";
|
|
2152
|
+
title.style.fontWeight = "700";
|
|
2153
|
+
title.style.lineHeight = "1.4";
|
|
2154
|
+
const description = document.createElement("p");
|
|
2155
|
+
description.style.margin = "0";
|
|
2156
|
+
description.style.fontSize = "13px";
|
|
2157
|
+
description.style.lineHeight = "1.7";
|
|
2158
|
+
description.style.color = "#5b554e";
|
|
2159
|
+
description.textContent = "このまま終了しますか?";
|
|
2160
|
+
const footer = document.createElement("div");
|
|
2161
|
+
footer.style.display = "flex";
|
|
2162
|
+
footer.style.justifyContent = "flex-end";
|
|
2163
|
+
footer.style.gap = "10px";
|
|
2164
|
+
const continueButton = document.createElement("button");
|
|
2165
|
+
continueButton.dataset.clueAuthoringSaveCompleteContinue = "true";
|
|
2166
|
+
continueButton.type = "button";
|
|
2167
|
+
continueButton.textContent = "続けて記録する";
|
|
2168
|
+
continueButton.style.padding = "9px 12px";
|
|
2169
|
+
continueButton.style.borderRadius = "10px";
|
|
2170
|
+
continueButton.style.border = "1px solid rgba(17,24,39,0.08)";
|
|
2171
|
+
continueButton.style.background = "rgba(255,255,255,0.54)";
|
|
2172
|
+
continueButton.style.fontSize = "12px";
|
|
2173
|
+
continueButton.style.fontWeight = "600";
|
|
2174
|
+
continueButton.style.cursor = "pointer";
|
|
2175
|
+
const finishButton = document.createElement("button");
|
|
2176
|
+
finishButton.dataset.clueAuthoringSaveCompleteFinish = "true";
|
|
2177
|
+
finishButton.type = "button";
|
|
2178
|
+
finishButton.textContent = "終了する";
|
|
2179
|
+
finishButton.style.padding = "9px 14px";
|
|
2180
|
+
finishButton.style.borderRadius = "10px";
|
|
2181
|
+
finishButton.style.border = "1px solid rgba(181,129,79,0.14)";
|
|
2182
|
+
finishButton.style.background = "rgba(181,129,79,0.16)";
|
|
2183
|
+
finishButton.style.fontSize = "12px";
|
|
2184
|
+
finishButton.style.fontWeight = "700";
|
|
2185
|
+
finishButton.style.color = "#8f5b2f";
|
|
2186
|
+
finishButton.style.cursor = "pointer";
|
|
2187
|
+
const setPending = (pending) => {
|
|
2188
|
+
continueButton.disabled = pending;
|
|
2189
|
+
finishButton.disabled = pending;
|
|
2190
|
+
continueButton.style.opacity = pending ? "0.5" : "1";
|
|
2191
|
+
finishButton.style.opacity = pending ? "0.5" : "1";
|
|
2192
|
+
};
|
|
2193
|
+
continueButton.addEventListener("click", () => {
|
|
2194
|
+
setPending(true);
|
|
2195
|
+
void options
|
|
2196
|
+
.onRestartAuthoring()
|
|
2197
|
+
.then(() => {
|
|
2198
|
+
backdrop.style.display = "none";
|
|
2199
|
+
})
|
|
2200
|
+
.finally(() => {
|
|
2201
|
+
setPending(false);
|
|
2202
|
+
});
|
|
2203
|
+
});
|
|
2204
|
+
finishButton.addEventListener("click", () => {
|
|
2205
|
+
options.onCloseAuthoring();
|
|
2206
|
+
});
|
|
2207
|
+
backdrop.addEventListener("click", (event) => {
|
|
2208
|
+
if (event.target === backdrop) {
|
|
2209
|
+
backdrop.style.display = "none";
|
|
2210
|
+
}
|
|
2211
|
+
});
|
|
2212
|
+
footer.append(continueButton, finishButton);
|
|
2213
|
+
dialog.append(title, description, footer);
|
|
2214
|
+
backdrop.append(dialog);
|
|
2215
|
+
return {
|
|
2216
|
+
root: backdrop,
|
|
2217
|
+
open(params) {
|
|
2218
|
+
title.textContent =
|
|
2219
|
+
params.saveMode === "publish" ? "公開が完了しました" : "下書き保存が完了しました";
|
|
2220
|
+
description.textContent = "このまま終了しますか?";
|
|
2221
|
+
backdrop.style.display = "grid";
|
|
2222
|
+
setPending(false);
|
|
2223
|
+
},
|
|
2224
|
+
close() {
|
|
2225
|
+
backdrop.style.display = "none";
|
|
2226
|
+
},
|
|
2227
|
+
dispose() {
|
|
2228
|
+
backdrop.remove();
|
|
2229
|
+
},
|
|
2230
|
+
};
|
|
2231
|
+
}
|
|
2232
|
+
function createResetConfirmDialog(options) {
|
|
2233
|
+
const backdrop = document.createElement("div");
|
|
2234
|
+
backdrop.dataset.clueAuthoringResetDialog = "true";
|
|
2235
|
+
markAuthoringRoot(backdrop);
|
|
2236
|
+
backdrop.style.position = "fixed";
|
|
2237
|
+
backdrop.style.inset = "0";
|
|
2238
|
+
backdrop.style.display = "none";
|
|
2239
|
+
backdrop.style.placeItems = "center";
|
|
2240
|
+
backdrop.style.padding = "24px";
|
|
2241
|
+
backdrop.style.background = "rgba(255,251,247,0.22)";
|
|
2242
|
+
backdrop.style.backdropFilter = "blur(8px)";
|
|
2243
|
+
backdrop.style.setProperty("-webkit-backdrop-filter", "blur(8px)");
|
|
2244
|
+
backdrop.style.zIndex = "2147483405";
|
|
2245
|
+
const dialog = document.createElement("div");
|
|
2246
|
+
markAuthoringRoot(dialog);
|
|
2247
|
+
dialog.style.width = "min(420px, 100%)";
|
|
2248
|
+
dialog.style.display = "grid";
|
|
2249
|
+
dialog.style.gap = "14px";
|
|
2250
|
+
dialog.style.padding = "18px";
|
|
2251
|
+
dialog.style.borderRadius = "18px";
|
|
2252
|
+
dialog.style.boxShadow = "rgba(17,24,39,0.12) 0px 22px 48px";
|
|
2253
|
+
dialog.style.color = "#111827";
|
|
2254
|
+
applyGlassSurfaceStyles(dialog, "strong");
|
|
2255
|
+
const title = document.createElement("h3");
|
|
2256
|
+
title.style.margin = "0";
|
|
2257
|
+
title.style.fontSize = "18px";
|
|
2258
|
+
title.style.fontWeight = "700";
|
|
2259
|
+
title.style.lineHeight = "1.4";
|
|
2260
|
+
title.textContent = "イベントをリセット";
|
|
2261
|
+
const description = document.createElement("p");
|
|
2262
|
+
description.style.margin = "0";
|
|
2263
|
+
description.style.fontSize = "13px";
|
|
2264
|
+
description.style.lineHeight = "1.7";
|
|
2265
|
+
description.style.color = "#5b554e";
|
|
2266
|
+
description.textContent = "現在のイベントをすべてリセットします。よろしいですか?";
|
|
2267
|
+
const footer = document.createElement("div");
|
|
2268
|
+
footer.style.display = "flex";
|
|
2269
|
+
footer.style.justifyContent = "flex-end";
|
|
2270
|
+
footer.style.gap = "10px";
|
|
2271
|
+
const cancelButton = document.createElement("button");
|
|
2272
|
+
cancelButton.dataset.clueAuthoringResetCancel = "true";
|
|
2273
|
+
cancelButton.type = "button";
|
|
2274
|
+
cancelButton.textContent = "キャンセル";
|
|
2275
|
+
cancelButton.style.padding = "9px 12px";
|
|
2276
|
+
cancelButton.style.borderRadius = "10px";
|
|
2277
|
+
cancelButton.style.border = "1px solid rgba(17,24,39,0.08)";
|
|
2278
|
+
cancelButton.style.background = "rgba(255,255,255,0.54)";
|
|
2279
|
+
cancelButton.style.fontSize = "12px";
|
|
2280
|
+
cancelButton.style.fontWeight = "600";
|
|
2281
|
+
cancelButton.style.cursor = "pointer";
|
|
2282
|
+
const confirmButton = document.createElement("button");
|
|
2283
|
+
confirmButton.dataset.clueAuthoringResetConfirm = "true";
|
|
2284
|
+
confirmButton.type = "button";
|
|
2285
|
+
confirmButton.textContent = "はい";
|
|
2286
|
+
confirmButton.style.padding = "9px 14px";
|
|
2287
|
+
confirmButton.style.borderRadius = "10px";
|
|
2288
|
+
confirmButton.style.border = "1px solid rgba(181,129,79,0.14)";
|
|
2289
|
+
confirmButton.style.background = "rgba(181,129,79,0.16)";
|
|
2290
|
+
confirmButton.style.fontSize = "12px";
|
|
2291
|
+
confirmButton.style.fontWeight = "700";
|
|
2292
|
+
confirmButton.style.color = "#8f5b2f";
|
|
2293
|
+
confirmButton.style.cursor = "pointer";
|
|
2294
|
+
const setPending = (pending) => {
|
|
2295
|
+
cancelButton.disabled = pending;
|
|
2296
|
+
confirmButton.disabled = pending;
|
|
2297
|
+
cancelButton.style.opacity = pending ? "0.5" : "1";
|
|
2298
|
+
confirmButton.style.opacity = pending ? "0.5" : "1";
|
|
2299
|
+
};
|
|
2300
|
+
cancelButton.addEventListener("click", () => {
|
|
2301
|
+
backdrop.style.display = "none";
|
|
2302
|
+
});
|
|
2303
|
+
confirmButton.addEventListener("click", () => {
|
|
2304
|
+
setPending(true);
|
|
2305
|
+
void options
|
|
2306
|
+
.onConfirm()
|
|
2307
|
+
.then(() => {
|
|
2308
|
+
backdrop.style.display = "none";
|
|
2309
|
+
})
|
|
2310
|
+
.finally(() => {
|
|
2311
|
+
setPending(false);
|
|
2312
|
+
});
|
|
2313
|
+
});
|
|
2314
|
+
backdrop.addEventListener("click", (event) => {
|
|
2315
|
+
if (event.target === backdrop) {
|
|
2316
|
+
backdrop.style.display = "none";
|
|
2317
|
+
}
|
|
2318
|
+
});
|
|
2319
|
+
footer.append(cancelButton, confirmButton);
|
|
2320
|
+
dialog.append(title, description, footer);
|
|
2321
|
+
backdrop.append(dialog);
|
|
2322
|
+
return {
|
|
2323
|
+
root: backdrop,
|
|
2324
|
+
open() {
|
|
2325
|
+
backdrop.style.display = "grid";
|
|
2326
|
+
setPending(false);
|
|
2327
|
+
},
|
|
2328
|
+
close() {
|
|
2329
|
+
backdrop.style.display = "none";
|
|
2330
|
+
},
|
|
2331
|
+
dispose() {
|
|
2332
|
+
backdrop.remove();
|
|
2333
|
+
},
|
|
2334
|
+
};
|
|
2335
|
+
}
|
|
2336
|
+
export function mountToolbar(session, options) {
|
|
2337
|
+
const existingRoot = document.getElementById(TOOLBAR_ROOT_ID);
|
|
2338
|
+
if (existingRoot) {
|
|
2339
|
+
options.onReady();
|
|
2340
|
+
return Promise.resolve({
|
|
2341
|
+
root: existingRoot,
|
|
2342
|
+
dispose: toolbarDisposers.get(existingRoot) ?? (() => undefined),
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2345
|
+
const shell = document.createElement("div");
|
|
2346
|
+
shell.id = TOOLBAR_ROOT_ID;
|
|
2347
|
+
markAuthoringRoot(shell);
|
|
2348
|
+
shell.style.position = "fixed";
|
|
2349
|
+
shell.style.top = `${TOOLBAR_INITIAL_TOP}px`;
|
|
2350
|
+
shell.style.left = `${TOOLBAR_VIEWPORT_GUTTER}px`;
|
|
2351
|
+
shell.style.zIndex = "2147483000";
|
|
2352
|
+
shell.style.pointerEvents = "none";
|
|
2353
|
+
shell.style.width = `${TOOLBAR_COLLAPSED_WIDTH}px`;
|
|
2354
|
+
shell.style.height = `${TOOLBAR_COLLAPSED_HEIGHT}px`;
|
|
2355
|
+
const toolbar = document.createElement("div");
|
|
2356
|
+
toolbar.style.position = "relative";
|
|
2357
|
+
toolbar.style.pointerEvents = "none";
|
|
2358
|
+
toolbar.style.width = "100%";
|
|
2359
|
+
toolbar.style.height = "100%";
|
|
2360
|
+
toolbar.style.overflow = "visible";
|
|
2361
|
+
toolbar.style.fontFamily =
|
|
2362
|
+
'Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
|
|
2363
|
+
toolbar.style.userSelect = "none";
|
|
2364
|
+
const cluster = document.createElement("div");
|
|
2365
|
+
cluster.style.position = "relative";
|
|
2366
|
+
cluster.style.width = `${TOOLBAR_COLLAPSED_WIDTH}px`;
|
|
2367
|
+
cluster.style.height = `${TOOLBAR_COLLAPSED_HEIGHT}px`;
|
|
2368
|
+
cluster.style.pointerEvents = "none";
|
|
2369
|
+
let closeToolbarAndEnd = () => undefined;
|
|
2370
|
+
const saveCompleteDialog = createSaveCompleteDialog({
|
|
2371
|
+
onCloseAuthoring: () => {
|
|
2372
|
+
closeToolbarAndEnd();
|
|
2373
|
+
},
|
|
2374
|
+
onRestartAuthoring: async () => {
|
|
2375
|
+
const snapshot = options.recording.getSnapshot();
|
|
2376
|
+
if (!snapshot.isRecording) {
|
|
2377
|
+
await options.onSetRecording(true);
|
|
2378
|
+
}
|
|
2379
|
+
options.recording.replaceActionUnits([]);
|
|
2380
|
+
},
|
|
2381
|
+
});
|
|
2382
|
+
const resetConfirmDialog = createResetConfirmDialog({
|
|
2383
|
+
onConfirm: async () => {
|
|
2384
|
+
await options.onResetRecording();
|
|
2385
|
+
},
|
|
2386
|
+
});
|
|
2387
|
+
const saveDialog = createSaveDialog({
|
|
2388
|
+
recording: options.recording,
|
|
2389
|
+
onSave: options.onSave,
|
|
2390
|
+
onSaveCompleted: async ({ saveMode }) => {
|
|
2391
|
+
saveCompleteDialog.open({ saveMode });
|
|
2392
|
+
},
|
|
2393
|
+
});
|
|
2394
|
+
const panelHandle = createPanel(session, options.recording, {
|
|
2395
|
+
onResetRequested: async () => {
|
|
2396
|
+
saveDialog.close();
|
|
2397
|
+
saveCompleteDialog.close();
|
|
2398
|
+
resetConfirmDialog.open();
|
|
2399
|
+
},
|
|
2400
|
+
});
|
|
2401
|
+
const panelWrap = panelHandle.panelWrap;
|
|
2402
|
+
const baseButton = createBaseButton();
|
|
2403
|
+
const actionElements = AUTHORING_ACTIONS.map((action) => {
|
|
2404
|
+
const button = createActionButton(action.label, action.x, action.y);
|
|
2405
|
+
button.dataset.clueAuthoringAction = action.key;
|
|
2406
|
+
button.append(createLucideIcon(action.icon, 20));
|
|
2407
|
+
cluster.append(button);
|
|
2408
|
+
return {
|
|
2409
|
+
...action,
|
|
2410
|
+
button,
|
|
2411
|
+
};
|
|
2412
|
+
});
|
|
2413
|
+
let isDisposed = false;
|
|
2414
|
+
let isExpanded = false;
|
|
2415
|
+
const baseAnchorX = TOOLBAR_BASE_BUTTON_EDGE_OFFSET + TOOLBAR_BASE_BUTTON_SIZE / 2;
|
|
2416
|
+
const baseAnchorY = TOOLBAR_BASE_BUTTON_EDGE_OFFSET + TOOLBAR_BASE_BUTTON_SIZE / 2;
|
|
2417
|
+
const maxActionOffset = Math.max(...AUTHORING_ACTIONS.map((action) => action.y));
|
|
2418
|
+
const syncFloatingLayout = () => {
|
|
2419
|
+
const shellLeft = Number.parseFloat(shell.style.left) || shell.offsetLeft;
|
|
2420
|
+
const shellTop = Number.parseFloat(shell.style.top) || shell.offsetTop;
|
|
2421
|
+
const viewport = getViewportSize();
|
|
2422
|
+
const anchorViewportX = shellLeft + baseAnchorX;
|
|
2423
|
+
const anchorViewportY = shellTop + baseAnchorY;
|
|
2424
|
+
const bottomMostActionBottom = anchorViewportY + maxActionOffset + TOOLBAR_ACTION_BUTTON_SIZE / 2;
|
|
2425
|
+
const shouldStackUp = bottomMostActionBottom > viewport.height - TOOLBAR_VIEWPORT_GUTTER;
|
|
2426
|
+
const direction = shouldStackUp ? -1 : 1;
|
|
2427
|
+
actionElements.forEach((action) => {
|
|
2428
|
+
action.button.dataset.clueAuthoringStackDirection = shouldStackUp ? "up" : "down";
|
|
2429
|
+
action.button.style.transform = isExpanded
|
|
2430
|
+
? `translate(${action.x}px, ${action.y * direction}px) scale(1)`
|
|
2431
|
+
: "translate(0px, 0px) scale(0.72)";
|
|
2432
|
+
});
|
|
2433
|
+
if (!isExpanded) {
|
|
2434
|
+
return;
|
|
2435
|
+
}
|
|
2436
|
+
const preferredRightPanelLeft = anchorViewportX + TOOLBAR_BASE_BUTTON_SIZE / 2 + TOOLBAR_DOCK_GAP;
|
|
2437
|
+
const preferredLeftPanelLeft = anchorViewportX -
|
|
2438
|
+
TOOLBAR_BASE_BUTTON_SIZE / 2 -
|
|
2439
|
+
TOOLBAR_DOCK_GAP -
|
|
2440
|
+
TOOLBAR_PANEL_WIDTH;
|
|
2441
|
+
const maxPanelLeft = Math.max(TOOLBAR_VIEWPORT_GUTTER, viewport.width - TOOLBAR_PANEL_WIDTH - TOOLBAR_VIEWPORT_GUTTER);
|
|
2442
|
+
const rightPanelLeft = Math.min(Math.max(preferredRightPanelLeft, TOOLBAR_VIEWPORT_GUTTER), maxPanelLeft);
|
|
2443
|
+
const leftPanelLeft = Math.min(Math.max(preferredLeftPanelLeft, TOOLBAR_VIEWPORT_GUTTER), maxPanelLeft);
|
|
2444
|
+
const canPlaceRight = preferredRightPanelLeft + TOOLBAR_PANEL_WIDTH <=
|
|
2445
|
+
viewport.width - TOOLBAR_VIEWPORT_GUTTER;
|
|
2446
|
+
const canPlaceLeft = preferredLeftPanelLeft >= TOOLBAR_VIEWPORT_GUTTER;
|
|
2447
|
+
const nextPanelLeft = canPlaceRight || !canPlaceLeft ? rightPanelLeft : leftPanelLeft;
|
|
2448
|
+
const preferredPanelTop = anchorViewportY - TOOLBAR_BASE_BUTTON_SIZE / 2;
|
|
2449
|
+
const maxPanelTop = Math.max(TOOLBAR_INITIAL_TOP, viewport.height - TOOLBAR_PANEL_HEIGHT - TOOLBAR_VIEWPORT_GUTTER);
|
|
2450
|
+
const nextPanelTop = Math.min(Math.max(preferredPanelTop, TOOLBAR_INITIAL_TOP), maxPanelTop);
|
|
2451
|
+
panelWrap.style.left = `${Math.round(nextPanelLeft - shellLeft)}px`;
|
|
2452
|
+
panelWrap.style.top = `${Math.round(nextPanelTop - shellTop)}px`;
|
|
2453
|
+
panelWrap.style.transformOrigin =
|
|
2454
|
+
nextPanelLeft >= anchorViewportX ? "left top" : "right top";
|
|
2455
|
+
};
|
|
2456
|
+
const syncExpandedState = (toolbarDrag) => {
|
|
2457
|
+
baseButton.setAttribute("aria-expanded", isExpanded ? "true" : "false");
|
|
2458
|
+
baseButton.style.transform = isExpanded ? "scale(1.04)" : "scale(1)";
|
|
2459
|
+
baseButton.style.boxShadow = isExpanded
|
|
2460
|
+
? "rgba(17,24,39,0.14) 0px 16px 34px, rgba(17,24,39,0.1) 0px 0px 0px 1px"
|
|
2461
|
+
: "rgba(17,24,39,0.1) 0px 12px 28px, rgba(17,24,39,0.08) 0px 0px 0px 1px";
|
|
2462
|
+
panelWrap.style.width = isExpanded ? `${TOOLBAR_PANEL_WIDTH}px` : "0";
|
|
2463
|
+
panelWrap.style.opacity = isExpanded ? "1" : "0";
|
|
2464
|
+
panelWrap.style.transform = isExpanded
|
|
2465
|
+
? "translateX(0px) scale(1)"
|
|
2466
|
+
: "translateX(-26px) scale(0.96)";
|
|
2467
|
+
panelWrap.style.pointerEvents = isExpanded ? "auto" : "none";
|
|
2468
|
+
actionElements.forEach((action, index) => {
|
|
2469
|
+
action.button.style.transitionDelay = isExpanded
|
|
2470
|
+
? `${index * 28}ms`
|
|
2471
|
+
: `${(actionElements.length - index - 1) * 18}ms`;
|
|
2472
|
+
action.button.style.opacity = isExpanded ? "1" : "0";
|
|
2473
|
+
action.button.style.pointerEvents = isExpanded ? "auto" : "none";
|
|
2474
|
+
});
|
|
2475
|
+
syncFloatingLayout();
|
|
2476
|
+
window.requestAnimationFrame(() => {
|
|
2477
|
+
toolbarDrag.updateBounds();
|
|
2478
|
+
});
|
|
2479
|
+
};
|
|
2480
|
+
cluster.append(baseButton);
|
|
2481
|
+
toolbar.append(cluster, panelWrap);
|
|
2482
|
+
shell.append(toolbar);
|
|
2483
|
+
(document.body ?? document.documentElement).append(shell);
|
|
2484
|
+
(document.body ?? document.documentElement).append(saveDialog.root);
|
|
2485
|
+
(document.body ?? document.documentElement).append(resetConfirmDialog.root);
|
|
2486
|
+
(document.body ?? document.documentElement).append(saveCompleteDialog.root);
|
|
2487
|
+
const initialPosition = clampToolbarPosition({
|
|
2488
|
+
x: Math.round((getViewportSize().width - measureToolbar(shell).width) / 2),
|
|
2489
|
+
y: TOOLBAR_INITIAL_TOP,
|
|
2490
|
+
...measureToolbar(shell),
|
|
2491
|
+
});
|
|
2492
|
+
applyToolbarPosition(shell, initialPosition);
|
|
2493
|
+
return installToolbarDrag(shell, baseButton, {
|
|
2494
|
+
onPositionChange: () => {
|
|
2495
|
+
syncFloatingLayout();
|
|
2496
|
+
},
|
|
2497
|
+
}).then((toolbarDrag) => {
|
|
2498
|
+
const handleResize = () => {
|
|
2499
|
+
syncFloatingLayout();
|
|
2500
|
+
toolbarDrag.updateBounds();
|
|
2501
|
+
};
|
|
2502
|
+
const dispose = () => {
|
|
2503
|
+
if (isDisposed) {
|
|
2504
|
+
return;
|
|
2505
|
+
}
|
|
2506
|
+
isDisposed = true;
|
|
2507
|
+
toolbarDisposers.delete(shell);
|
|
2508
|
+
window.removeEventListener("resize", handleResize);
|
|
2509
|
+
panelHandle.dispose();
|
|
2510
|
+
saveDialog.dispose();
|
|
2511
|
+
resetConfirmDialog.dispose();
|
|
2512
|
+
saveCompleteDialog.dispose();
|
|
2513
|
+
toolbarDrag.dispose();
|
|
2514
|
+
};
|
|
2515
|
+
const recordAction = actionElements.find((action) => action.key === "record");
|
|
2516
|
+
recordAction?.button.addEventListener("click", () => {
|
|
2517
|
+
if (!recordButton || recordButton.dataset.pending === "true") {
|
|
2518
|
+
return;
|
|
2519
|
+
}
|
|
2520
|
+
recordButton.dataset.pending = "true";
|
|
2521
|
+
recordButton.disabled = true;
|
|
2522
|
+
void options
|
|
2523
|
+
.onSetRecording(!options.recording.getSnapshot().isRecording)
|
|
2524
|
+
.finally(() => {
|
|
2525
|
+
recordButton.dataset.pending = "false";
|
|
2526
|
+
recordButton.disabled = false;
|
|
2527
|
+
});
|
|
2528
|
+
});
|
|
2529
|
+
const recordButton = recordAction?.button ?? null;
|
|
2530
|
+
const unsubscribeRecordingState = options.recording.subscribe((snapshot) => {
|
|
2531
|
+
if (recordButton) {
|
|
2532
|
+
setActionButtonVisual(recordButton, snapshot.isRecording
|
|
2533
|
+
? "イベント記録を一時停止する"
|
|
2534
|
+
: "イベントを記録する", snapshot.isRecording ? PauseCircle : recordAction?.icon ?? []);
|
|
2535
|
+
recordButton.setAttribute("aria-pressed", snapshot.isRecording ? "true" : "false");
|
|
2536
|
+
recordButton.style.background = snapshot.isRecording
|
|
2537
|
+
? "rgba(181,129,79,0.14)"
|
|
2538
|
+
: "#ffffff";
|
|
2539
|
+
recordButton.style.color = snapshot.isRecording
|
|
2540
|
+
? "#8f5b2f"
|
|
2541
|
+
: "#111827";
|
|
2542
|
+
}
|
|
2543
|
+
});
|
|
2544
|
+
const disposeToolbar = () => {
|
|
2545
|
+
unsubscribeRecordingState();
|
|
2546
|
+
dispose();
|
|
2547
|
+
};
|
|
2548
|
+
closeToolbarAndEnd = () => {
|
|
2549
|
+
saveDialog.close();
|
|
2550
|
+
resetConfirmDialog.close();
|
|
2551
|
+
saveCompleteDialog.close();
|
|
2552
|
+
disposeToolbar();
|
|
2553
|
+
shell.remove();
|
|
2554
|
+
delete document.documentElement.dataset.clueBusinessEventAuthoring;
|
|
2555
|
+
options.onClose();
|
|
2556
|
+
};
|
|
2557
|
+
const closeAction = actionElements.find((action) => action.key === "close");
|
|
2558
|
+
const saveAction = actionElements.find((action) => action.key === "save");
|
|
2559
|
+
saveAction?.button.addEventListener("click", () => {
|
|
2560
|
+
saveCompleteDialog.close();
|
|
2561
|
+
saveDialog.open();
|
|
2562
|
+
});
|
|
2563
|
+
closeAction?.button.addEventListener("click", () => {
|
|
2564
|
+
closeToolbarAndEnd();
|
|
2565
|
+
});
|
|
2566
|
+
baseButton.addEventListener("click", () => {
|
|
2567
|
+
if (toolbarDrag.consumeSuppressedClick()) {
|
|
2568
|
+
return;
|
|
2569
|
+
}
|
|
2570
|
+
isExpanded = !isExpanded;
|
|
2571
|
+
syncExpandedState(toolbarDrag);
|
|
2572
|
+
});
|
|
2573
|
+
syncExpandedState(toolbarDrag);
|
|
2574
|
+
window.addEventListener("resize", handleResize);
|
|
2575
|
+
toolbarDisposers.set(shell, disposeToolbar);
|
|
2576
|
+
document.documentElement.dataset.clueBusinessEventAuthoring =
|
|
2577
|
+
session.session_id;
|
|
2578
|
+
options.onReady();
|
|
2579
|
+
return {
|
|
2580
|
+
root: shell,
|
|
2581
|
+
dispose: disposeToolbar,
|
|
2582
|
+
};
|
|
2583
|
+
});
|
|
2584
|
+
}
|