persona-dsl 26.1.20.8__py3-none-any.whl
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.
- persona_dsl/__init__.py +35 -0
- persona_dsl/components/action.py +10 -0
- persona_dsl/components/base_step.py +251 -0
- persona_dsl/components/combined_step.py +68 -0
- persona_dsl/components/expectation.py +10 -0
- persona_dsl/components/fact.py +10 -0
- persona_dsl/components/goal.py +10 -0
- persona_dsl/components/ops.py +7 -0
- persona_dsl/components/step.py +75 -0
- persona_dsl/expectations/generic/__init__.py +15 -0
- persona_dsl/expectations/generic/contains_item.py +19 -0
- persona_dsl/expectations/generic/contains_the_text.py +15 -0
- persona_dsl/expectations/generic/has_entries.py +21 -0
- persona_dsl/expectations/generic/is_equal.py +24 -0
- persona_dsl/expectations/generic/is_greater_than.py +18 -0
- persona_dsl/expectations/generic/path_equal.py +27 -0
- persona_dsl/expectations/web/__init__.py +5 -0
- persona_dsl/expectations/web/is_displayed.py +13 -0
- persona_dsl/expectations/web/matches_aria_snapshot.py +221 -0
- persona_dsl/expectations/web/matches_screenshot.py +160 -0
- persona_dsl/generators/__init__.py +5 -0
- persona_dsl/generators/api_generator.py +423 -0
- persona_dsl/generators/cli.py +431 -0
- persona_dsl/generators/page_generator.py +1140 -0
- persona_dsl/ops/api/__init__.py +5 -0
- persona_dsl/ops/api/json_as.py +104 -0
- persona_dsl/ops/api/json_response.py +48 -0
- persona_dsl/ops/api/send_request.py +41 -0
- persona_dsl/ops/db/__init__.py +5 -0
- persona_dsl/ops/db/execute_sql.py +22 -0
- persona_dsl/ops/db/fetch_all.py +29 -0
- persona_dsl/ops/db/fetch_one.py +22 -0
- persona_dsl/ops/kafka/__init__.py +4 -0
- persona_dsl/ops/kafka/message_in_topic.py +89 -0
- persona_dsl/ops/kafka/send_message.py +35 -0
- persona_dsl/ops/soap/__init__.py +4 -0
- persona_dsl/ops/soap/call_operation.py +24 -0
- persona_dsl/ops/soap/operation_result.py +24 -0
- persona_dsl/ops/web/__init__.py +37 -0
- persona_dsl/ops/web/aria_snapshot.py +87 -0
- persona_dsl/ops/web/click.py +30 -0
- persona_dsl/ops/web/current_path.py +17 -0
- persona_dsl/ops/web/element_attribute.py +24 -0
- persona_dsl/ops/web/element_is_visible.py +27 -0
- persona_dsl/ops/web/element_text.py +28 -0
- persona_dsl/ops/web/elements_count.py +42 -0
- persona_dsl/ops/web/fill.py +41 -0
- persona_dsl/ops/web/generate_page_object.py +118 -0
- persona_dsl/ops/web/input_value.py +23 -0
- persona_dsl/ops/web/navigate.py +52 -0
- persona_dsl/ops/web/press_key.py +37 -0
- persona_dsl/ops/web/rich_aria_snapshot.py +146 -0
- persona_dsl/ops/web/screenshot.py +68 -0
- persona_dsl/ops/web/table_data.py +43 -0
- persona_dsl/ops/web/wait_for_navigation.py +23 -0
- persona_dsl/pages/__init__.py +133 -0
- persona_dsl/pages/elements.py +998 -0
- persona_dsl/pages/page.py +44 -0
- persona_dsl/pages/virtual_page.py +94 -0
- persona_dsl/persona.py +125 -0
- persona_dsl/pytest_plugin.py +1064 -0
- persona_dsl/runtime/dist/persona_bundle.js +1077 -0
- persona_dsl/skills/__init__.py +7 -0
- persona_dsl/skills/core/base.py +41 -0
- persona_dsl/skills/core/skill_definition.py +30 -0
- persona_dsl/skills/use_api.py +251 -0
- persona_dsl/skills/use_browser.py +78 -0
- persona_dsl/skills/use_database.py +129 -0
- persona_dsl/skills/use_kafka.py +135 -0
- persona_dsl/skills/use_soap.py +66 -0
- persona_dsl/utils/__init__.py +0 -0
- persona_dsl/utils/artifacts.py +22 -0
- persona_dsl/utils/config.py +54 -0
- persona_dsl/utils/data_providers.py +159 -0
- persona_dsl/utils/decorators.py +80 -0
- persona_dsl/utils/metrics.py +69 -0
- persona_dsl/utils/naming.py +14 -0
- persona_dsl/utils/path.py +202 -0
- persona_dsl/utils/retry.py +51 -0
- persona_dsl/utils/taas_integration.py +124 -0
- persona_dsl/utils/waits.py +112 -0
- persona_dsl-26.1.20.8.dist-info/METADATA +35 -0
- persona_dsl-26.1.20.8.dist-info/RECORD +86 -0
- persona_dsl-26.1.20.8.dist-info/WHEEL +5 -0
- persona_dsl-26.1.20.8.dist-info/entry_points.txt +6 -0
- persona_dsl-26.1.20.8.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,1077 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
(() => {
|
|
3
|
+
// src/utils/hash.ts
|
|
4
|
+
function hashString(str) {
|
|
5
|
+
let hash = 5381;
|
|
6
|
+
for (let i = 0; i < str.length; i++) {
|
|
7
|
+
hash = (hash << 5) + hash + str.charCodeAt(i);
|
|
8
|
+
}
|
|
9
|
+
return (hash >>> 0).toString(36);
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// src/core/IdGenerator.ts
|
|
13
|
+
var IdGenerator = class {
|
|
14
|
+
/**
|
|
15
|
+
* Generates a deterministic ID for an element based on its context.
|
|
16
|
+
* Includes siblingIndex to ensure uniqueness for identical siblings.
|
|
17
|
+
*/
|
|
18
|
+
generate(parentId, role, element, siblingIndex) {
|
|
19
|
+
const tag = element.tagName.toLowerCase();
|
|
20
|
+
const idAttr = element.getAttribute("id") || "";
|
|
21
|
+
const testId = element.getAttribute("data-test-id") || element.getAttribute("data-testid") || "";
|
|
22
|
+
const href = element.getAttribute("href") || "";
|
|
23
|
+
const accessibleName = (element.getAttribute("aria-label") || "").slice(0, 40);
|
|
24
|
+
const safeName = accessibleName.replace(/\s+/g, "_");
|
|
25
|
+
const localSignature = [
|
|
26
|
+
tag,
|
|
27
|
+
role || "",
|
|
28
|
+
idAttr,
|
|
29
|
+
testId,
|
|
30
|
+
href,
|
|
31
|
+
safeName,
|
|
32
|
+
siblingIndex
|
|
33
|
+
// Critical for uniqueness
|
|
34
|
+
].join("|");
|
|
35
|
+
const raw = `${parentId}:${localSignature}`;
|
|
36
|
+
return hashString(raw);
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
// src/utils/dom.ts
|
|
41
|
+
var cacheStyle;
|
|
42
|
+
var cacheStyleBefore;
|
|
43
|
+
var cacheStyleAfter;
|
|
44
|
+
var domCachesCounter = 0;
|
|
45
|
+
function beginDOMCaches() {
|
|
46
|
+
++domCachesCounter;
|
|
47
|
+
cacheStyle || (cacheStyle = /* @__PURE__ */ new Map());
|
|
48
|
+
cacheStyleBefore || (cacheStyleBefore = /* @__PURE__ */ new Map());
|
|
49
|
+
cacheStyleAfter || (cacheStyleAfter = /* @__PURE__ */ new Map());
|
|
50
|
+
}
|
|
51
|
+
function endDOMCaches() {
|
|
52
|
+
if (!--domCachesCounter) {
|
|
53
|
+
cacheStyle = void 0;
|
|
54
|
+
cacheStyleBefore = void 0;
|
|
55
|
+
cacheStyleAfter = void 0;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function getElementComputedStyle(element, pseudo) {
|
|
59
|
+
const cache = pseudo === "::before" ? cacheStyleBefore : pseudo === "::after" ? cacheStyleAfter : cacheStyle;
|
|
60
|
+
if (cache && cache.has(element))
|
|
61
|
+
return cache.get(element);
|
|
62
|
+
const style = element.ownerDocument && element.ownerDocument.defaultView ? element.ownerDocument.defaultView.getComputedStyle(element, pseudo) : void 0;
|
|
63
|
+
cache && style && cache.set(element, style);
|
|
64
|
+
return style;
|
|
65
|
+
}
|
|
66
|
+
function isElementStyleVisibilityVisible(element, style) {
|
|
67
|
+
style = style || getElementComputedStyle(element);
|
|
68
|
+
if (!style)
|
|
69
|
+
return true;
|
|
70
|
+
if (Element.prototype.checkVisibility) {
|
|
71
|
+
if (!element.checkVisibility())
|
|
72
|
+
return false;
|
|
73
|
+
} else {
|
|
74
|
+
const detailsOrSummary = element.closest("details,summary");
|
|
75
|
+
if (detailsOrSummary !== element && detailsOrSummary && detailsOrSummary.nodeName === "DETAILS" && !detailsOrSummary.open)
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
if (style.visibility !== "visible")
|
|
79
|
+
return false;
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
function isVisibleTextNode(node) {
|
|
83
|
+
if (node.nodeType !== Node.TEXT_NODE)
|
|
84
|
+
return false;
|
|
85
|
+
const range = node.ownerDocument.createRange();
|
|
86
|
+
range.selectNode(node);
|
|
87
|
+
const rect = range.getBoundingClientRect();
|
|
88
|
+
return rect.width > 0 && rect.height > 0;
|
|
89
|
+
}
|
|
90
|
+
function elementSafeTagName(element) {
|
|
91
|
+
const tagName = element.tagName;
|
|
92
|
+
if (typeof tagName === "string")
|
|
93
|
+
return tagName.toUpperCase();
|
|
94
|
+
if (element instanceof HTMLFormElement)
|
|
95
|
+
return "FORM";
|
|
96
|
+
return element.tagName.toUpperCase();
|
|
97
|
+
}
|
|
98
|
+
function parentElementOrShadowHost(element) {
|
|
99
|
+
if (element.parentElement)
|
|
100
|
+
return element.parentElement;
|
|
101
|
+
if (!element.parentNode)
|
|
102
|
+
return void 0;
|
|
103
|
+
if (element.parentNode.nodeType === 11 && element.parentNode.host)
|
|
104
|
+
return element.parentNode.host;
|
|
105
|
+
return void 0;
|
|
106
|
+
}
|
|
107
|
+
function enclosingShadowRootOrDocument(element) {
|
|
108
|
+
let node = element;
|
|
109
|
+
while (node && node.parentNode)
|
|
110
|
+
node = node.parentNode;
|
|
111
|
+
if (node && (node.nodeType === 11 || node.nodeType === 9))
|
|
112
|
+
return node;
|
|
113
|
+
return void 0;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// src/utils/aria.ts
|
|
117
|
+
function hasExplicitAccessibleName(e) {
|
|
118
|
+
return e.hasAttribute("aria-label") || e.hasAttribute("aria-labelledby");
|
|
119
|
+
}
|
|
120
|
+
var kAncestorPreventingLandmark = "article:not([role]), aside:not([role]), main:not([role]), nav:not([role]), section:not([role]), [role=article], [role=complementary], [role=main], [role=navigation], [role=region]";
|
|
121
|
+
var kGlobalAriaAttributes = [
|
|
122
|
+
["aria-atomic", void 0],
|
|
123
|
+
["aria-busy", void 0],
|
|
124
|
+
["aria-controls", void 0],
|
|
125
|
+
["aria-current", void 0],
|
|
126
|
+
["aria-describedby", void 0],
|
|
127
|
+
["aria-details", void 0],
|
|
128
|
+
["aria-dropeffect", void 0],
|
|
129
|
+
["aria-flowto", void 0],
|
|
130
|
+
["aria-grabbed", void 0],
|
|
131
|
+
["aria-hidden", void 0],
|
|
132
|
+
["aria-keyshortcuts", void 0],
|
|
133
|
+
["aria-label", ["caption", "code", "deletion", "emphasis", "generic", "insertion", "paragraph", "presentation", "strong", "subscript", "superscript"]],
|
|
134
|
+
["aria-labelledby", ["caption", "code", "deletion", "emphasis", "generic", "insertion", "paragraph", "presentation", "strong", "subscript", "superscript"]],
|
|
135
|
+
["aria-live", void 0],
|
|
136
|
+
["aria-owns", void 0],
|
|
137
|
+
["aria-relevant", void 0],
|
|
138
|
+
["aria-roledescription", ["generic"]]
|
|
139
|
+
];
|
|
140
|
+
function hasGlobalAriaAttribute(element, forRole) {
|
|
141
|
+
return kGlobalAriaAttributes.some(([attr, prohibited]) => {
|
|
142
|
+
return !(prohibited || []).includes(forRole || "") && element.hasAttribute(attr);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function hasTabIndex(element) {
|
|
146
|
+
return !Number.isNaN(Number(String(element.getAttribute("tabindex"))));
|
|
147
|
+
}
|
|
148
|
+
function isNativelyDisabled(element) {
|
|
149
|
+
const tag = elementSafeTagName(element);
|
|
150
|
+
const isNativeFormControl = ["BUTTON", "INPUT", "SELECT", "TEXTAREA", "OPTION", "OPTGROUP"].includes(tag);
|
|
151
|
+
if (!isNativeFormControl)
|
|
152
|
+
return false;
|
|
153
|
+
if (element.hasAttribute("disabled"))
|
|
154
|
+
return true;
|
|
155
|
+
if (tag === "OPTION" && element.closest("OPTGROUP[DISABLED]"))
|
|
156
|
+
return true;
|
|
157
|
+
const fieldSetElement = element.closest("FIELDSET[DISABLED]");
|
|
158
|
+
if (!fieldSetElement)
|
|
159
|
+
return false;
|
|
160
|
+
const legendElement = fieldSetElement.querySelector(":scope > LEGEND");
|
|
161
|
+
return !legendElement || !legendElement.contains(element);
|
|
162
|
+
}
|
|
163
|
+
function isFocusable(element) {
|
|
164
|
+
const tagName = elementSafeTagName(element);
|
|
165
|
+
const nativelyFocusable = ["BUTTON", "DETAILS", "SELECT", "TEXTAREA"].includes(tagName) || (tagName === "A" || tagName === "AREA") && element.hasAttribute("href") || tagName === "INPUT" && !element.hidden;
|
|
166
|
+
return !isNativelyDisabled(element) && (nativelyFocusable || hasTabIndex(element));
|
|
167
|
+
}
|
|
168
|
+
var kImplicitRoleByTagName = {
|
|
169
|
+
"A": (e) => e.hasAttribute("href") ? "link" : null,
|
|
170
|
+
"AREA": (e) => e.hasAttribute("href") ? "link" : null,
|
|
171
|
+
"ARTICLE": () => "article",
|
|
172
|
+
"ASIDE": () => "complementary",
|
|
173
|
+
"BLOCKQUOTE": () => "blockquote",
|
|
174
|
+
"BUTTON": () => "button",
|
|
175
|
+
"CAPTION": () => "caption",
|
|
176
|
+
"CODE": () => "code",
|
|
177
|
+
"DATALIST": () => "listbox",
|
|
178
|
+
"DEL": () => "deletion",
|
|
179
|
+
"DETAILS": () => "group",
|
|
180
|
+
"DFN": () => "term",
|
|
181
|
+
"DIALOG": () => "dialog",
|
|
182
|
+
"DT": () => "term",
|
|
183
|
+
"EM": () => "emphasis",
|
|
184
|
+
"FIELDSET": () => "group",
|
|
185
|
+
"FIGURE": () => "figure",
|
|
186
|
+
"FOOTER": (e) => e.closest(kAncestorPreventingLandmark) ? null : "contentinfo",
|
|
187
|
+
"FORM": (e) => hasExplicitAccessibleName(e) ? "form" : null,
|
|
188
|
+
"H1": () => "heading",
|
|
189
|
+
"H2": () => "heading",
|
|
190
|
+
"H3": () => "heading",
|
|
191
|
+
"H4": () => "heading",
|
|
192
|
+
"H5": () => "heading",
|
|
193
|
+
"H6": () => "heading",
|
|
194
|
+
"HEADER": (e) => e.closest(kAncestorPreventingLandmark) ? null : "banner",
|
|
195
|
+
"HR": () => "separator",
|
|
196
|
+
"HTML": () => "document",
|
|
197
|
+
"IMG": (e) => e.getAttribute("alt") === "" && !e.getAttribute("title") && !hasGlobalAriaAttribute(e) && !hasTabIndex(e) ? "presentation" : "img",
|
|
198
|
+
"INPUT": (e) => {
|
|
199
|
+
const type = e.type.toLowerCase();
|
|
200
|
+
if (type === "search")
|
|
201
|
+
return e.hasAttribute("list") ? "combobox" : "searchbox";
|
|
202
|
+
if (["email", "tel", "text", "url", ""].includes(type)) {
|
|
203
|
+
const listId = e.getAttribute("list");
|
|
204
|
+
const root = enclosingShadowRootOrDocument(e);
|
|
205
|
+
const list = listId && root ? root.querySelector("#" + CSS.escape(listId)) : null;
|
|
206
|
+
return list && elementSafeTagName(list) === "DATALIST" ? "combobox" : "textbox";
|
|
207
|
+
}
|
|
208
|
+
if (type === "hidden")
|
|
209
|
+
return null;
|
|
210
|
+
if (type === "file")
|
|
211
|
+
return "button";
|
|
212
|
+
const inputTypeToRole = {
|
|
213
|
+
"button": "button",
|
|
214
|
+
"checkbox": "checkbox",
|
|
215
|
+
"image": "button",
|
|
216
|
+
"number": "spinbutton",
|
|
217
|
+
"radio": "radio",
|
|
218
|
+
"range": "slider",
|
|
219
|
+
"reset": "button",
|
|
220
|
+
"submit": "button"
|
|
221
|
+
};
|
|
222
|
+
return inputTypeToRole[type] || "textbox";
|
|
223
|
+
},
|
|
224
|
+
"INS": () => "insertion",
|
|
225
|
+
"LI": () => "listitem",
|
|
226
|
+
"MAIN": () => "main",
|
|
227
|
+
"MARK": () => "mark",
|
|
228
|
+
"MATH": () => "math",
|
|
229
|
+
"MENU": () => "list",
|
|
230
|
+
"METER": () => "meter",
|
|
231
|
+
"NAV": () => "navigation",
|
|
232
|
+
"OL": () => "list",
|
|
233
|
+
"OPTGROUP": () => "group",
|
|
234
|
+
"OPTION": () => "option",
|
|
235
|
+
"OUTPUT": () => "status",
|
|
236
|
+
"P": () => "paragraph",
|
|
237
|
+
"PROGRESS": () => "progressbar",
|
|
238
|
+
"SEARCH": () => "search",
|
|
239
|
+
"SECTION": (e) => hasExplicitAccessibleName(e) ? "region" : null,
|
|
240
|
+
"SELECT": (e) => e.hasAttribute("multiple") || e.size > 1 ? "listbox" : "combobox",
|
|
241
|
+
"STRONG": () => "strong",
|
|
242
|
+
"SUB": () => "subscript",
|
|
243
|
+
"SUP": () => "superscript",
|
|
244
|
+
"SVG": () => "img",
|
|
245
|
+
"TABLE": () => "table",
|
|
246
|
+
"TBODY": () => "rowgroup",
|
|
247
|
+
"TD": (e) => {
|
|
248
|
+
const table = e.closest("table");
|
|
249
|
+
const role = table ? getExplicitAriaRole(table) : "";
|
|
250
|
+
return role === "grid" || role === "treegrid" ? "gridcell" : "cell";
|
|
251
|
+
},
|
|
252
|
+
"TEXTAREA": () => "textbox",
|
|
253
|
+
"TFOOT": () => "rowgroup",
|
|
254
|
+
"TH": (e) => {
|
|
255
|
+
const scope = e.getAttribute("scope");
|
|
256
|
+
if (scope === "col" || scope === "colgroup")
|
|
257
|
+
return "columnheader";
|
|
258
|
+
if (scope === "row" || scope === "rowgroup")
|
|
259
|
+
return "rowheader";
|
|
260
|
+
return "columnheader";
|
|
261
|
+
},
|
|
262
|
+
"THEAD": () => "rowgroup",
|
|
263
|
+
"TIME": () => "time",
|
|
264
|
+
"TR": () => "row",
|
|
265
|
+
"UL": () => "list"
|
|
266
|
+
};
|
|
267
|
+
var validRoles = [
|
|
268
|
+
"alert",
|
|
269
|
+
"alertdialog",
|
|
270
|
+
"application",
|
|
271
|
+
"article",
|
|
272
|
+
"banner",
|
|
273
|
+
"blockquote",
|
|
274
|
+
"button",
|
|
275
|
+
"caption",
|
|
276
|
+
"cell",
|
|
277
|
+
"checkbox",
|
|
278
|
+
"code",
|
|
279
|
+
"columnheader",
|
|
280
|
+
"combobox",
|
|
281
|
+
"complementary",
|
|
282
|
+
"contentinfo",
|
|
283
|
+
"definition",
|
|
284
|
+
"deletion",
|
|
285
|
+
"dialog",
|
|
286
|
+
"directory",
|
|
287
|
+
"document",
|
|
288
|
+
"emphasis",
|
|
289
|
+
"feed",
|
|
290
|
+
"figure",
|
|
291
|
+
"form",
|
|
292
|
+
"generic",
|
|
293
|
+
"grid",
|
|
294
|
+
"gridcell",
|
|
295
|
+
"group",
|
|
296
|
+
"heading",
|
|
297
|
+
"img",
|
|
298
|
+
"insertion",
|
|
299
|
+
"link",
|
|
300
|
+
"list",
|
|
301
|
+
"listbox",
|
|
302
|
+
"listitem",
|
|
303
|
+
"log",
|
|
304
|
+
"main",
|
|
305
|
+
"mark",
|
|
306
|
+
"marquee",
|
|
307
|
+
"math",
|
|
308
|
+
"meter",
|
|
309
|
+
"menu",
|
|
310
|
+
"menubar",
|
|
311
|
+
"menuitem",
|
|
312
|
+
"menuitemcheckbox",
|
|
313
|
+
"menuitemradio",
|
|
314
|
+
"navigation",
|
|
315
|
+
"none",
|
|
316
|
+
"note",
|
|
317
|
+
"option",
|
|
318
|
+
"paragraph",
|
|
319
|
+
"presentation",
|
|
320
|
+
"progressbar",
|
|
321
|
+
"radio",
|
|
322
|
+
"radiogroup",
|
|
323
|
+
"region",
|
|
324
|
+
"row",
|
|
325
|
+
"rowgroup",
|
|
326
|
+
"rowheader",
|
|
327
|
+
"scrollbar",
|
|
328
|
+
"search",
|
|
329
|
+
"searchbox",
|
|
330
|
+
"separator",
|
|
331
|
+
"slider",
|
|
332
|
+
"spinbutton",
|
|
333
|
+
"status",
|
|
334
|
+
"strong",
|
|
335
|
+
"subscript",
|
|
336
|
+
"superscript",
|
|
337
|
+
"switch",
|
|
338
|
+
"tab",
|
|
339
|
+
"table",
|
|
340
|
+
"tablist",
|
|
341
|
+
"tabpanel",
|
|
342
|
+
"term",
|
|
343
|
+
"textbox",
|
|
344
|
+
"time",
|
|
345
|
+
"timer",
|
|
346
|
+
"toolbar",
|
|
347
|
+
"tooltip",
|
|
348
|
+
"tree",
|
|
349
|
+
"treegrid",
|
|
350
|
+
"treeitem"
|
|
351
|
+
];
|
|
352
|
+
function getExplicitAriaRole(element) {
|
|
353
|
+
const roles = (element.getAttribute("role") || "").split(" ").map((r) => r.trim());
|
|
354
|
+
return roles.find((role) => validRoles.includes(role)) || null;
|
|
355
|
+
}
|
|
356
|
+
var kPresentationInheritanceParents = {
|
|
357
|
+
"DD": ["DL", "DIV"],
|
|
358
|
+
"DIV": ["DL"],
|
|
359
|
+
"DT": ["DL", "DIV"],
|
|
360
|
+
"LI": ["OL", "UL"],
|
|
361
|
+
"TBODY": ["TABLE"],
|
|
362
|
+
"TD": ["TR"],
|
|
363
|
+
"TFOOT": ["TABLE"],
|
|
364
|
+
"TH": ["TR"],
|
|
365
|
+
"THEAD": ["TABLE"],
|
|
366
|
+
"TR": ["THEAD", "TBODY", "TFOOT", "TABLE"]
|
|
367
|
+
};
|
|
368
|
+
function getImplicitAriaRole(element) {
|
|
369
|
+
const implicit = (kImplicitRoleByTagName[elementSafeTagName(element)] || (() => null))(element) || "";
|
|
370
|
+
if (!implicit)
|
|
371
|
+
return null;
|
|
372
|
+
let ancestor = element;
|
|
373
|
+
while (ancestor) {
|
|
374
|
+
const parent = parentElementOrShadowHost(ancestor);
|
|
375
|
+
if (!parent)
|
|
376
|
+
break;
|
|
377
|
+
const parents = kPresentationInheritanceParents[elementSafeTagName(ancestor)];
|
|
378
|
+
if (!parents || !parents.includes(elementSafeTagName(parent)))
|
|
379
|
+
break;
|
|
380
|
+
const parentExplicitRole = getExplicitAriaRole(parent);
|
|
381
|
+
if ((parentExplicitRole === "none" || parentExplicitRole === "presentation") && hasPresentationConflictResolution(parent, parentExplicitRole))
|
|
382
|
+
return parentExplicitRole;
|
|
383
|
+
ancestor = parent;
|
|
384
|
+
}
|
|
385
|
+
return implicit;
|
|
386
|
+
}
|
|
387
|
+
function hasPresentationConflictResolution(element, role) {
|
|
388
|
+
return hasGlobalAriaAttribute(element, role) || isFocusable(element);
|
|
389
|
+
}
|
|
390
|
+
function getAriaRole(element) {
|
|
391
|
+
const explicitRole = getExplicitAriaRole(element);
|
|
392
|
+
if (!explicitRole)
|
|
393
|
+
return getImplicitAriaRole(element);
|
|
394
|
+
if (explicitRole === "none" || explicitRole === "presentation") {
|
|
395
|
+
const implicitRole = getImplicitAriaRole(element);
|
|
396
|
+
if (implicitRole && hasPresentationConflictResolution(element, implicitRole))
|
|
397
|
+
return implicitRole;
|
|
398
|
+
}
|
|
399
|
+
return explicitRole;
|
|
400
|
+
}
|
|
401
|
+
function getAriaBoolean(attr) {
|
|
402
|
+
return attr === null ? void 0 : attr.toLowerCase() === "true";
|
|
403
|
+
}
|
|
404
|
+
function isElementIgnoredForAria(element) {
|
|
405
|
+
const tag = elementSafeTagName(element);
|
|
406
|
+
return ["STYLE", "SCRIPT", "NOSCRIPT", "TEMPLATE"].includes(tag);
|
|
407
|
+
}
|
|
408
|
+
var cacheIsHidden;
|
|
409
|
+
function belongsToDisplayNoneOrAriaHiddenOrNonSlotted(element) {
|
|
410
|
+
let hidden = cacheIsHidden && cacheIsHidden.get(element);
|
|
411
|
+
if (hidden === void 0) {
|
|
412
|
+
hidden = false;
|
|
413
|
+
if (element.parentElement && element.parentElement.shadowRoot && !element.slot)
|
|
414
|
+
hidden = true;
|
|
415
|
+
if (!hidden) {
|
|
416
|
+
const style = getElementComputedStyle(element);
|
|
417
|
+
hidden = !style || style.display === "none" || getAriaBoolean(element.getAttribute("aria-hidden")) === true;
|
|
418
|
+
}
|
|
419
|
+
if (!hidden) {
|
|
420
|
+
const parent = parentElementOrShadowHost(element);
|
|
421
|
+
if (parent)
|
|
422
|
+
hidden = belongsToDisplayNoneOrAriaHiddenOrNonSlotted(parent);
|
|
423
|
+
}
|
|
424
|
+
cacheIsHidden && cacheIsHidden.set(element, hidden);
|
|
425
|
+
}
|
|
426
|
+
return hidden;
|
|
427
|
+
}
|
|
428
|
+
function isElementHiddenForAria(element) {
|
|
429
|
+
if (element === (element.ownerDocument && element.ownerDocument.body))
|
|
430
|
+
return false;
|
|
431
|
+
if (isElementIgnoredForAria(element))
|
|
432
|
+
return true;
|
|
433
|
+
const style = getElementComputedStyle(element);
|
|
434
|
+
const isSlot = element.nodeName === "SLOT";
|
|
435
|
+
if (style && style.display === "contents" && !isSlot) {
|
|
436
|
+
for (let child = element.firstChild; child; child = child.nextSibling) {
|
|
437
|
+
if (child.nodeType === 1 && !isElementHiddenForAria(child))
|
|
438
|
+
return false;
|
|
439
|
+
if (child.nodeType === 3 && isVisibleTextNode(child))
|
|
440
|
+
return false;
|
|
441
|
+
}
|
|
442
|
+
return true;
|
|
443
|
+
}
|
|
444
|
+
const isOptionInsideSelect = element.nodeName === "OPTION" && !!element.closest("select");
|
|
445
|
+
if (!isOptionInsideSelect && !isSlot && !isElementStyleVisibilityVisible(element, style))
|
|
446
|
+
return true;
|
|
447
|
+
return belongsToDisplayNoneOrAriaHiddenOrNonSlotted(element);
|
|
448
|
+
}
|
|
449
|
+
function getIdRefs(element, ref) {
|
|
450
|
+
if (!ref)
|
|
451
|
+
return [];
|
|
452
|
+
const root = enclosingShadowRootOrDocument(element);
|
|
453
|
+
if (!root)
|
|
454
|
+
return [];
|
|
455
|
+
try {
|
|
456
|
+
const ids = ref.split(" ").filter((id) => !!id);
|
|
457
|
+
const result = [];
|
|
458
|
+
for (const id of ids) {
|
|
459
|
+
const el = root.querySelector("#" + CSS.escape(id));
|
|
460
|
+
if (el && !result.includes(el))
|
|
461
|
+
result.push(el);
|
|
462
|
+
}
|
|
463
|
+
return result;
|
|
464
|
+
} catch (e) {
|
|
465
|
+
return [];
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
var cacheAccessibleName;
|
|
469
|
+
var cacheAccessibleNameHidden;
|
|
470
|
+
function beginAriaCaches() {
|
|
471
|
+
cacheAccessibleName = /* @__PURE__ */ new Map();
|
|
472
|
+
cacheAccessibleNameHidden = /* @__PURE__ */ new Map();
|
|
473
|
+
cacheIsHidden = /* @__PURE__ */ new Map();
|
|
474
|
+
}
|
|
475
|
+
function endAriaCaches() {
|
|
476
|
+
cacheAccessibleName = void 0;
|
|
477
|
+
cacheAccessibleNameHidden = void 0;
|
|
478
|
+
cacheIsHidden = void 0;
|
|
479
|
+
}
|
|
480
|
+
function asFlatString(s) {
|
|
481
|
+
return s.split("\xA0").map((chunk) => chunk.replace(/\r\n/g, "\n").replace(/[\u200b\u00ad]/g, "").replace(/\s\s*/g, " ")).join("\xA0").trim();
|
|
482
|
+
}
|
|
483
|
+
var kRolesAllowNameFromContent = /* @__PURE__ */ new Set([
|
|
484
|
+
// Interactive + "meaningful" roles where name-from-content is expected even with nested markup.
|
|
485
|
+
"button",
|
|
486
|
+
"link",
|
|
487
|
+
"menuitem",
|
|
488
|
+
"menuitemcheckbox",
|
|
489
|
+
"menuitemradio",
|
|
490
|
+
"checkbox",
|
|
491
|
+
"radio",
|
|
492
|
+
"switch",
|
|
493
|
+
"tab",
|
|
494
|
+
"option",
|
|
495
|
+
"textbox",
|
|
496
|
+
"searchbox",
|
|
497
|
+
"combobox",
|
|
498
|
+
"heading",
|
|
499
|
+
// Common structural leaves.
|
|
500
|
+
"cell",
|
|
501
|
+
"gridcell",
|
|
502
|
+
"rowheader",
|
|
503
|
+
"columnheader"
|
|
504
|
+
]);
|
|
505
|
+
function getCSSContent(element, pseudo) {
|
|
506
|
+
const style = getElementComputedStyle(element, pseudo);
|
|
507
|
+
if (!style)
|
|
508
|
+
return void 0;
|
|
509
|
+
const raw = style.getPropertyValue("content");
|
|
510
|
+
const content = (raw || "").trim();
|
|
511
|
+
if (!content || content === "none" || content === "normal")
|
|
512
|
+
return void 0;
|
|
513
|
+
if (content.startsWith('"') && content.endsWith('"') || content.startsWith("'") && content.endsWith("'")) {
|
|
514
|
+
return content.slice(1, -1);
|
|
515
|
+
}
|
|
516
|
+
return void 0;
|
|
517
|
+
}
|
|
518
|
+
function getElementAccessibleName(element, includeHidden) {
|
|
519
|
+
const cache = includeHidden ? cacheAccessibleNameHidden : cacheAccessibleName;
|
|
520
|
+
if (cache && cache.has(element))
|
|
521
|
+
return cache.get(element);
|
|
522
|
+
const role = getAriaRole(element) || "generic";
|
|
523
|
+
const elementProhibitsNaming = [
|
|
524
|
+
"caption",
|
|
525
|
+
"code",
|
|
526
|
+
"definition",
|
|
527
|
+
"deletion",
|
|
528
|
+
"emphasis",
|
|
529
|
+
// важно: НЕ блокируем generic/paragraph/mark/etc. — текст этих контейнеров нужен как имя,
|
|
530
|
+
// если у них нет дочерних ELEMENT_NODE. Поэтому сюда не добавляем 'generic', 'paragraph', и т.п.
|
|
531
|
+
"presentation",
|
|
532
|
+
"strong",
|
|
533
|
+
"subscript",
|
|
534
|
+
"suggestion",
|
|
535
|
+
"superscript",
|
|
536
|
+
"term",
|
|
537
|
+
"time",
|
|
538
|
+
"menu",
|
|
539
|
+
"menubar"
|
|
540
|
+
];
|
|
541
|
+
const hasExplicitName = hasExplicitAccessibleName(element);
|
|
542
|
+
const hasElementChildren = !!element.firstElementChild || !!(element.shadowRoot && element.shadowRoot.firstElementChild);
|
|
543
|
+
if (!hasExplicitName && hasElementChildren) {
|
|
544
|
+
const isNonSemantic = role === "generic" || role === "paragraph";
|
|
545
|
+
if (isNonSemantic || !kRolesAllowNameFromContent.has(role)) {
|
|
546
|
+
cache && cache.set(element, "");
|
|
547
|
+
return "";
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
if (elementProhibitsNaming.includes(role)) {
|
|
551
|
+
cache && cache.set(element, "");
|
|
552
|
+
return "";
|
|
553
|
+
}
|
|
554
|
+
const name = asFlatString(
|
|
555
|
+
getTextAlternativeInternal(element, {
|
|
556
|
+
includeHidden,
|
|
557
|
+
visitedElements: /* @__PURE__ */ new Set(),
|
|
558
|
+
embeddedInTargetElement: "self"
|
|
559
|
+
})
|
|
560
|
+
);
|
|
561
|
+
cache && cache.set(element, name);
|
|
562
|
+
return name;
|
|
563
|
+
}
|
|
564
|
+
function innerAccumulatedElementText(element, options) {
|
|
565
|
+
const tokens = [];
|
|
566
|
+
function visit(node, skipSlotted) {
|
|
567
|
+
if (skipSlotted && node.assignedSlot)
|
|
568
|
+
return;
|
|
569
|
+
if (node.nodeType === 1) {
|
|
570
|
+
const display = (getElementComputedStyle(node) || {}).display || "inline";
|
|
571
|
+
let token = getTextAlternativeInternal(node, options);
|
|
572
|
+
if (display !== "inline" || node.nodeName === "BR")
|
|
573
|
+
token = " " + token + " ";
|
|
574
|
+
tokens.push(token);
|
|
575
|
+
} else if (node.nodeType === 3) {
|
|
576
|
+
tokens.push(node.textContent || "");
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
tokens.push(getCSSContent(element, "::before") || "");
|
|
580
|
+
const content = getCSSContent(element);
|
|
581
|
+
if (content !== void 0) {
|
|
582
|
+
tokens.push(content);
|
|
583
|
+
} else {
|
|
584
|
+
const assignedNodes = element.nodeName === "SLOT" ? element.assignedNodes() : [];
|
|
585
|
+
if (assignedNodes.length) {
|
|
586
|
+
for (const child of assignedNodes)
|
|
587
|
+
visit(child, false);
|
|
588
|
+
} else {
|
|
589
|
+
for (let child = element.firstChild; child; child = child.nextSibling)
|
|
590
|
+
visit(child, true);
|
|
591
|
+
if (element.shadowRoot) {
|
|
592
|
+
for (let child = element.shadowRoot.firstChild; child; child = child.nextSibling)
|
|
593
|
+
visit(child, true);
|
|
594
|
+
}
|
|
595
|
+
for (const owned of getIdRefs(element, element.getAttribute("aria-owns")))
|
|
596
|
+
visit(owned, true);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
tokens.push(getCSSContent(element, "::after") || "");
|
|
600
|
+
return tokens.join("");
|
|
601
|
+
}
|
|
602
|
+
function getAriaLabelledByElements(element) {
|
|
603
|
+
const ref = element.getAttribute("aria-labelledby");
|
|
604
|
+
if (ref === null)
|
|
605
|
+
return null;
|
|
606
|
+
const refs = getIdRefs(element, ref);
|
|
607
|
+
return refs.length ? refs : null;
|
|
608
|
+
}
|
|
609
|
+
function getLabelElementsForControl(element) {
|
|
610
|
+
const labels = [];
|
|
611
|
+
const id = element.getAttribute("id");
|
|
612
|
+
if (id) {
|
|
613
|
+
const root = enclosingShadowRootOrDocument(element);
|
|
614
|
+
if (root) {
|
|
615
|
+
try {
|
|
616
|
+
const byFor = root.querySelectorAll('label[for="' + CSS.escape(id) + '"]');
|
|
617
|
+
byFor && byFor.forEach((n) => {
|
|
618
|
+
if (!labels.includes(n))
|
|
619
|
+
labels.push(n);
|
|
620
|
+
});
|
|
621
|
+
} catch (e) {
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
let parent = element.parentElement;
|
|
626
|
+
while (parent) {
|
|
627
|
+
if (parent.tagName.toUpperCase() === "LABEL") {
|
|
628
|
+
if (!labels.includes(parent))
|
|
629
|
+
labels.push(parent);
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
parent = parentElementOrShadowHost(parent) || parent.parentElement;
|
|
633
|
+
}
|
|
634
|
+
return labels;
|
|
635
|
+
}
|
|
636
|
+
function getTextAlternativeInternal(element, options) {
|
|
637
|
+
if (options.visitedElements.has(element))
|
|
638
|
+
return "";
|
|
639
|
+
options.visitedElements.add(element);
|
|
640
|
+
const childOptions = {
|
|
641
|
+
...options,
|
|
642
|
+
embeddedInTargetElement: options.embeddedInTargetElement === "self" ? "descendant" : options.embeddedInTargetElement
|
|
643
|
+
};
|
|
644
|
+
if (!options.includeHidden) {
|
|
645
|
+
const hiddenRefTraversal = !!(options.embeddedInLabelledBy && options.embeddedInLabelledBy.hidden) || !!(options.embeddedInDescribedBy && options.embeddedInDescribedBy.hidden) || !!(options.embeddedInNativeTextAlternative && options.embeddedInNativeTextAlternative.hidden) || !!(options.embeddedInLabel && options.embeddedInLabel.hidden);
|
|
646
|
+
if (isElementIgnoredForAria(element) || !hiddenRefTraversal && isElementHiddenForAria(element)) {
|
|
647
|
+
return "";
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
const labelledBy = getAriaLabelledByElements(element);
|
|
651
|
+
if (!options.embeddedInLabelledBy && labelledBy) {
|
|
652
|
+
const accessibleName = labelledBy.map(
|
|
653
|
+
(ref) => getTextAlternativeInternal(ref, {
|
|
654
|
+
...options,
|
|
655
|
+
embeddedInLabelledBy: { element: ref, hidden: isElementHiddenForAria(ref) },
|
|
656
|
+
embeddedInDescribedBy: void 0,
|
|
657
|
+
embeddedInTargetElement: void 0,
|
|
658
|
+
embeddedInLabel: void 0,
|
|
659
|
+
embeddedInNativeTextAlternative: void 0
|
|
660
|
+
})
|
|
661
|
+
).join(" ");
|
|
662
|
+
if (accessibleName)
|
|
663
|
+
return accessibleName;
|
|
664
|
+
}
|
|
665
|
+
const ariaLabel = element.getAttribute("aria-label") || "";
|
|
666
|
+
if (ariaLabel.trim()) {
|
|
667
|
+
return ariaLabel;
|
|
668
|
+
}
|
|
669
|
+
const tagName = elementSafeTagName(element);
|
|
670
|
+
if (tagName === "INPUT" || tagName === "TEXTAREA" || tagName === "SELECT") {
|
|
671
|
+
const labels = getLabelElementsForControl(element);
|
|
672
|
+
if (labels.length) {
|
|
673
|
+
const labelsText = labels.map(
|
|
674
|
+
(labelEl) => innerAccumulatedElementText(labelEl, {
|
|
675
|
+
...options,
|
|
676
|
+
embeddedInLabel: { element: labelEl, hidden: isElementHiddenForAria(labelEl) }
|
|
677
|
+
})
|
|
678
|
+
).join(" ").trim();
|
|
679
|
+
if (labelsText) {
|
|
680
|
+
return labelsText;
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
return innerAccumulatedElementText(element, childOptions);
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// src/core/TreeWalker.ts
|
|
688
|
+
var TreeWalker = class {
|
|
689
|
+
constructor() {
|
|
690
|
+
this.idGenerator = new IdGenerator();
|
|
691
|
+
this.visited = /* @__PURE__ */ new Set();
|
|
692
|
+
}
|
|
693
|
+
snapshot(root) {
|
|
694
|
+
beginDOMCaches();
|
|
695
|
+
beginAriaCaches();
|
|
696
|
+
this.visited.clear();
|
|
697
|
+
try {
|
|
698
|
+
const raw = this.processNode(root, "root", 0, true);
|
|
699
|
+
if (!raw)
|
|
700
|
+
return null;
|
|
701
|
+
return this.normalizeTree(raw);
|
|
702
|
+
} finally {
|
|
703
|
+
endAriaCaches();
|
|
704
|
+
endDOMCaches();
|
|
705
|
+
this.visited.clear();
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
/**
|
|
709
|
+
* Упрощает дерево, убирая пустые generic-обёртки без собственного содержимого.
|
|
710
|
+
*
|
|
711
|
+
* Правила:
|
|
712
|
+
* - Узлы с role="generic" и пустым name, без props/статусов и с одним дочерним AriaNode
|
|
713
|
+
* схлопываются: возвращается их единственный ребёнок.
|
|
714
|
+
* - Всё остальное сохраняется как есть.
|
|
715
|
+
*/
|
|
716
|
+
normalizeTree(node) {
|
|
717
|
+
const simplify = (n) => {
|
|
718
|
+
const normalizedChildren = [];
|
|
719
|
+
for (const child of n.children) {
|
|
720
|
+
if (typeof child === "string")
|
|
721
|
+
continue;
|
|
722
|
+
normalizedChildren.push(simplify(child));
|
|
723
|
+
}
|
|
724
|
+
n.children = normalizedChildren;
|
|
725
|
+
const isPlainGeneric = n.role === "generic" && !n.name && (!n.props || Object.keys(n.props).length === 0) && !n.disabled && !n.checked && !n.expanded && !n.selected && !n.pressed && !n.level && n.children.length === 1;
|
|
726
|
+
if (isPlainGeneric) {
|
|
727
|
+
return n.children[0];
|
|
728
|
+
}
|
|
729
|
+
return n;
|
|
730
|
+
};
|
|
731
|
+
return simplify(node);
|
|
732
|
+
}
|
|
733
|
+
processNode(element, parentId, index, forceVisible = false) {
|
|
734
|
+
if (this.visited.has(element))
|
|
735
|
+
return null;
|
|
736
|
+
this.visited.add(element);
|
|
737
|
+
const hidden = isElementHiddenForAria(element);
|
|
738
|
+
const tagName = element.tagName.toUpperCase();
|
|
739
|
+
const semanticRoles = ["A", "BUTTON", "INPUT", "SELECT", "TEXTAREA", "OPTION", "LI"];
|
|
740
|
+
if (!forceVisible && hidden && !semanticRoles.includes(tagName))
|
|
741
|
+
return null;
|
|
742
|
+
const role = getAriaRole(element) || "";
|
|
743
|
+
let name = getElementAccessibleName(element, false);
|
|
744
|
+
if (role === "none" || role === "presentation") {
|
|
745
|
+
name = "";
|
|
746
|
+
}
|
|
747
|
+
let finalRole = role || "generic";
|
|
748
|
+
let linkWrapperInfo = null;
|
|
749
|
+
if (finalRole === "menuitem" && tagName === "LI") {
|
|
750
|
+
const anchor = element.querySelector("a[href]");
|
|
751
|
+
const text = anchor ? (anchor.textContent || "").trim() : "";
|
|
752
|
+
if (anchor && text && text.toLowerCase() !== "ellipsis") {
|
|
753
|
+
finalRole = "generic";
|
|
754
|
+
name = "";
|
|
755
|
+
linkWrapperInfo = { anchor, text };
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
const id = this.idGenerator.generate(parentId, finalRole, element, index);
|
|
759
|
+
element.setAttribute("data-persona-id", id);
|
|
760
|
+
const node = {
|
|
761
|
+
role: finalRole,
|
|
762
|
+
name,
|
|
763
|
+
ref: id,
|
|
764
|
+
children: [],
|
|
765
|
+
element,
|
|
766
|
+
props: {}
|
|
767
|
+
};
|
|
768
|
+
if (linkWrapperInfo) {
|
|
769
|
+
const href = linkWrapperInfo.anchor.getAttribute("href") || "";
|
|
770
|
+
const linkId = this.idGenerator.generate(id, "link", linkWrapperInfo.anchor, 0);
|
|
771
|
+
linkWrapperInfo.anchor.setAttribute("data-persona-id", linkId);
|
|
772
|
+
const linkNode = {
|
|
773
|
+
role: "link",
|
|
774
|
+
name: linkWrapperInfo.text,
|
|
775
|
+
ref: linkId,
|
|
776
|
+
children: [],
|
|
777
|
+
element: linkWrapperInfo.anchor,
|
|
778
|
+
props: {}
|
|
779
|
+
};
|
|
780
|
+
if (href)
|
|
781
|
+
linkNode.props.url = href;
|
|
782
|
+
if (hidden) {
|
|
783
|
+
linkNode.hidden = true;
|
|
784
|
+
}
|
|
785
|
+
node.children.push(linkNode);
|
|
786
|
+
this.visited.add(linkWrapperInfo.anchor);
|
|
787
|
+
}
|
|
788
|
+
const ariaHiddenAttr = element.getAttribute("aria-hidden");
|
|
789
|
+
const isHiddenAria = ariaHiddenAttr === "true";
|
|
790
|
+
const isHiddenVisual = isElementHiddenForAria(element);
|
|
791
|
+
if (isHiddenAria || isHiddenVisual) {
|
|
792
|
+
node.hidden = true;
|
|
793
|
+
}
|
|
794
|
+
if (element.hasAttribute("disabled") || element.getAttribute("aria-disabled") === "true") {
|
|
795
|
+
node.disabled = true;
|
|
796
|
+
}
|
|
797
|
+
if (finalRole === "checkbox" || finalRole === "radio" || finalRole === "menuitemcheckbox" || finalRole === "menuitemradio") {
|
|
798
|
+
const checked = element.getAttribute("aria-checked") || element.checked;
|
|
799
|
+
if (checked === "true" || checked === true)
|
|
800
|
+
node.checked = true;
|
|
801
|
+
else if (checked === "mixed")
|
|
802
|
+
node.checked = "mixed";
|
|
803
|
+
}
|
|
804
|
+
if (element.hasAttribute("aria-expanded")) {
|
|
805
|
+
node.expanded = element.getAttribute("aria-expanded") === "true";
|
|
806
|
+
}
|
|
807
|
+
if (element.hasAttribute("aria-selected")) {
|
|
808
|
+
node.selected = element.getAttribute("aria-selected") === "true";
|
|
809
|
+
}
|
|
810
|
+
if (element.hasAttribute("aria-pressed")) {
|
|
811
|
+
const pressed = element.getAttribute("aria-pressed");
|
|
812
|
+
if (pressed === "true")
|
|
813
|
+
node.pressed = true;
|
|
814
|
+
else if (pressed === "mixed")
|
|
815
|
+
node.pressed = "mixed";
|
|
816
|
+
}
|
|
817
|
+
if (element.hasAttribute("aria-level")) {
|
|
818
|
+
const lvl = parseInt(element.getAttribute("aria-level") || "");
|
|
819
|
+
if (!isNaN(lvl))
|
|
820
|
+
node.level = lvl;
|
|
821
|
+
}
|
|
822
|
+
if (finalRole === "link" && element.hasAttribute("href")) {
|
|
823
|
+
node.props.url = element.getAttribute("href") || "";
|
|
824
|
+
}
|
|
825
|
+
if (finalRole === "menuitem") {
|
|
826
|
+
const anchor = element.querySelector("a[href]");
|
|
827
|
+
if (anchor) {
|
|
828
|
+
const href = anchor.getAttribute("href") || "";
|
|
829
|
+
const label = (anchor.textContent || "").trim();
|
|
830
|
+
const linkId = this.idGenerator.generate(id, "link", anchor, 0);
|
|
831
|
+
anchor.setAttribute("data-persona-id", linkId);
|
|
832
|
+
const linkNode = {
|
|
833
|
+
role: "link",
|
|
834
|
+
name: label,
|
|
835
|
+
ref: linkId,
|
|
836
|
+
children: [],
|
|
837
|
+
element: anchor,
|
|
838
|
+
props: {}
|
|
839
|
+
};
|
|
840
|
+
if (href)
|
|
841
|
+
linkNode.props.url = href;
|
|
842
|
+
if (hidden) {
|
|
843
|
+
linkNode.hidden = true;
|
|
844
|
+
}
|
|
845
|
+
node.children.push(linkNode);
|
|
846
|
+
}
|
|
847
|
+
}
|
|
848
|
+
if ((finalRole === "textbox" || finalRole === "searchbox" || finalRole === "combobox") && element instanceof HTMLInputElement) {
|
|
849
|
+
if (element.value)
|
|
850
|
+
node.props.value = element.value;
|
|
851
|
+
}
|
|
852
|
+
if (element.hasAttribute("placeholder")) {
|
|
853
|
+
node.props.placeholder = element.getAttribute("placeholder") || "";
|
|
854
|
+
}
|
|
855
|
+
const testIdAttr = ["data-test-id", "data-testid", "data-test"].find((attr) => element.hasAttribute(attr));
|
|
856
|
+
if (testIdAttr) {
|
|
857
|
+
node.props.testId = element.getAttribute(testIdAttr) || "";
|
|
858
|
+
}
|
|
859
|
+
this.processChildren(element, node);
|
|
860
|
+
return node;
|
|
861
|
+
}
|
|
862
|
+
processChildren(element, parentNode) {
|
|
863
|
+
if (element.shadowRoot) {
|
|
864
|
+
let idx2 = 0;
|
|
865
|
+
for (let child = element.shadowRoot.firstElementChild; child; child = child.nextElementSibling) {
|
|
866
|
+
if (this.processChildNode(child, parentNode, idx2))
|
|
867
|
+
idx2++;
|
|
868
|
+
}
|
|
869
|
+
return;
|
|
870
|
+
}
|
|
871
|
+
let idx = 0;
|
|
872
|
+
for (let child = element.firstElementChild; child; child = child.nextElementSibling) {
|
|
873
|
+
if (this.processChildNode(child, parentNode, idx))
|
|
874
|
+
idx++;
|
|
875
|
+
}
|
|
876
|
+
if (element.tagName === "IFRAME") {
|
|
877
|
+
try {
|
|
878
|
+
const iframeDoc = element.contentDocument;
|
|
879
|
+
if (iframeDoc && iframeDoc.body) {
|
|
880
|
+
const childNode = this.processNode(iframeDoc.body, parentNode.ref, 0, true);
|
|
881
|
+
if (childNode)
|
|
882
|
+
parentNode.children.push(childNode);
|
|
883
|
+
}
|
|
884
|
+
} catch (e) {
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
processChildNode(node, parentNode, index) {
|
|
889
|
+
if (node.nodeType === Node.ELEMENT_NODE) {
|
|
890
|
+
const childEl = node;
|
|
891
|
+
const childNode = this.processNode(childEl, parentNode.ref, index, false);
|
|
892
|
+
if (childNode) {
|
|
893
|
+
parentNode.children.push(childNode);
|
|
894
|
+
return true;
|
|
895
|
+
}
|
|
896
|
+
return true;
|
|
897
|
+
}
|
|
898
|
+
return false;
|
|
899
|
+
}
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
// src/core/Observer.ts
|
|
903
|
+
var Observer = class {
|
|
904
|
+
constructor(root, treeWalker) {
|
|
905
|
+
this.updateScheduled = false;
|
|
906
|
+
this.root = root;
|
|
907
|
+
this.treeWalker = treeWalker;
|
|
908
|
+
this.observer = new MutationObserver(() => {
|
|
909
|
+
this.scheduleUpdate();
|
|
910
|
+
});
|
|
911
|
+
}
|
|
912
|
+
start() {
|
|
913
|
+
this.observer.observe(this.root, {
|
|
914
|
+
childList: true,
|
|
915
|
+
subtree: true,
|
|
916
|
+
attributes: true,
|
|
917
|
+
attributeFilter: [
|
|
918
|
+
"role",
|
|
919
|
+
"aria-label",
|
|
920
|
+
"aria-labelledby",
|
|
921
|
+
"aria-hidden",
|
|
922
|
+
"hidden",
|
|
923
|
+
"style",
|
|
924
|
+
"class",
|
|
925
|
+
"type",
|
|
926
|
+
"placeholder",
|
|
927
|
+
"title",
|
|
928
|
+
"alt",
|
|
929
|
+
"href",
|
|
930
|
+
"value"
|
|
931
|
+
]
|
|
932
|
+
});
|
|
933
|
+
this.scheduleUpdate();
|
|
934
|
+
}
|
|
935
|
+
stop() {
|
|
936
|
+
this.observer.disconnect();
|
|
937
|
+
}
|
|
938
|
+
scheduleUpdate() {
|
|
939
|
+
if (this.updateScheduled)
|
|
940
|
+
return;
|
|
941
|
+
this.updateScheduled = true;
|
|
942
|
+
requestAnimationFrame(() => {
|
|
943
|
+
this.update();
|
|
944
|
+
this.updateScheduled = false;
|
|
945
|
+
});
|
|
946
|
+
}
|
|
947
|
+
update() {
|
|
948
|
+
const start = performance.now();
|
|
949
|
+
this.treeWalker.snapshot(this.root);
|
|
950
|
+
const end = performance.now();
|
|
951
|
+
if (end - start > 50) {
|
|
952
|
+
console.warn(`Persona: DOM snapshot took ${Math.round(end - start)}ms`);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
};
|
|
956
|
+
|
|
957
|
+
// src/index.ts
|
|
958
|
+
var Runtime = class {
|
|
959
|
+
constructor() {
|
|
960
|
+
// Ref -> Parent AriaNode
|
|
961
|
+
this.rootNode = null;
|
|
962
|
+
this.version = "1.0.0";
|
|
963
|
+
this.treeWalker = new TreeWalker();
|
|
964
|
+
this.elementMap = /* @__PURE__ */ new Map();
|
|
965
|
+
this.nodeMap = /* @__PURE__ */ new Map();
|
|
966
|
+
this.parentMap = /* @__PURE__ */ new Map();
|
|
967
|
+
const root = document.body;
|
|
968
|
+
if (root) {
|
|
969
|
+
this.observer = new Observer(root, this.treeWalker);
|
|
970
|
+
this.observer.start();
|
|
971
|
+
}
|
|
972
|
+
this.refresh();
|
|
973
|
+
console.log(`[Persona] Runtime v${this.version} initialized`);
|
|
974
|
+
}
|
|
975
|
+
refresh() {
|
|
976
|
+
const root = document.body;
|
|
977
|
+
if (root) {
|
|
978
|
+
this.rootNode = this.treeWalker.snapshot(root);
|
|
979
|
+
this.rebuildMaps(this.rootNode);
|
|
980
|
+
}
|
|
981
|
+
}
|
|
982
|
+
rebuildMaps(root) {
|
|
983
|
+
this.elementMap.clear();
|
|
984
|
+
this.nodeMap.clear();
|
|
985
|
+
this.parentMap.clear();
|
|
986
|
+
if (!root)
|
|
987
|
+
return;
|
|
988
|
+
const visit = (node, parent) => {
|
|
989
|
+
this.elementMap.set(node.ref, node.element);
|
|
990
|
+
this.nodeMap.set(node.ref, node);
|
|
991
|
+
if (parent) {
|
|
992
|
+
this.parentMap.set(node.ref, parent);
|
|
993
|
+
}
|
|
994
|
+
for (const child of node.children) {
|
|
995
|
+
if (typeof child === "object" && child) {
|
|
996
|
+
visit(child, node);
|
|
997
|
+
}
|
|
998
|
+
}
|
|
999
|
+
};
|
|
1000
|
+
visit(root, null);
|
|
1001
|
+
}
|
|
1002
|
+
getTreeForTransport() {
|
|
1003
|
+
this.refresh();
|
|
1004
|
+
if (!this.rootNode)
|
|
1005
|
+
return null;
|
|
1006
|
+
const clean = (node) => {
|
|
1007
|
+
const { element, children, ...rest } = node;
|
|
1008
|
+
const cleanedChildren = (Array.isArray(children) ? children : []).map((c) => {
|
|
1009
|
+
if (typeof c === "string")
|
|
1010
|
+
return c;
|
|
1011
|
+
return clean(c);
|
|
1012
|
+
});
|
|
1013
|
+
return { ...rest, children: cleanedChildren };
|
|
1014
|
+
};
|
|
1015
|
+
try {
|
|
1016
|
+
return clean(this.rootNode);
|
|
1017
|
+
} catch (e) {
|
|
1018
|
+
console.error("[Persona] Error building transport tree", e);
|
|
1019
|
+
return null;
|
|
1020
|
+
}
|
|
1021
|
+
}
|
|
1022
|
+
find(ref) {
|
|
1023
|
+
if (this.elementMap.has(ref)) {
|
|
1024
|
+
const el2 = this.elementMap.get(ref);
|
|
1025
|
+
if (el2.isConnected)
|
|
1026
|
+
return el2;
|
|
1027
|
+
}
|
|
1028
|
+
const el = document.querySelector(`[data-persona-id="${ref}"]`);
|
|
1029
|
+
return el || null;
|
|
1030
|
+
}
|
|
1031
|
+
getSnapshot() {
|
|
1032
|
+
this.refresh();
|
|
1033
|
+
return this.rootNode;
|
|
1034
|
+
}
|
|
1035
|
+
navigate(ref, direction, index) {
|
|
1036
|
+
if (!this.nodeMap.has(ref)) {
|
|
1037
|
+
this.refresh();
|
|
1038
|
+
if (!this.nodeMap.has(ref))
|
|
1039
|
+
return null;
|
|
1040
|
+
}
|
|
1041
|
+
const node = this.nodeMap.get(ref);
|
|
1042
|
+
const parent = this.parentMap.get(ref);
|
|
1043
|
+
if (direction === "parent") {
|
|
1044
|
+
return parent ? parent.element : null;
|
|
1045
|
+
}
|
|
1046
|
+
if (direction === "child") {
|
|
1047
|
+
if (typeof index !== "number")
|
|
1048
|
+
return null;
|
|
1049
|
+
const idx = index < 0 ? node.children.length + index : index;
|
|
1050
|
+
if (idx >= 0 && idx < node.children.length) {
|
|
1051
|
+
const child = node.children[idx];
|
|
1052
|
+
return typeof child === "object" && child ? child.element : null;
|
|
1053
|
+
}
|
|
1054
|
+
return null;
|
|
1055
|
+
}
|
|
1056
|
+
if (direction === "next" || direction === "prev") {
|
|
1057
|
+
if (!parent)
|
|
1058
|
+
return null;
|
|
1059
|
+
const siblings = parent.children.filter((c) => typeof c === "object");
|
|
1060
|
+
const idx = siblings.indexOf(node);
|
|
1061
|
+
if (idx === -1)
|
|
1062
|
+
return null;
|
|
1063
|
+
if (direction === "next") {
|
|
1064
|
+
return idx + 1 < siblings.length ? siblings[idx + 1].element : null;
|
|
1065
|
+
}
|
|
1066
|
+
if (direction === "prev") {
|
|
1067
|
+
return idx - 1 >= 0 ? siblings[idx - 1].element : null;
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
return null;
|
|
1071
|
+
}
|
|
1072
|
+
};
|
|
1073
|
+
if (typeof window !== "undefined") {
|
|
1074
|
+
window.__PERSONA__ = new Runtime();
|
|
1075
|
+
}
|
|
1076
|
+
})();
|
|
1077
|
+
//# sourceMappingURL=persona_bundle.js.map
|