@genn-inc/cluebase-frontend-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.
Files changed (140) hide show
  1. package/README.md +222 -0
  2. package/dist/_generated/correlation-attribute-keys.d.ts +2 -0
  3. package/dist/_generated/correlation-attribute-keys.js +33 -0
  4. package/dist/_generated/identity-pii-policy.d.ts +38 -0
  5. package/dist/_generated/identity-pii-policy.js +103 -0
  6. package/dist/_generated/ingest-privacy-tables.d.ts +120 -0
  7. package/dist/_generated/ingest-privacy-tables.js +890 -0
  8. package/dist/_generated/otel-resource-keys.d.ts +21 -0
  9. package/dist/_generated/otel-resource-keys.js +72 -0
  10. package/dist/_generated/otel-span-conversion.d.ts +48 -0
  11. package/dist/_generated/otel-span-conversion.js +69 -0
  12. package/dist/_generated/parameter-snapshot-leaf.d.ts +11 -0
  13. package/dist/_generated/parameter-snapshot-leaf.js +37 -0
  14. package/dist/_generated/sdk-runtime-policy.d.ts +7 -0
  15. package/dist/_generated/sdk-runtime-policy.js +14 -0
  16. package/dist/_generated/usage-governor-tables.d.ts +15 -0
  17. package/dist/_generated/usage-governor-tables.js +44 -0
  18. package/dist/context/browser-storage.d.ts +15 -0
  19. package/dist/context/browser-storage.js +38 -0
  20. package/dist/context/environment.d.ts +37 -0
  21. package/dist/context/environment.js +229 -0
  22. package/dist/context/identity.d.ts +16 -0
  23. package/dist/context/identity.js +139 -0
  24. package/dist/context/organization.d.ts +16 -0
  25. package/dist/context/organization.js +60 -0
  26. package/dist/context/session.d.ts +28 -0
  27. package/dist/context/session.js +130 -0
  28. package/dist/context/tab.d.ts +22 -0
  29. package/dist/context/tab.js +117 -0
  30. package/dist/context/trace.d.ts +76 -0
  31. package/dist/context/trace.js +163 -0
  32. package/dist/exporter/batch.d.ts +94 -0
  33. package/dist/exporter/batch.js +485 -0
  34. package/dist/exporter/browser-outbox-records.d.ts +159 -0
  35. package/dist/exporter/browser-outbox-records.js +341 -0
  36. package/dist/exporter/browser-outbox.d.ts +112 -0
  37. package/dist/exporter/browser-outbox.js +876 -0
  38. package/dist/exporter/client.d.ts +92 -0
  39. package/dist/exporter/client.js +541 -0
  40. package/dist/exporter/delivery-response-contract.d.ts +103 -0
  41. package/dist/exporter/delivery-response-contract.js +147 -0
  42. package/dist/exporter/delivery-response.d.ts +37 -0
  43. package/dist/exporter/delivery-response.js +151 -0
  44. package/dist/exporter/diagnostics.d.ts +42 -0
  45. package/dist/exporter/diagnostics.js +148 -0
  46. package/dist/exporter/event-id.d.ts +10 -0
  47. package/dist/exporter/event-id.js +75 -0
  48. package/dist/exporter/frontend-resource.d.ts +20 -0
  49. package/dist/exporter/frontend-resource.js +52 -0
  50. package/dist/exporter/local-minimization.d.ts +29 -0
  51. package/dist/exporter/local-minimization.js +87 -0
  52. package/dist/exporter/observation-source-event-exporter.d.ts +69 -0
  53. package/dist/exporter/observation-source-event-exporter.js +154 -0
  54. package/dist/exporter/parameter-snapshot.d.ts +14 -0
  55. package/dist/exporter/parameter-snapshot.js +215 -0
  56. package/dist/exporter/path-template.d.ts +1 -0
  57. package/dist/exporter/path-template.js +67 -0
  58. package/dist/exporter/raw-event-to-source-signal.d.ts +12 -0
  59. package/dist/exporter/raw-event-to-source-signal.js +297 -0
  60. package/dist/exporter/retry.d.ts +8 -0
  61. package/dist/exporter/retry.js +8 -0
  62. package/dist/exporter/sanitize.d.ts +30 -0
  63. package/dist/exporter/sanitize.js +177 -0
  64. package/dist/exporter/schema-evidence.d.ts +20 -0
  65. package/dist/exporter/schema-evidence.js +232 -0
  66. package/dist/exporter/source-event.d.ts +99 -0
  67. package/dist/exporter/source-event.js +13 -0
  68. package/dist/exporter/span-to-source-signal.d.ts +8 -0
  69. package/dist/exporter/span-to-source-signal.js +174 -0
  70. package/dist/exporter/text-fingerprint.d.ts +9 -0
  71. package/dist/exporter/text-fingerprint.js +16 -0
  72. package/dist/index.d.ts +25 -0
  73. package/dist/index.js +158 -0
  74. package/dist/instrumentation/action.d.ts +2 -0
  75. package/dist/instrumentation/action.js +171 -0
  76. package/dist/instrumentation/cluebase-authored-attributes.d.ts +4 -0
  77. package/dist/instrumentation/cluebase-authored-attributes.js +32 -0
  78. package/dist/instrumentation/common.d.ts +73 -0
  79. package/dist/instrumentation/common.js +90 -0
  80. package/dist/instrumentation/dom.d.ts +63 -0
  81. package/dist/instrumentation/dom.js +640 -0
  82. package/dist/instrumentation/drag.d.ts +2 -0
  83. package/dist/instrumentation/drag.js +82 -0
  84. package/dist/instrumentation/error.d.ts +2 -0
  85. package/dist/instrumentation/error.js +318 -0
  86. package/dist/instrumentation/form.d.ts +2 -0
  87. package/dist/instrumentation/form.js +132 -0
  88. package/dist/instrumentation/input.d.ts +2 -0
  89. package/dist/instrumentation/input.js +180 -0
  90. package/dist/instrumentation/location.d.ts +11 -0
  91. package/dist/instrumentation/location.js +69 -0
  92. package/dist/instrumentation/naming-evidence.d.ts +20 -0
  93. package/dist/instrumentation/naming-evidence.js +11 -0
  94. package/dist/instrumentation/navigation.d.ts +2 -0
  95. package/dist/instrumentation/navigation.js +144 -0
  96. package/dist/instrumentation/network-request.d.ts +103 -0
  97. package/dist/instrumentation/network-request.js +285 -0
  98. package/dist/instrumentation/network-span-attributes.d.ts +25 -0
  99. package/dist/instrumentation/network-span-attributes.js +153 -0
  100. package/dist/instrumentation/network.d.ts +16 -0
  101. package/dist/instrumentation/network.js +502 -0
  102. package/dist/instrumentation/page.d.ts +2 -0
  103. package/dist/instrumentation/page.js +56 -0
  104. package/dist/instrumentation/performance.d.ts +2 -0
  105. package/dist/instrumentation/performance.js +365 -0
  106. package/dist/integrations/next-router.d.ts +16 -0
  107. package/dist/integrations/next-router.js +18 -0
  108. package/dist/integrations/react-router.d.ts +7 -0
  109. package/dist/integrations/react-router.js +61 -0
  110. package/dist/internal/internal-log.d.ts +23 -0
  111. package/dist/internal/internal-log.js +47 -0
  112. package/dist/internal/metrics.d.ts +10 -0
  113. package/dist/internal/metrics.js +50 -0
  114. package/dist/provider/_version.d.ts +1 -0
  115. package/dist/provider/_version.js +3 -0
  116. package/dist/provider/config.d.ts +12 -0
  117. package/dist/provider/config.js +177 -0
  118. package/dist/provider/constants.d.ts +62 -0
  119. package/dist/provider/constants.js +118 -0
  120. package/dist/provider/contracts.d.ts +51 -0
  121. package/dist/provider/contracts.js +46 -0
  122. package/dist/provider/privacy-policy-store.d.ts +20 -0
  123. package/dist/provider/privacy-policy-store.js +56 -0
  124. package/dist/provider/sampling.d.ts +2 -0
  125. package/dist/provider/sampling.js +17 -0
  126. package/dist/provider/sdk-event-pipeline.d.ts +60 -0
  127. package/dist/provider/sdk-event-pipeline.js +478 -0
  128. package/dist/provider/sdk-input-normalization.d.ts +7 -0
  129. package/dist/provider/sdk-input-normalization.js +113 -0
  130. package/dist/provider/sdk-otel-pipeline.d.ts +14 -0
  131. package/dist/provider/sdk-otel-pipeline.js +148 -0
  132. package/dist/provider/sdk-runtime-surfaces.d.ts +49 -0
  133. package/dist/provider/sdk-runtime-surfaces.js +216 -0
  134. package/dist/provider/sdk.d.ts +2 -0
  135. package/dist/provider/sdk.js +614 -0
  136. package/dist/provider/types.d.ts +323 -0
  137. package/dist/provider/types.js +1 -0
  138. package/dist/provider/usage-governor.d.ts +7 -0
  139. package/dist/provider/usage-governor.js +116 -0
  140. package/package.json +59 -0
