@michaelmagan/dnumb 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +180 -0
- package/LICENSE +21 -0
- package/README.md +297 -0
- package/dist/analysis/cluster.cjs +114 -0
- package/dist/analysis/cluster.d.cts +68 -0
- package/dist/analysis/cluster.d.ts +68 -0
- package/dist/analysis/cluster.js +109 -0
- package/dist/analysis/distances.cjs +124 -0
- package/dist/analysis/distances.d.cts +58 -0
- package/dist/analysis/distances.d.ts +58 -0
- package/dist/analysis/distances.js +119 -0
- package/dist/analysis/geometry.cjs +60 -0
- package/dist/analysis/geometry.d.cts +34 -0
- package/dist/analysis/geometry.d.ts +34 -0
- package/dist/analysis/geometry.js +53 -0
- package/dist/analysis/layout-tree.cjs +245 -0
- package/dist/analysis/layout-tree.d.cts +45 -0
- package/dist/analysis/layout-tree.d.ts +45 -0
- package/dist/analysis/layout-tree.js +242 -0
- package/dist/analysis/region-query.cjs +71 -0
- package/dist/analysis/region-query.d.cts +25 -0
- package/dist/analysis/region-query.d.ts +25 -0
- package/dist/analysis/region-query.js +67 -0
- package/dist/analysis/types.cjs +7 -0
- package/dist/analysis/types.d.cts +143 -0
- package/dist/analysis/types.d.ts +143 -0
- package/dist/analysis/types.js +6 -0
- package/dist/bundle.cjs +182 -0
- package/dist/bundle.d.cts +91 -0
- package/dist/bundle.d.ts +91 -0
- package/dist/bundle.js +175 -0
- package/dist/cli.cjs +755 -0
- package/dist/cli.d.cts +2 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +719 -0
- package/dist/diff/changes.cjs +656 -0
- package/dist/diff/changes.d.cts +2 -0
- package/dist/diff/changes.d.ts +2 -0
- package/dist/diff/changes.js +653 -0
- package/dist/diff/identity.cjs +167 -0
- package/dist/diff/identity.d.cts +62 -0
- package/dist/diff/identity.d.ts +62 -0
- package/dist/diff/identity.js +159 -0
- package/dist/diff/index.cjs +84 -0
- package/dist/diff/index.d.cts +76 -0
- package/dist/diff/index.d.ts +76 -0
- package/dist/diff/index.js +68 -0
- package/dist/diff/match.cjs +156 -0
- package/dist/diff/match.d.cts +17 -0
- package/dist/diff/match.d.ts +17 -0
- package/dist/diff/match.js +152 -0
- package/dist/diff/render.cjs +540 -0
- package/dist/diff/render.d.cts +2 -0
- package/dist/diff/render.d.ts +2 -0
- package/dist/diff/render.js +537 -0
- package/dist/diff/types.cjs +2 -0
- package/dist/diff/types.d.cts +211 -0
- package/dist/diff/types.d.ts +211 -0
- package/dist/diff/types.js +1 -0
- package/dist/digest/index.cjs +8 -0
- package/dist/digest/index.d.cts +4 -0
- package/dist/digest/index.d.ts +4 -0
- package/dist/digest/index.js +2 -0
- package/dist/digest/parse.cjs +163 -0
- package/dist/digest/parse.d.cts +80 -0
- package/dist/digest/parse.d.ts +80 -0
- package/dist/digest/parse.js +160 -0
- package/dist/digest/render.cjs +1575 -0
- package/dist/digest/render.d.cts +41 -0
- package/dist/digest/render.d.ts +41 -0
- package/dist/digest/render.js +1571 -0
- package/dist/digest/types.cjs +10 -0
- package/dist/digest/types.d.cts +86 -0
- package/dist/digest/types.d.ts +86 -0
- package/dist/digest/types.js +9 -0
- package/dist/index.cjs +61 -0
- package/dist/index.d.cts +27 -0
- package/dist/index.d.ts +27 -0
- package/dist/index.js +19 -0
- package/dist/page.cjs +35 -0
- package/dist/page.d.cts +40 -0
- package/dist/page.d.ts +40 -0
- package/dist/page.js +31 -0
- package/dist/playwright.cjs +138 -0
- package/dist/playwright.d.cts +69 -0
- package/dist/playwright.d.ts +69 -0
- package/dist/playwright.js +131 -0
- package/dist/query.cjs +1152 -0
- package/dist/query.d.cts +47 -0
- package/dist/query.d.ts +47 -0
- package/dist/query.js +1146 -0
- package/dist/snapshot/capture.cjs +80 -0
- package/dist/snapshot/capture.d.cts +34 -0
- package/dist/snapshot/capture.d.ts +34 -0
- package/dist/snapshot/capture.js +77 -0
- package/dist/snapshot/cdp.cjs +450 -0
- package/dist/snapshot/cdp.d.cts +36 -0
- package/dist/snapshot/cdp.d.ts +36 -0
- package/dist/snapshot/cdp.js +447 -0
- package/dist/snapshot/clip.cjs +100 -0
- package/dist/snapshot/clip.d.cts +26 -0
- package/dist/snapshot/clip.d.ts +26 -0
- package/dist/snapshot/clip.js +96 -0
- package/dist/snapshot/collect.cjs +358 -0
- package/dist/snapshot/collect.d.cts +18 -0
- package/dist/snapshot/collect.d.ts +18 -0
- package/dist/snapshot/collect.js +355 -0
- package/dist/snapshot/color.cjs +1039 -0
- package/dist/snapshot/color.d.cts +316 -0
- package/dist/snapshot/color.d.ts +316 -0
- package/dist/snapshot/color.js +1026 -0
- package/dist/snapshot/facts.cjs +167 -0
- package/dist/snapshot/facts.d.cts +48 -0
- package/dist/snapshot/facts.d.ts +48 -0
- package/dist/snapshot/facts.js +161 -0
- package/dist/snapshot/limits.cjs +69 -0
- package/dist/snapshot/limits.d.cts +59 -0
- package/dist/snapshot/limits.d.ts +59 -0
- package/dist/snapshot/limits.js +65 -0
- package/dist/snapshot/not-measured.cjs +44 -0
- package/dist/snapshot/not-measured.d.cts +14 -0
- package/dist/snapshot/not-measured.d.ts +14 -0
- package/dist/snapshot/not-measured.js +41 -0
- package/dist/snapshot/types.cjs +2 -0
- package/dist/snapshot/types.d.cts +238 -0
- package/dist/snapshot/types.d.ts +238 -0
- package/dist/snapshot/types.js +1 -0
- package/dist/spec/index.cjs +9 -0
- package/dist/spec/index.d.cts +15 -0
- package/dist/spec/index.d.ts +15 -0
- package/dist/spec/index.js +6 -0
- package/package.json +141 -0
- package/skills/dnumb/SKILL.md +136 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses one line of a rendered digest back into structured data. This is what
|
|
3
|
+
* makes the format machine-readable as well as agent-readable: a consumer that
|
|
4
|
+
* wants numbers rather than text does not have to re-implement the grammar.
|
|
5
|
+
*
|
|
6
|
+
* Ported from the prototype, where it round-tripped every golden digest, plus
|
|
7
|
+
* the v2 addition: facts.
|
|
8
|
+
*/
|
|
9
|
+
export interface ParsedGap {
|
|
10
|
+
direction: 'down' | 'right';
|
|
11
|
+
value: number;
|
|
12
|
+
overlap: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface ParsedElementLine {
|
|
15
|
+
kind: 'element';
|
|
16
|
+
id: string;
|
|
17
|
+
tag: string;
|
|
18
|
+
/** From `.role` or `#testid`, whichever was rendered; bare, no sigil. */
|
|
19
|
+
annotation: string | null;
|
|
20
|
+
text: string | null;
|
|
21
|
+
rect: {
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
};
|
|
27
|
+
flow: 'row' | 'column' | 'grid' | 'none';
|
|
28
|
+
gap: ParsedGap | null;
|
|
29
|
+
/** Fact bodies, without their brackets, in the order rendered. */
|
|
30
|
+
facts: string[];
|
|
31
|
+
belowFold: boolean;
|
|
32
|
+
/** Tree depth, from the line's indentation. */
|
|
33
|
+
depth: number;
|
|
34
|
+
}
|
|
35
|
+
export interface ParsedFoldEntry {
|
|
36
|
+
id: string;
|
|
37
|
+
x: number;
|
|
38
|
+
y: number;
|
|
39
|
+
gap: ParsedGap | null;
|
|
40
|
+
}
|
|
41
|
+
export interface ParsedFoldLine {
|
|
42
|
+
kind: 'fold';
|
|
43
|
+
count: number;
|
|
44
|
+
likeId: string;
|
|
45
|
+
/** Size of the repeating unit the fold covers. `1` for a plain run of
|
|
46
|
+
* identical siblings; `>1` for a periodic run (story/subtext/spacer, `dt`/
|
|
47
|
+
* `dd`, ...) where only the first whole period was printed in full. */
|
|
48
|
+
period: number;
|
|
49
|
+
/** The `(...)` size-spread token, verbatim and unparsed, or `null` when the
|
|
50
|
+
* folded members were uniform enough that none was emitted. A `period > 1`
|
|
51
|
+
* fold's spread is per phase (`phase0 h 2-90`) rather than pooled. */
|
|
52
|
+
variance: string | null;
|
|
53
|
+
entries: ParsedFoldEntry[];
|
|
54
|
+
}
|
|
55
|
+
export interface ParsedCollapsedLine {
|
|
56
|
+
kind: 'collapsed';
|
|
57
|
+
nodeCount: number;
|
|
58
|
+
/** Elements carrying a fact inside the collapsed subtree. `0` when the
|
|
59
|
+
* renderer omitted the clause -- a real, counted zero, not "unknown" --
|
|
60
|
+
* see `digest/render.ts#countFacts`. */
|
|
61
|
+
factCount: number;
|
|
62
|
+
bbox: {
|
|
63
|
+
x: number;
|
|
64
|
+
y: number;
|
|
65
|
+
width: number;
|
|
66
|
+
height: number;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* The id `--focus` reaches this collapsed subtree through -- always
|
|
70
|
+
* present; `digest/render.ts#renderChildren` prints it on every
|
|
71
|
+
* `[folded: …]` line unconditionally (it is the collapsed container's own
|
|
72
|
+
* id, already printed on the line directly above), unlike the fold line's
|
|
73
|
+
* `…+N not listed (--focus <id>)` hint, which is conditional on there
|
|
74
|
+
* being a hidden tail at all.
|
|
75
|
+
*/
|
|
76
|
+
focusHint: string;
|
|
77
|
+
}
|
|
78
|
+
export type ParsedLine = ParsedElementLine | ParsedFoldLine | ParsedCollapsedLine;
|
|
79
|
+
/** Comment (`#…`) and blank lines carry no per-node data and parse to `null`. */
|
|
80
|
+
export declare function parseDigestLine(line: string): ParsedLine | null;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses one line of a rendered digest back into structured data. This is what
|
|
3
|
+
* makes the format machine-readable as well as agent-readable: a consumer that
|
|
4
|
+
* wants numbers rather than text does not have to re-implement the grammar.
|
|
5
|
+
*
|
|
6
|
+
* Ported from the prototype, where it round-tripped every golden digest, plus
|
|
7
|
+
* the v2 addition: facts.
|
|
8
|
+
*/
|
|
9
|
+
export interface ParsedGap {
|
|
10
|
+
direction: 'down' | 'right';
|
|
11
|
+
value: number;
|
|
12
|
+
overlap: boolean;
|
|
13
|
+
}
|
|
14
|
+
export interface ParsedElementLine {
|
|
15
|
+
kind: 'element';
|
|
16
|
+
id: string;
|
|
17
|
+
tag: string;
|
|
18
|
+
/** From `.role` or `#testid`, whichever was rendered; bare, no sigil. */
|
|
19
|
+
annotation: string | null;
|
|
20
|
+
text: string | null;
|
|
21
|
+
rect: {
|
|
22
|
+
x: number;
|
|
23
|
+
y: number;
|
|
24
|
+
width: number;
|
|
25
|
+
height: number;
|
|
26
|
+
};
|
|
27
|
+
flow: 'row' | 'column' | 'grid' | 'none';
|
|
28
|
+
gap: ParsedGap | null;
|
|
29
|
+
/** Fact bodies, without their brackets, in the order rendered. */
|
|
30
|
+
facts: string[];
|
|
31
|
+
belowFold: boolean;
|
|
32
|
+
/** Tree depth, from the line's indentation. */
|
|
33
|
+
depth: number;
|
|
34
|
+
}
|
|
35
|
+
export interface ParsedFoldEntry {
|
|
36
|
+
id: string;
|
|
37
|
+
x: number;
|
|
38
|
+
y: number;
|
|
39
|
+
gap: ParsedGap | null;
|
|
40
|
+
}
|
|
41
|
+
export interface ParsedFoldLine {
|
|
42
|
+
kind: 'fold';
|
|
43
|
+
count: number;
|
|
44
|
+
likeId: string;
|
|
45
|
+
/** Size of the repeating unit the fold covers. `1` for a plain run of
|
|
46
|
+
* identical siblings; `>1` for a periodic run (story/subtext/spacer, `dt`/
|
|
47
|
+
* `dd`, ...) where only the first whole period was printed in full. */
|
|
48
|
+
period: number;
|
|
49
|
+
/** The `(...)` size-spread token, verbatim and unparsed, or `null` when the
|
|
50
|
+
* folded members were uniform enough that none was emitted. A `period > 1`
|
|
51
|
+
* fold's spread is per phase (`phase0 h 2-90`) rather than pooled. */
|
|
52
|
+
variance: string | null;
|
|
53
|
+
entries: ParsedFoldEntry[];
|
|
54
|
+
}
|
|
55
|
+
export interface ParsedCollapsedLine {
|
|
56
|
+
kind: 'collapsed';
|
|
57
|
+
nodeCount: number;
|
|
58
|
+
/** Elements carrying a fact inside the collapsed subtree. `0` when the
|
|
59
|
+
* renderer omitted the clause -- a real, counted zero, not "unknown" --
|
|
60
|
+
* see `digest/render.ts#countFacts`. */
|
|
61
|
+
factCount: number;
|
|
62
|
+
bbox: {
|
|
63
|
+
x: number;
|
|
64
|
+
y: number;
|
|
65
|
+
width: number;
|
|
66
|
+
height: number;
|
|
67
|
+
};
|
|
68
|
+
/**
|
|
69
|
+
* The id `--focus` reaches this collapsed subtree through -- always
|
|
70
|
+
* present; `digest/render.ts#renderChildren` prints it on every
|
|
71
|
+
* `[folded: …]` line unconditionally (it is the collapsed container's own
|
|
72
|
+
* id, already printed on the line directly above), unlike the fold line's
|
|
73
|
+
* `…+N not listed (--focus <id>)` hint, which is conditional on there
|
|
74
|
+
* being a hidden tail at all.
|
|
75
|
+
*/
|
|
76
|
+
focusHint: string;
|
|
77
|
+
}
|
|
78
|
+
export type ParsedLine = ParsedElementLine | ParsedFoldLine | ParsedCollapsedLine;
|
|
79
|
+
/** Comment (`#…`) and blank lines carry no per-node data and parse to `null`. */
|
|
80
|
+
export declare function parseDigestLine(line: string): ParsedLine | null;
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses one line of a rendered digest back into structured data. This is what
|
|
3
|
+
* makes the format machine-readable as well as agent-readable: a consumer that
|
|
4
|
+
* wants numbers rather than text does not have to re-implement the grammar.
|
|
5
|
+
*
|
|
6
|
+
* Ported from the prototype, where it round-tripped every golden digest, plus
|
|
7
|
+
* the v2 addition: facts.
|
|
8
|
+
*/
|
|
9
|
+
const GAP_RE = /([↓→])(-?\d+)( OVERLAP)?/;
|
|
10
|
+
const POS_SIZE_RE = /@(-?\d+),(-?\d+)\s+(\d+)x(\d+)/;
|
|
11
|
+
function parseGapToken(token) {
|
|
12
|
+
if (!token)
|
|
13
|
+
return null;
|
|
14
|
+
const match = GAP_RE.exec(token);
|
|
15
|
+
if (!match)
|
|
16
|
+
return null;
|
|
17
|
+
const [, arrow, valueStr, overlapStr] = match;
|
|
18
|
+
return {
|
|
19
|
+
direction: arrow === '↓' ? 'down' : 'right',
|
|
20
|
+
value: Number(valueStr),
|
|
21
|
+
overlap: Boolean(overlapStr),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/** A fold line's per-entry list: `id @x,y [gap], id @x,y [gap], ...`. */
|
|
25
|
+
function parseFoldEntries(list) {
|
|
26
|
+
const entries = [];
|
|
27
|
+
// Entries never contain a literal ", " -- ids, positions and gaps cannot --
|
|
28
|
+
// so a plain split is safe.
|
|
29
|
+
for (const part of list.split(', ')) {
|
|
30
|
+
const match = /^(\S+)\s+@(-?\d+),(-?\d+)(.*)$/.exec(part.trim());
|
|
31
|
+
if (!match)
|
|
32
|
+
continue;
|
|
33
|
+
const [, id, xStr, yStr, rest] = match;
|
|
34
|
+
if (!id || xStr === undefined || yStr === undefined)
|
|
35
|
+
continue;
|
|
36
|
+
entries.push({ id, x: Number(xStr), y: Number(yStr), gap: parseGapToken(rest ?? null) });
|
|
37
|
+
}
|
|
38
|
+
return entries;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Every `[...]` group after the geometry that is not a known structural token.
|
|
42
|
+
* Facts are open-ended by design — the renderer must be able to add one without
|
|
43
|
+
* the parser needing to know its name first.
|
|
44
|
+
*/
|
|
45
|
+
function parseFacts(after) {
|
|
46
|
+
const structural = /^(row|column|grid|below fold|wraps \d+)$/;
|
|
47
|
+
const facts = [];
|
|
48
|
+
for (const match of after.matchAll(/\[([^\]]+)\]/g)) {
|
|
49
|
+
const body = match[1];
|
|
50
|
+
if (body && !structural.test(body))
|
|
51
|
+
facts.push(body);
|
|
52
|
+
}
|
|
53
|
+
return facts;
|
|
54
|
+
}
|
|
55
|
+
/** Comment (`#…`) and blank lines carry no per-node data and parse to `null`. */
|
|
56
|
+
export function parseDigestLine(line) {
|
|
57
|
+
const trimmed = line.trim();
|
|
58
|
+
if (trimmed === '' || trimmed.startsWith('#'))
|
|
59
|
+
return null;
|
|
60
|
+
const depth = Math.floor((line.length - line.trimStart().length) / 2);
|
|
61
|
+
// The `, <K> facts` clause is optional -- present only when the collapsed
|
|
62
|
+
// subtree hides at least one fact-carrying element (`digest/render.ts`'s
|
|
63
|
+
// `renderChildren`) -- so it is its own optional group rather than folded
|
|
64
|
+
// into a catch-all, the same discipline the fold line's variance token
|
|
65
|
+
// uses. `(--focus <id>)` is not optional in what the renderer emits (every
|
|
66
|
+
// `[folded: …]` line carries it -- P1-1: the collapse line used to be the
|
|
67
|
+
// one place in the whole format that disclosed the most while pointing
|
|
68
|
+
// nowhere), but the group here is still `?` so a hand-built or
|
|
69
|
+
// differently-versioned line missing it still parses rather than going
|
|
70
|
+
// `null`.
|
|
71
|
+
const collapsed = /^\[folded:\s*(\d+)\s*nodes(?:,\s*(\d+)\s*facts)?,\s*bbox\s*@(-?\d+),(-?\d+)\s+(\d+)x(\d+)(?:\s*\(--focus\s+(\S+)\))?\]$/.exec(trimmed);
|
|
72
|
+
if (collapsed) {
|
|
73
|
+
const [, count, facts, x, y, w, h, focusHint] = collapsed;
|
|
74
|
+
return {
|
|
75
|
+
kind: 'collapsed',
|
|
76
|
+
nodeCount: Number(count),
|
|
77
|
+
factCount: facts ? Number(facts) : 0,
|
|
78
|
+
bbox: { x: Number(x), y: Number(y), width: Number(w), height: Number(h) },
|
|
79
|
+
focusHint: focusHint ?? '',
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
// `×N[/period] more like <id>[ (variance)] — entries`. The variance and
|
|
83
|
+
// period groups are both explicit and optional rather than folded into a
|
|
84
|
+
// catch-all `\S+` for `likeId` -- a variance token has a space in it
|
|
85
|
+
// (`r0 (h 30-90)`), and `(\S+)\s*—` alone cannot span that, which used to
|
|
86
|
+
// make every folded id on a page with a size spread unparseable.
|
|
87
|
+
const fold = /^\[fold:\s*×(\d+)(?:\/(\d+))?\s*more like (\S+)(?:\s*(\([^()]*\)))?\s*—\s*(.+)\]$/.exec(trimmed);
|
|
88
|
+
if (fold) {
|
|
89
|
+
const [, count, period, likeId, variance, list] = fold;
|
|
90
|
+
if (!likeId || !list)
|
|
91
|
+
return null;
|
|
92
|
+
return {
|
|
93
|
+
kind: 'fold',
|
|
94
|
+
count: Number(count),
|
|
95
|
+
likeId,
|
|
96
|
+
period: period ? Number(period) : 1,
|
|
97
|
+
variance: variance ?? null,
|
|
98
|
+
entries: parseFoldEntries(list),
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const posSize = POS_SIZE_RE.exec(trimmed);
|
|
102
|
+
if (!posSize)
|
|
103
|
+
return null;
|
|
104
|
+
const [, xStr, yStr, wStr, hStr] = posSize;
|
|
105
|
+
if (xStr === undefined || yStr === undefined || wStr === undefined || hStr === undefined) {
|
|
106
|
+
return null;
|
|
107
|
+
}
|
|
108
|
+
const before = trimmed.slice(0, posSize.index).trim();
|
|
109
|
+
const after = trimmed.slice(posSize.index + posSize[0].length);
|
|
110
|
+
// Text is split off first, then id/tag/annotation are parsed from what's
|
|
111
|
+
// left -- not one combined regex -- because the renderer does not escape
|
|
112
|
+
// the text it quotes, and a real accessible name can contain a literal
|
|
113
|
+
// `"` (ar.wikipedia.org link titles quote a word inline: `"الملحوظة"`
|
|
114
|
+
// inside the name). `(.*?)` is lazy, so it finds the *first* `\s+"` in
|
|
115
|
+
// `before` -- right after tag/annotation, since neither ever contains
|
|
116
|
+
// `\s+"` itself -- and the greedy `(.*)"$` then takes everything from
|
|
117
|
+
// there to the final `"` at the very end of the line as the text,
|
|
118
|
+
// embedded quotes included. Without this split, a naive `"([^"]*)"$`
|
|
119
|
+
// stops at the first embedded quote and the anchored `$` never matches,
|
|
120
|
+
// so the whole line -- id, tag, position, every fact on it -- was
|
|
121
|
+
// discarded.
|
|
122
|
+
const textMatch = /^(.*?)\s+"(.*)"$/.exec(before);
|
|
123
|
+
const head = textMatch ? textMatch[1] : before;
|
|
124
|
+
const text = textMatch ? textMatch[2] : undefined;
|
|
125
|
+
if (head === undefined)
|
|
126
|
+
return null;
|
|
127
|
+
// Tag: an optional `::` pseudo-element prefix (`::before`, `::after`,
|
|
128
|
+
// `::marker` -- real tokens on a real capture; the CDP snapshot path
|
|
129
|
+
// reports a pseudo-element's node name verbatim) followed by a plain tag
|
|
130
|
+
// name, which on a modern page is routinely a hyphenated custom-element
|
|
131
|
+
// name (`sl-button`, or any other web component). Annotation, when
|
|
132
|
+
// present, is greedy (`.*`) rather than `[^\s"]+` from its `.`/`#` sigil to
|
|
133
|
+
// the end of what's left: a role (`.heading`) never has whitespace in it,
|
|
134
|
+
// but a `data-testid` value is raw attribute text, and real pages put
|
|
135
|
+
// whitespace in it -- e.g. grafana.com renders
|
|
136
|
+
// `data-testid="data-testid navigation mega-menu"` verbatim, so the
|
|
137
|
+
// annotation itself is `data-testid navigation mega-menu`. The text (if
|
|
138
|
+
// any) is already stripped off above, so there is nothing after the
|
|
139
|
+
// annotation left to over-consume.
|
|
140
|
+
const idTagMatch = /^(\S+)\s+((?:::)?[a-zA-Z][a-zA-Z0-9-]*)([.#].*)?$/.exec(head);
|
|
141
|
+
if (!idTagMatch)
|
|
142
|
+
return null;
|
|
143
|
+
const [, id, tag, annotation] = idTagMatch;
|
|
144
|
+
if (!id || !tag)
|
|
145
|
+
return null;
|
|
146
|
+
const flow = /\[(row|column|grid)\]/.exec(after);
|
|
147
|
+
return {
|
|
148
|
+
kind: 'element',
|
|
149
|
+
id,
|
|
150
|
+
tag,
|
|
151
|
+
annotation: annotation ? annotation.slice(1) : null,
|
|
152
|
+
text: text ?? null,
|
|
153
|
+
rect: { x: Number(xStr), y: Number(yStr), width: Number(wStr), height: Number(hStr) },
|
|
154
|
+
flow: flow?.[1] ?? 'none',
|
|
155
|
+
gap: parseGapToken(GAP_RE.exec(after)?.[0] ?? null),
|
|
156
|
+
facts: parseFacts(after),
|
|
157
|
+
belowFold: /\[below fold\]/.test(after),
|
|
158
|
+
depth,
|
|
159
|
+
};
|
|
160
|
+
}
|