@@ -0,0 +1,640 @@
1
+ export const buildDomPath = (element) => {
2
+ const segments = [];
3
+ let current = element;
4
+ while (current && segments.length < 8) {
5
+ const tag = current.tagName.toLowerCase();
6
+ const id = current.id ? `#${current.id}` : "";
7
+ const className = typeof current.className === "string" && current.className.trim()
8
+ ? `.${current.className.trim().split(/\s+/).slice(0, 2).join(".")}`
9
+ : "";
10
+ segments.unshift(`${tag}${id}${className}`);
11
+ current = current.parentElement;
12
+ }
13
+ return segments.join(" > ");
14
+ };
15
+ export const getTextPreview = (element) => {
16
+ const text = element.textContent?.trim() ?? "";
17
+ if (!text) {
18
+ return null;
19
+ }
20
+ return text.slice(0, 120);
21
+ };
22
+ export const getElementClassList = (element) => {
23
+ if (typeof element.className !== "string") {
24
+ return null;
25
+ }
26
+ const classList = element.className.trim().split(/\s+/).filter(Boolean);
27
+ return classList.length > 0 ? classList : null;
28
+ };
29
+ export const getElementRole = (element) => {
30
+ const explicitRole = element.getAttribute("role");
31
+ if (explicitRole) {
32
+ return explicitRole;
33
+ }
34
+ if (element instanceof HTMLAnchorElement && element.hasAttribute("href")) {
35
+ return "link";
36
+ }
37
+ if (element instanceof HTMLButtonElement) {
38
+ return "button";
39
+ }
40
+ if (element instanceof HTMLSelectElement) {
41
+ return element.multiple ? "listbox" : "combobox";
42
+ }
43
+ if (element instanceof HTMLTextAreaElement) {
44
+ return "textbox";
45
+ }
46
+ if (element instanceof HTMLInputElement) {
47
+ switch (element.type) {
48
+ case "button":
49
+ case "image":
50
+ case "reset":
51
+ case "submit":
52
+ return "button";
53
+ case "checkbox":
54
+ return "checkbox";
55
+ case "radio":
56
+ return "radio";
57
+ case "range":
58
+ return "slider";
59
+ case "hidden":
60
+ return null;
61
+ default:
62
+ return "textbox";
63
+ }
64
+ }
65
+ if (element.tagName.toLowerCase() === "summary") {
66
+ return "button";
67
+ }
68
+ return null;
69
+ };
70
+ export const getElementPlaceholder = (element) => {
71
+ if (element instanceof HTMLInputElement ||
72
+ element instanceof HTMLTextAreaElement) {
73
+ return element.placeholder || null;
74
+ }
75
+ return null;
76
+ };
77
+ export const getElementAriaLabel = (element) => {
78
+ return element.getAttribute("aria-label") || null;
79
+ };
80
+ const getElementTitle = (element) => element.getAttribute("title") || null;
81
+ const normalizeHumanText = (value) => {
82
+ const normalized = value?.replace(/\s+/g, " ").trim() ?? "";
83
+ return normalized ? normalized.slice(0, 120) : null;
84
+ };
85
+ const getSelectSelectionLabel = (element) => {
86
+ const labels = Array.from(element.selectedOptions)
87
+ .map((option) => normalizeHumanText(option.label || option.textContent))
88
+ .filter((value) => Boolean(value));
89
+ const uniqueLabels = Array.from(new Set(labels));
90
+ if (uniqueLabels.length === 0) {
91
+ return null;
92
+ }
93
+ if (uniqueLabels.length <= 3) {
94
+ return uniqueLabels.join(", ");
95
+ }
96
+ return `${uniqueLabels.slice(0, 3).join(", ")} +${uniqueLabels.length - 3}`;
97
+ };
98
+ const SECTION_CONTAINER_SELECTOR = [
99
+ "section",
100
+ "article",
101
+ "aside",
102
+ "nav",
103
+ "main",
104
+ "form",
105
+ "fieldset",
106
+ "dialog",
107
+ "[role='dialog']",
108
+ "[role='region']",
109
+ ].join(",");
110
+ const SECTION_HEADING_SELECTOR = [
111
+ "h1",
112
+ "h2",
113
+ "h3",
114
+ "h4",
115
+ "h5",
116
+ "h6",
117
+ "legend",
118
+ ].join(",");
119
+ const readSectionHeadingLabel = (container) => {
120
+ const labelledBy = container.getAttribute("aria-labelledby");
121
+ if (labelledBy) {
122
+ const text = readAriaLabelledBy(container);
123
+ if (text) {
124
+ return text;
125
+ }
126
+ }
127
+ const headingNode = container.querySelector(SECTION_HEADING_SELECTOR);
128
+ if (headingNode instanceof Element) {
129
+ const text = getTextPreview(headingNode);
130
+ if (text) {
131
+ return text;
132
+ }
133
+ }
134
+ return (container.getAttribute("aria-label") ||
135
+ container.getAttribute("title") ||
136
+ null);
137
+ };
138
+ export const getElementSectionHeading = (element) => {
139
+ let container = element.closest(SECTION_CONTAINER_SELECTOR);
140
+ while (container) {
141
+ const label = readSectionHeadingLabel(container);
142
+ if (label) {
143
+ return label;
144
+ }
145
+ container =
146
+ container.parentElement?.closest(SECTION_CONTAINER_SELECTOR) ?? null;
147
+ }
148
+ return null;
149
+ };
150
+ const readAriaLabelledBy = (element) => {
151
+ const labelledBy = element.getAttribute("aria-labelledby");
152
+ if (!labelledBy) {
153
+ return null;
154
+ }
155
+ const text = labelledBy
156
+ .split(/\s+/)
157
+ .map((id) => element.ownerDocument?.getElementById(id))
158
+ .filter((node) => node instanceof HTMLElement)
159
+ .map((node) => getTextPreview(node))
160
+ .filter((value) => Boolean(value))
161
+ .join(" ")
162
+ .replace(/\s+/g, " ")
163
+ .trim();
164
+ return text || null;
165
+ };
166
+ const getAssociatedLabel = (element) => {
167
+ if (element instanceof HTMLInputElement ||
168
+ element instanceof HTMLTextAreaElement ||
169
+ element instanceof HTMLSelectElement) {
170
+ const label = element.labels?.[0]?.textContent?.trim();
171
+ if (label) {
172
+ return label.replace(/\s+/g, " ").slice(0, 120);
173
+ }
174
+ }
175
+ const closestLabel = element.closest("label");
176
+ const closestLabelText = closestLabel?.textContent?.trim();
177
+ if (closestLabelText) {
178
+ return closestLabelText.replace(/\s+/g, " ").slice(0, 120);
179
+ }
180
+ return null;
181
+ };
182
+ const readElementContentLabel = (element) => {
183
+ if (element instanceof HTMLSelectElement) {
184
+ return getSelectSelectionLabel(element);
185
+ }
186
+ const ownText = Array.from(element.childNodes)
187
+ .filter((node) => node.nodeType === Node.TEXT_NODE)
188
+ .map((node) => node.textContent?.trim() ?? "")
189
+ .filter((value) => value.length > 0)
190
+ .join(" ")
191
+ .trim();
192
+ if (ownText) {
193
+ return ownText.slice(0, 120);
194
+ }
195
+ const isInteractiveDisplayTarget = element instanceof HTMLButtonElement ||
196
+ element instanceof HTMLAnchorElement ||
197
+ element.getAttribute("role") === "button" ||
198
+ element.getAttribute("role") === "link";
199
+ const descendantCandidates = Array.from(element.querySelectorAll("label, p, span, strong, em, small, button, a, [role='button'], [role='link']"))
200
+ .map((candidate) => candidate.textContent?.trim() ?? "")
201
+ .map((value) => value.replace(/\s+/g, " ").trim())
202
+ .filter((value) => value.length > 0 && value.length <= 80);
203
+ if (isInteractiveDisplayTarget && descendantCandidates.length > 0) {
204
+ return descendantCandidates[0] ?? null;
205
+ }
206
+ if (descendantCandidates.length > 0) {
207
+ descendantCandidates.sort((left, right) => {
208
+ const leftWordCount = left.split(/\s+/).length;
209
+ const rightWordCount = right.split(/\s+/).length;
210
+ if (leftWordCount !== rightWordCount) {
211
+ return leftWordCount - rightWordCount;
212
+ }
213
+ return left.length - right.length;
214
+ });
215
+ return descendantCandidates[0] ?? null;
216
+ }
217
+ return getTextPreview(element);
218
+ };
219
+ const readNativeHostLabel = (element) => {
220
+ const associatedLabel = getAssociatedLabel(element);
221
+ if (associatedLabel) {
222
+ return associatedLabel;
223
+ }
224
+ if (element instanceof HTMLButtonElement ||
225
+ (element instanceof HTMLInputElement &&
226
+ ["button", "submit", "reset"].includes(element.type))) {
227
+ const value = element.getAttribute("value");
228
+ if (value?.trim()) {
229
+ return value.trim().replace(/\s+/g, " ").slice(0, 120);
230
+ }
231
+ }
232
+ if (element instanceof HTMLImageElement) {
233
+ const alt = element.getAttribute("alt");
234
+ if (alt?.trim()) {
235
+ return alt.trim().replace(/\s+/g, " ").slice(0, 120);
236
+ }
237
+ }
238
+ return null;
239
+ };
240
+ /**
241
+ * 要素の名前を **1 本**に決めます。
242
+ *
243
+ * 順序は W3C の名前算出 (Accessible Name Computation) に従います。
244
+ * `aria-labelledby` → `aria-label` → 対応ラベル / `alt` → 要素内の文言 → `title`。
245
+ * **自前の優先順を作ってはいけません。** 顧客の画面を読み上げる道具が名乗る名前と
246
+ * 食い違う名前を保管すると、行動の名付けが「利用者が見ている名前」から離れます。
247
+ *
248
+ * 生の候補を並べて持たないのも同じ理由です。どれを採るかを後段でもう一度決めると、
249
+ * 決め方が 2 つになります。
250
+ */
251
+ export const getAccessibleName = (element) => {
252
+ const labelledBy = readAriaLabelledBy(element);
253
+ if (labelledBy) {
254
+ return { name: labelledBy.slice(0, 120), source: "aria_labelledby" };
255
+ }
256
+ const ariaLabel = getElementAriaLabel(element);
257
+ if (ariaLabel?.trim()) {
258
+ return { name: ariaLabel.trim().slice(0, 120), source: "aria_label" };
259
+ }
260
+ const nativeLabel = readNativeHostLabel(element);
261
+ if (nativeLabel) {
262
+ return { name: nativeLabel, source: "native_label" };
263
+ }
264
+ const content = readElementContentLabel(element);
265
+ if (content) {
266
+ return { name: content, source: "content" };
267
+ }
268
+ const title = getElementTitle(element);
269
+ if (title?.trim()) {
270
+ return { name: title.trim().replace(/\s+/g, " ").slice(0, 120), source: "title" };
271
+ }
272
+ return null;
273
+ };
274
+ export const getElementLabel = (element) => getAccessibleName(element)?.name ?? null;
275
+ // 祖先の並びは深さと長さの両方で止めます。深い画面ほど並びが伸びるため、
276
+ // 深さだけで止めると 1 件あたりの大きさが顧客の画面構造に引きずられます。
277
+ const MAX_ANCESTOR_CHAIN_DEPTH = 6;
278
+ const MAX_ANCESTOR_CHAIN_LENGTH = 200;
279
+ /**
280
+ * 押された要素の**祖先の並び**。`main > form[role=search] > button` の形で返します。
281
+ *
282
+ * 綴りは**機械が決めた値だけ**で作ります (要素の種類と役割)。顧客が書いた文言を
283
+ * 混ぜないので、文言を伏せた状態でも「画面のどのまとまりの中の操作か」が残ります。
284
+ */
285
+ export const getAncestorChain = (element) => {
286
+ const segments = [];
287
+ let current = element.parentElement;
288
+ while (current && segments.length < MAX_ANCESTOR_CHAIN_DEPTH) {
289
+ if (current instanceof HTMLHtmlElement) {
290
+ break;
291
+ }
292
+ const tag = current.tagName.toLowerCase();
293
+ const role = current.getAttribute("role");
294
+ segments.unshift(role ? `${tag}[role=${role}]` : tag);
295
+ current = current.parentElement;
296
+ }
297
+ if (segments.length === 0) {
298
+ return null;
299
+ }
300
+ return segments.join(" > ").slice(0, MAX_ANCESTOR_CHAIN_LENGTH);
301
+ };
302
+ /** 画面の題。頁が何の画面かを表す、顧客が書いた文言です。 */
303
+ export const getDocumentTitle = (element) => normalizeHumanText(element.ownerDocument?.title ?? null);
304
+ /**
305
+ * 押した後に行く先。`href` を持つ要素はその値を返します。
306
+ *
307
+ * 問い合わせ文字列は落とします。**そこに載るのは利用者ごとに違う値**で、
308
+ * 名前に入れると行動の型が利用者の数だけ増えます。
309
+ */
310
+ export const getNavigationHref = (element) => {
311
+ const href = element.getAttribute("href");
312
+ if (!href?.trim()) {
313
+ return null;
314
+ }
315
+ const trimmed = href.trim();
316
+ try {
317
+ const url = new URL(trimmed, element.ownerDocument?.baseURI);
318
+ return `${url.origin}${url.pathname}`.slice(0, 200);
319
+ }
320
+ catch {
321
+ return trimmed.split("?")[0]?.slice(0, 200) ?? null;
322
+ }
323
+ };
324
+ const CLICKABLE_DISPLAY_SELECTOR = [
325
+ "a[href]",
326
+ "button",
327
+ "input",
328
+ "textarea",
329
+ "select",
330
+ "summary",
331
+ "[role='button']",
332
+ "[role='link']",
333
+ "[role='menuitem']",
334
+ "[role='menuitemcheckbox']",
335
+ "[role='menuitemradio']",
336
+ "[role='tab']",
337
+ "[role='switch']",
338
+ "[role='checkbox']",
339
+ "[role='radio']",
340
+ "[role='option']",
341
+ ].join(",");
342
+ const CLICK_ACTION_ROLES = new Set([
343
+ "button",
344
+ "link",
345
+ "menuitem",
346
+ "menuitemcheckbox",
347
+ "menuitemradio",
348
+ "tab",
349
+ "switch",
350
+ "checkbox",
351
+ "radio",
352
+ "option",
353
+ ]);
354
+ const PORTALLED_SCOPE_CONTINUATION_SELECTOR = [
355
+ "[popover]",
356
+ "[data-slot='calendar']",
357
+ "[data-slot='command-list']",
358
+ "[data-slot='dropdown-menu-content']",
359
+ "[data-slot='popover-content']",
360
+ "[data-slot='select-content']",
361
+ "[data-day]",
362
+ "[role='grid']",
363
+ "[role='listbox']",
364
+ "[role='menu']",
365
+ "[role='tree']",
366
+ ".rdp-root",
367
+ ".rdp-months",
368
+ ".rdp-month",
369
+ ".rdp-month_grid",
370
+ ".rdp-day",
371
+ ".rdp-day_button",
372
+ ".rdp-button_next",
373
+ ".rdp-button_previous",
374
+ "[data-radix-popper-content-wrapper]",
375
+ "[data-radix-select-content]",
376
+ "[data-radix-popover-content]",
377
+ "[data-radix-menu-content]",
378
+ "[data-side][data-align]",
379
+ ].join(",");
380
+ export const resolveClickDisplayTarget = (element) => {
381
+ const matched = element.closest(CLICKABLE_DISPLAY_SELECTOR);
382
+ return matched ?? element;
383
+ };
384
+ export const shouldCaptureClickTarget = (element) => {
385
+ if (element instanceof HTMLHtmlElement ||
386
+ element instanceof HTMLBodyElement) {
387
+ return false;
388
+ }
389
+ const role = getElementRole(element);
390
+ if (role && CLICK_ACTION_ROLES.has(role)) {
391
+ return true;
392
+ }
393
+ if (isEditableElement(element)) {
394
+ return true;
395
+ }
396
+ if (getElementLabel(element) ||
397
+ getElementAriaLabel(element) ||
398
+ getElementPlaceholder(element) ||
399
+ getElementTitle(element)) {
400
+ return true;
401
+ }
402
+ return false;
403
+ };
404
+ const SAFE_FALLBACK_STABLE_IDENTIFIER = /^[a-z0-9._:-]{1,80}$/i;
405
+ const toSafeFallbackStableSegment = (value) => {
406
+ if (!value) {
407
+ return null;
408
+ }
409
+ const normalized = value.trim();
410
+ if (!normalized || !SAFE_FALLBACK_STABLE_IDENTIFIER.test(normalized)) {
411
+ return null;
412
+ }
413
+ return normalized.toLowerCase();
414
+ };
415
+ const buildCoarsePosition = (element) => {
416
+ const segments = [];
417
+ let current = element;
418
+ let depth = 0;
419
+ while (current && depth < 4) {
420
+ const parent = current.parentElement;
421
+ if (!parent) {
422
+ break;
423
+ }
424
+ const siblings = Array.from(parent.children);
425
+ const currentTagName = current.tagName;
426
+ const sameTagSiblings = siblings.filter((child) => child.tagName === currentTagName);
427
+ const positionIndex = siblings.indexOf(current) + 1;
428
+ const sameTagIndex = sameTagSiblings.indexOf(current) + 1;
429
+ segments.unshift(`p${positionIndex}-t${sameTagIndex}`);
430
+ current = parent;
431
+ depth += 1;
432
+ }
433
+ if (segments.length === 0) {
434
+ return "root";
435
+ }
436
+ return segments.join("/");
437
+ };
438
+ const buildStructuralStableKey = (element) => {
439
+ const tag = element.tagName.toLowerCase();
440
+ const role = toSafeFallbackStableSegment(getElementRole(element)) || "none";
441
+ const inputType = (element instanceof HTMLInputElement && element.type) ||
442
+ (element instanceof HTMLTextAreaElement && "textarea") ||
443
+ (element instanceof HTMLSelectElement && "select") ||
444
+ (element instanceof HTMLElement &&
445
+ element.isContentEditable &&
446
+ "contenteditable") ||
447
+ "na";
448
+ return `struct:${tag}:${role}:${inputType}:${buildCoarsePosition(element)}`;
449
+ };
450
+ export const getFormKey = (element) => {
451
+ return toSafeFallbackStableSegment(findParentFormId(element));
452
+ };
453
+ export const resolveStableKeyDescriptor = (element) => {
454
+ const dataTestId = toSafeFallbackStableSegment(element.getAttribute("data-testid"));
455
+ if (dataTestId) {
456
+ return {
457
+ stableKey: `dtid:${dataTestId}`,
458
+ stableKeyQuality: "compat",
459
+ formKey: getFormKey(element),
460
+ };
461
+ }
462
+ const dataQa = toSafeFallbackStableSegment(element.getAttribute("data-qa"));
463
+ if (dataQa) {
464
+ return {
465
+ stableKey: `dqa:${dataQa}`,
466
+ stableKeyQuality: "compat",
467
+ formKey: getFormKey(element),
468
+ };
469
+ }
470
+ const safeName = toSafeFallbackStableSegment(element.getAttribute("name"));
471
+ if (safeName) {
472
+ return {
473
+ stableKey: `name:${safeName}`,
474
+ stableKeyQuality: "structural",
475
+ formKey: getFormKey(element),
476
+ };
477
+ }
478
+ const safeAriaLabel = toSafeFallbackStableSegment(getElementAriaLabel(element));
479
+ if (safeAriaLabel) {
480
+ return {
481
+ stableKey: `aria:${safeAriaLabel}`,
482
+ stableKeyQuality: "structural",
483
+ formKey: getFormKey(element),
484
+ };
485
+ }
486
+ const safeId = toSafeFallbackStableSegment(element.id);
487
+ if (safeId) {
488
+ return {
489
+ stableKey: `id:${safeId}`,
490
+ stableKeyQuality: "structural",
491
+ formKey: getFormKey(element),
492
+ };
493
+ }
494
+ return {
495
+ stableKey: buildStructuralStableKey(element),
496
+ stableKeyQuality: "structural",
497
+ formKey: getFormKey(element),
498
+ };
499
+ };
500
+ export const getStableKey = (element) => {
501
+ return resolveStableKeyDescriptor(element).stableKey;
502
+ };
503
+ const resolveOwnerForm = (element) => {
504
+ if (element instanceof HTMLFormElement) {
505
+ return element;
506
+ }
507
+ if (element instanceof HTMLButtonElement ||
508
+ element instanceof HTMLInputElement ||
509
+ element instanceof HTMLSelectElement ||
510
+ element instanceof HTMLTextAreaElement) {
511
+ return element.form;
512
+ }
513
+ const closestForm = element.closest("form");
514
+ return closestForm instanceof HTMLFormElement ? closestForm : null;
515
+ };
516
+ const resolveDirectInteractionScopeDescriptor = (element) => {
517
+ const form = resolveOwnerForm(element);
518
+ if (form) {
519
+ return {
520
+ interactionScopeKind: "form",
521
+ interactionScopeKey: `form:${resolveStableKeyDescriptor(form).stableKey}`,
522
+ };
523
+ }
524
+ const fieldset = element.closest("fieldset");
525
+ if (fieldset instanceof HTMLFieldSetElement) {
526
+ return {
527
+ interactionScopeKind: "fieldset",
528
+ interactionScopeKey: `fieldset:${resolveStableKeyDescriptor(fieldset).stableKey}`,
529
+ };
530
+ }
531
+ if (element.closest(PORTALLED_SCOPE_CONTINUATION_SELECTOR)) {
532
+ return null;
533
+ }
534
+ const dialog = element.closest("dialog, [role='dialog']");
535
+ if (dialog instanceof Element) {
536
+ return {
537
+ interactionScopeKind: "dialog",
538
+ interactionScopeKey: `dialog:${resolveStableKeyDescriptor(dialog).stableKey}`,
539
+ };
540
+ }
541
+ return null;
542
+ };
543
+ const escapeAttributeValue = (value) => value.replace(/\\/g, "\\\\").replace(/"/g, '\\"');
544
+ const resolveControlledPortalScopeDescriptor = (element) => {
545
+ const portal = element.closest(PORTALLED_SCOPE_CONTINUATION_SELECTOR);
546
+ const portalId = portal?.id || element.id || null;
547
+ if (!portalId || !element.ownerDocument) {
548
+ return null;
549
+ }
550
+ const escapedId = escapeAttributeValue(portalId);
551
+ const controller = element.ownerDocument.querySelector(`[aria-controls="${escapedId}"], [aria-owns="${escapedId}"]`);
552
+ if (!(controller instanceof Element)) {
553
+ return null;
554
+ }
555
+ return resolveDirectInteractionScopeDescriptor(controller);
556
+ };
557
+ export const resolveInteractionScopeDescriptor = (element) => resolveDirectInteractionScopeDescriptor(element) ??
558
+ resolveControlledPortalScopeDescriptor(element);
559
+ export const extractFieldName = (element) => {
560
+ if (element instanceof HTMLInputElement ||
561
+ element instanceof HTMLTextAreaElement ||
562
+ element instanceof HTMLSelectElement) {
563
+ return (element.getAttribute("name") ||
564
+ element.getAttribute("id") ||
565
+ element.getAttribute("aria-label") ||
566
+ null);
567
+ }
568
+ if (element instanceof HTMLElement && element.isContentEditable) {
569
+ return (element.getAttribute("data-field-name") ||
570
+ element.getAttribute("aria-label") ||
571
+ element.getAttribute("id") ||
572
+ null);
573
+ }
574
+ return null;
575
+ };
576
+ export const extractFieldType = (element) => {
577
+ if (element instanceof HTMLInputElement) {
578
+ return element.type || "input";
579
+ }
580
+ if (element instanceof HTMLTextAreaElement) {
581
+ return "textarea";
582
+ }
583
+ if (element instanceof HTMLSelectElement) {
584
+ return "select";
585
+ }
586
+ if (element instanceof HTMLElement && element.isContentEditable) {
587
+ return "contenteditable";
588
+ }
589
+ return null;
590
+ };
591
+ export const findParentFormId = (element) => {
592
+ if (element instanceof HTMLInputElement ||
593
+ element instanceof HTMLTextAreaElement ||
594
+ element instanceof HTMLSelectElement) {
595
+ const form = element.form;
596
+ return form ? form.id || form.getAttribute("name") || null : null;
597
+ }
598
+ if (element instanceof HTMLElement) {
599
+ const form = element.closest("form");
600
+ return form instanceof HTMLFormElement
601
+ ? form.id || form.getAttribute("name") || null
602
+ : null;
603
+ }
604
+ return null;
605
+ };
606
+ export const isEditableElement = (element) => {
607
+ return Boolean(element instanceof HTMLInputElement ||
608
+ element instanceof HTMLTextAreaElement ||
609
+ element instanceof HTMLSelectElement ||
610
+ (element instanceof HTMLElement && element.isContentEditable));
611
+ };
612
+ export const getElementValue = (element) => {
613
+ if (element instanceof HTMLInputElement) {
614
+ if (element.type === "checkbox") {
615
+ return element.checked;
616
+ }
617
+ if (element.type === "radio") {
618
+ return element.checked ? element.value : null;
619
+ }
620
+ if (element.type === "file") {
621
+ return {
622
+ fileCount: element.files?.length ?? 0,
623
+ };
624
+ }
625
+ return element.value;
626
+ }
627
+ if (element instanceof HTMLTextAreaElement) {
628
+ return element.value;
629
+ }
630
+ if (element instanceof HTMLSelectElement) {
631
+ if (element.multiple) {
632
+ return Array.from(element.selectedOptions).map((option) => option.value);
633
+ }
634
+ return element.value;
635
+ }
636
+ if (element instanceof HTMLElement && element.isContentEditable) {
637
+ return element.textContent?.trim() ?? null;
638
+ }
639
+ return null;
640
+ };
@@ -0,0 +1,2 @@
1
+ import type { CaptureModuleContext, CaptureStopHandle } from "../provider/types.js";
2
+ export declare const startDragCapture: (context: CaptureModuleContext) => CaptureStopHandle;