@issuegraph/editor 0.4.0 → 0.22.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +65 -9
- package/dist/a11y/baseline.d.ts +182 -0
- package/dist/a11y/baseline.d.ts.map +1 -0
- package/dist/a11y/baseline.js +434 -0
- package/dist/a11y/baseline.js.map +1 -0
- package/dist/audit/findings.d.ts +107 -1
- package/dist/audit/findings.d.ts.map +1 -1
- package/dist/audit/findings.js +113 -6
- package/dist/audit/findings.js.map +1 -1
- package/dist/audit/panel.d.ts +277 -0
- package/dist/audit/panel.d.ts.map +1 -0
- package/dist/audit/panel.js +462 -0
- package/dist/audit/panel.js.map +1 -0
- package/dist/audit/refused.d.ts +99 -0
- package/dist/audit/refused.d.ts.map +1 -0
- package/dist/audit/refused.js +276 -0
- package/dist/audit/refused.js.map +1 -0
- package/dist/audit/styles.d.ts +1 -1
- package/dist/audit/styles.d.ts.map +1 -1
- package/dist/audit/styles.js +429 -1
- package/dist/audit/styles.js.map +1 -1
- package/dist/audit/surface.d.ts +54 -12
- package/dist/audit/surface.d.ts.map +1 -1
- package/dist/audit/surface.js +53 -13
- package/dist/audit/surface.js.map +1 -1
- package/dist/create/draft.d.ts +36 -0
- package/dist/create/draft.d.ts.map +1 -1
- package/dist/create/draft.js +40 -0
- package/dist/create/draft.js.map +1 -1
- package/dist/create/keys.d.ts +134 -9
- package/dist/create/keys.d.ts.map +1 -1
- package/dist/create/keys.js +119 -20
- package/dist/create/keys.js.map +1 -1
- package/dist/firstpass/candidates.d.ts +17 -1
- package/dist/firstpass/candidates.d.ts.map +1 -1
- package/dist/index.d.ts +42 -17
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +45 -14
- package/dist/index.js.map +1 -1
- package/dist/overlay/render.d.ts +11 -3
- package/dist/overlay/render.d.ts.map +1 -1
- package/dist/overlay/render.js +76 -20
- package/dist/overlay/render.js.map +1 -1
- package/dist/overlay/request.d.ts +71 -0
- package/dist/overlay/request.d.ts.map +1 -0
- package/dist/overlay/request.js +129 -0
- package/dist/overlay/request.js.map +1 -0
- package/dist/overlay/styles.d.ts +1 -1
- package/dist/overlay/styles.d.ts.map +1 -1
- package/dist/overlay/styles.js +78 -7
- package/dist/overlay/styles.js.map +1 -1
- package/dist/picker/render.d.ts +26 -16
- package/dist/picker/render.d.ts.map +1 -1
- package/dist/picker/render.js +25 -36
- package/dist/picker/render.js.map +1 -1
- package/dist/picker/styles.d.ts +7 -7
- package/dist/picker/styles.d.ts.map +1 -1
- package/dist/picker/styles.js +7 -36
- package/dist/picker/styles.js.map +1 -1
- package/dist/picker/view.d.ts +8 -4
- package/dist/picker/view.d.ts.map +1 -1
- package/dist/picker/view.js +4 -3
- package/dist/picker/view.js.map +1 -1
- package/dist/picker/words.d.ts +12 -10
- package/dist/picker/words.d.ts.map +1 -1
- package/dist/picker/words.js +12 -8
- package/dist/picker/words.js.map +1 -1
- package/dist/reevaluate/parts.d.ts +130 -0
- package/dist/reevaluate/parts.d.ts.map +1 -0
- package/dist/reevaluate/parts.js +260 -0
- package/dist/reevaluate/parts.js.map +1 -0
- package/dist/reevaluate/render.d.ts +18 -11
- package/dist/reevaluate/render.d.ts.map +1 -1
- package/dist/reevaluate/render.js +21 -135
- package/dist/reevaluate/render.js.map +1 -1
- package/dist/reevaluate/styles.d.ts +1 -1
- package/dist/reevaluate/styles.d.ts.map +1 -1
- package/dist/reevaluate/styles.js +26 -0
- package/dist/reevaluate/styles.js.map +1 -1
- package/dist/reevaluate/view.d.ts +16 -1
- package/dist/reevaluate/view.d.ts.map +1 -1
- package/dist/reevaluate/view.js +21 -1
- package/dist/reevaluate/view.js.map +1 -1
- package/dist/reevaluate/words.d.ts +7 -0
- package/dist/reevaluate/words.d.ts.map +1 -1
- package/dist/scale/ladder.d.ts +62 -6
- package/dist/scale/ladder.d.ts.map +1 -1
- package/dist/scale/ladder.js +129 -11
- package/dist/scale/ladder.js.map +1 -1
- package/dist/scale/render.d.ts +44 -0
- package/dist/scale/render.d.ts.map +1 -1
- package/dist/scale/render.js +206 -28
- package/dist/scale/render.js.map +1 -1
- package/dist/scale/styles.d.ts +11 -5
- package/dist/scale/styles.d.ts.map +1 -1
- package/dist/scale/styles.js +133 -4
- package/dist/scale/styles.js.map +1 -1
- package/dist/workspace/chrome.d.ts +1 -1
- package/dist/workspace/chrome.d.ts.map +1 -1
- package/dist/workspace/chrome.js +48 -0
- package/dist/workspace/chrome.js.map +1 -1
- package/dist/workspace/firstpass.d.ts +166 -0
- package/dist/workspace/firstpass.d.ts.map +1 -0
- package/dist/workspace/firstpass.js +151 -0
- package/dist/workspace/firstpass.js.map +1 -0
- package/dist/workspace/host.d.ts +283 -11
- package/dist/workspace/host.d.ts.map +1 -1
- package/dist/workspace/host.js +614 -43
- package/dist/workspace/host.js.map +1 -1
- package/dist/workspace/inspector.d.ts +72 -2
- package/dist/workspace/inspector.d.ts.map +1 -1
- package/dist/workspace/inspector.js +26 -0
- package/dist/workspace/inspector.js.map +1 -1
- package/dist/workspace/mount.d.ts +75 -6
- package/dist/workspace/mount.d.ts.map +1 -1
- package/dist/workspace/mount.js +1851 -60
- package/dist/workspace/mount.js.map +1 -1
- package/dist/workspace/rail.d.ts +5 -2
- package/dist/workspace/rail.d.ts.map +1 -1
- package/dist/workspace/rail.js.map +1 -1
- package/dist/workspace/recovery.d.ts +155 -0
- package/dist/workspace/recovery.d.ts.map +1 -0
- package/dist/workspace/recovery.js +156 -0
- package/dist/workspace/recovery.js.map +1 -0
- package/dist/workspace/render.d.ts +779 -2
- package/dist/workspace/render.d.ts.map +1 -1
- package/dist/workspace/render.js +1727 -61
- package/dist/workspace/render.js.map +1 -1
- package/dist/workspace/selection.d.ts +11 -0
- package/dist/workspace/selection.d.ts.map +1 -1
- package/dist/workspace/selection.js +10 -0
- package/dist/workspace/selection.js.map +1 -1
- package/dist/workspace/styles.d.ts +1 -1
- package/dist/workspace/styles.d.ts.map +1 -1
- package/dist/workspace/styles.js +1405 -42
- package/dist/workspace/styles.js.map +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,434 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The accessible contract of every command control, read off a rendered
|
|
3
|
+
* surface — so it is committed as an artifact rather than asserted in prose.
|
|
4
|
+
*
|
|
5
|
+
* ## Why a DOM node and not an `ElementSpec`
|
|
6
|
+
*
|
|
7
|
+
* There is no spec tree for the workspace to walk. `renderWorkspace` returns
|
|
8
|
+
* `markup: string`; it composes its four zones by joining independently
|
|
9
|
+
* rendered strings, the `data-zone` wrapper is the one tag written by hand in
|
|
10
|
+
* that file, and every `…Spec` builder is module-private. Worse for a spec
|
|
11
|
+
* walk, a substantial part of the interactive surface never passes a renderer
|
|
12
|
+
* at all — `mountWorkspace` builds the kind chooser, the target search and its
|
|
13
|
+
* match list with `createElement`, and says so ("WHAT IS LEFT HERE IS WHAT
|
|
14
|
+
* NEEDS A DOM"). A baseline taken over specs would silently omit exactly the
|
|
15
|
+
* keyboard-relevant controls.
|
|
16
|
+
*
|
|
17
|
+
* The rendered DOM has all of it, and it is also what a screen reader actually
|
|
18
|
+
* meets.
|
|
19
|
+
*
|
|
20
|
+
* ## NOT EXPORTED, and that is the correction of a real mistake
|
|
21
|
+
*
|
|
22
|
+
* An earlier revision published `controlSurface` on the package surface,
|
|
23
|
+
* reasoning that a host composing these zones into its own chrome would want
|
|
24
|
+
* the same record. Nothing asked for it, and `index.ts` states the rule it
|
|
25
|
+
* broke: "a published package can add an export later and can never take one
|
|
26
|
+
* back, so nothing is exported before something is owed."
|
|
27
|
+
*
|
|
28
|
+
* The cost was not theoretical. Exporting it turned this file into a
|
|
29
|
+
* general-purpose accessible-name and focusability reader, and accessible-name
|
|
30
|
+
* computation has a long tail — `title`, `placeholder`, an input's own `value`,
|
|
31
|
+
* whitespace-only labels, hidden inputs, `contenteditable`. Three consecutive
|
|
32
|
+
* review rounds produced findings of exactly that shape: each real, each cheap,
|
|
33
|
+
* and none about markup this package draws. That tail has no end, and this
|
|
34
|
+
* change cannot discharge it.
|
|
35
|
+
*
|
|
36
|
+
* Internal, the obligation is bounded and discharged: the reader only ever
|
|
37
|
+
* meets what this package renders, which is a closed set recorded in six
|
|
38
|
+
* states. If a host ever owes this, export it then — with its tail addressed,
|
|
39
|
+
* and against a consumer that actually needs it.
|
|
40
|
+
*
|
|
41
|
+
* ## Structurally typed, so this module still touches no global
|
|
42
|
+
*
|
|
43
|
+
* {@link SurfaceElement} is declared by what it must answer rather than as
|
|
44
|
+
* `Element`, which is `element.ts`'s own `SpecDocument` idiom — "declared
|
|
45
|
+
* structurally rather than as `Document` so a host can pass any implementation
|
|
46
|
+
* that answers these five calls, and so this module stays honest about how
|
|
47
|
+
* little of the DOM it actually needs". `purity.test.ts` imports every shipped
|
|
48
|
+
* module with the browser globals removed; nothing here reaches for one.
|
|
49
|
+
*
|
|
50
|
+
* ## What is recorded, and what is deliberately not
|
|
51
|
+
*
|
|
52
|
+
* VALUES ARE KEPT FOR MACHINE STATES AND DROPPED FOR HUMAN-READABLE NAMES.
|
|
53
|
+
* That split is by provenance, not one blanket rule, because the two fail
|
|
54
|
+
* differently. `aria-pressed` is rendered as `cond ? 'true' : 'false'`, so a
|
|
55
|
+
* baseline holding only the attribute NAME is byte-identical when a toggle
|
|
56
|
+
* inverts — the regression it exists to catch. A label, meanwhile, is often
|
|
57
|
+
* the host's word (`picker/words.ts`, `reevaluate/words.ts` and
|
|
58
|
+
* `firstpass/words.ts` all require a `Record<K, string>` from the host and
|
|
59
|
+
* default none), so pinning the string would fail any host that translated it.
|
|
60
|
+
*
|
|
61
|
+
* An earlier draft justified dropping every value with "the package ships no
|
|
62
|
+
* English", which is false: `scale/render.ts` writes `'connected components'`,
|
|
63
|
+
* `'what to do next'`, `'search matches'` and `'isolated issues'` itself, and
|
|
64
|
+
* the viewer ships more landmark names. The true, narrower claim is the one
|
|
65
|
+
* above.
|
|
66
|
+
*
|
|
67
|
+
* AN IDREF IS RECORDED BY WHETHER IT RESOLVES, never by its value. The
|
|
68
|
+
* disclosure's `aria-controls` names `diffRegionId(recovery.mutationId)`, and a
|
|
69
|
+
* mutation id is generated per run — pinning it would make the artifact churn
|
|
70
|
+
* on every render for no signal. What is worth pinning is the fact a reader
|
|
71
|
+
* depends on: that the id names a region that is actually there. A dangling
|
|
72
|
+
* `aria-controls` is a real defect and a stable one to record.
|
|
73
|
+
*/
|
|
74
|
+
/**
|
|
75
|
+
* Every attribute a control publishes itself on.
|
|
76
|
+
*
|
|
77
|
+
* THREE CHANNELS, NOT ONE, and reading only the first is how a record comes to
|
|
78
|
+
* claim a surface it never saw. `data-ig-command` is the general one, but the
|
|
79
|
+
* first pass's y/n/s answers carry `data-ig-answer` and the audit header's
|
|
80
|
+
* toggle carries `data-ig-audit-filter` — `firstpass/render.ts` and
|
|
81
|
+
* `audit/surface.ts` each say so where they declare theirs. Keyed on the
|
|
82
|
+
* command alone, this would have recorded `null` for the audit toggle and for
|
|
83
|
+
* every answer, and the rules below would have skipped exactly the controls a
|
|
84
|
+
* keyboard reader depends on most.
|
|
85
|
+
*/
|
|
86
|
+
export const CONTROL_ATTRIBUTES = Object.freeze([
|
|
87
|
+
'data-ig-command',
|
|
88
|
+
'data-ig-answer',
|
|
89
|
+
'data-ig-audit-filter',
|
|
90
|
+
// §17d's header count, which opens the overlay. A SEPARATE CHANNEL from the
|
|
91
|
+
// filter beside it because it is a separate control now — `RULINGS.md` §3
|
|
92
|
+
// split one button that carried two meanings into two that carry one each.
|
|
93
|
+
'data-ig-audit-panel',
|
|
94
|
+
]);
|
|
95
|
+
/**
|
|
96
|
+
* Implicit roles, for the tags this package actually renders.
|
|
97
|
+
*
|
|
98
|
+
* Closed and small on purpose. A general HTML-AAM table would be a second
|
|
99
|
+
* implementation of a specification this package does not own, and every entry
|
|
100
|
+
* beyond what is rendered here would be untested. An unmapped tag records
|
|
101
|
+
* `null` rather than a guess — including everything in the SVG namespace,
|
|
102
|
+
* where the HTML implicit roles do not apply and `path` has none at all.
|
|
103
|
+
*/
|
|
104
|
+
const IMPLICIT_ROLES = Object.freeze({
|
|
105
|
+
button: 'button',
|
|
106
|
+
h1: 'heading',
|
|
107
|
+
h2: 'heading',
|
|
108
|
+
h3: 'heading',
|
|
109
|
+
h4: 'heading',
|
|
110
|
+
li: 'listitem',
|
|
111
|
+
ol: 'list',
|
|
112
|
+
ul: 'list',
|
|
113
|
+
});
|
|
114
|
+
/**
|
|
115
|
+
* The two tags whose implicit role depends on more than the tag.
|
|
116
|
+
*
|
|
117
|
+
* `a` is a `link` only with an `href` — without one it is not a link and not
|
|
118
|
+
* focusable. `section` is a `region` only when it has an accessible name;
|
|
119
|
+
* unnamed it exposes no role at all. Both were mapped unconditionally in an
|
|
120
|
+
* earlier revision, which recorded a role the accessibility tree does not have.
|
|
121
|
+
*/
|
|
122
|
+
/**
|
|
123
|
+
* `input`'s implicit role, which is its `type`'s rather than its tag's.
|
|
124
|
+
*
|
|
125
|
+
* The mounted target search is `<input type="search">` — `searchbox`, not
|
|
126
|
+
* nothing — and recording `null` for a control this package itself renders
|
|
127
|
+
* means the role column could not notice it being changed into a semantically
|
|
128
|
+
* different control. Only the types rendered here are mapped; the rest stay
|
|
129
|
+
* `null`, on the same rule as every other unmapped tag.
|
|
130
|
+
*/
|
|
131
|
+
const INPUT_ROLES = Object.freeze({
|
|
132
|
+
button: 'button',
|
|
133
|
+
checkbox: 'checkbox',
|
|
134
|
+
radio: 'radio',
|
|
135
|
+
reset: 'button',
|
|
136
|
+
search: 'searchbox',
|
|
137
|
+
submit: 'button',
|
|
138
|
+
text: 'textbox',
|
|
139
|
+
});
|
|
140
|
+
function conditionalRole(root, element, tag) {
|
|
141
|
+
if (tag === 'a')
|
|
142
|
+
return element.getAttribute('href') === null ? null : 'link';
|
|
143
|
+
if (tag === 'section')
|
|
144
|
+
return nameSource(root, element) === 'none' ? null : 'region';
|
|
145
|
+
// NO `type` IS `type="text"`, which is HTML's own default rather than an
|
|
146
|
+
// assumption: an `<input>` with no type is a text field.
|
|
147
|
+
if (tag === 'input')
|
|
148
|
+
return INPUT_ROLES[element.getAttribute('type') ?? 'text'] ?? null;
|
|
149
|
+
return null;
|
|
150
|
+
}
|
|
151
|
+
/** ARIA attributes whose value is drawn from a fixed set, so the value is data. */
|
|
152
|
+
const ENUMERATED_ARIA = Object.freeze([
|
|
153
|
+
'aria-busy',
|
|
154
|
+
'aria-checked',
|
|
155
|
+
'aria-current',
|
|
156
|
+
'aria-expanded',
|
|
157
|
+
'aria-hidden',
|
|
158
|
+
'aria-live',
|
|
159
|
+
'aria-modal',
|
|
160
|
+
'aria-pressed',
|
|
161
|
+
'aria-selected',
|
|
162
|
+
]);
|
|
163
|
+
/** ARIA attributes whose value is an id, recorded by whether it resolves. */
|
|
164
|
+
const IDREF_ARIA = Object.freeze([
|
|
165
|
+
'aria-controls',
|
|
166
|
+
'aria-describedby',
|
|
167
|
+
'aria-labelledby',
|
|
168
|
+
]);
|
|
169
|
+
const TEXT_NODE = 3;
|
|
170
|
+
function isElement(node) {
|
|
171
|
+
return 'tagName' in node;
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* The text a screen reader would announce for this element.
|
|
175
|
+
*
|
|
176
|
+
* SUBTREES MARKED `aria-hidden` CONTRIBUTE NOTHING, which is the case a naive
|
|
177
|
+
* "does it have a text child" test gets wrong. `parts.ts` renders a command
|
|
178
|
+
* button as a hidden glyph beside its label; delete the label and the button
|
|
179
|
+
* still has a text descendant while its accessible name is empty. That is a
|
|
180
|
+
* name loss no presence check would report.
|
|
181
|
+
*/
|
|
182
|
+
function visibleText(element) {
|
|
183
|
+
let out = '';
|
|
184
|
+
const walk = (node) => {
|
|
185
|
+
if (!isElement(node)) {
|
|
186
|
+
if (node.nodeType === TEXT_NODE)
|
|
187
|
+
out += node.nodeValue ?? '';
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
190
|
+
// `hidden` AS WELL AS `aria-hidden`, and for the same reason one step
|
|
191
|
+
// further along: a natively hidden subtree is not rendered at all, so its
|
|
192
|
+
// text reaches no reader. `<button><span hidden>Save</span></button>` is
|
|
193
|
+
// exposed with NO accessible name, and counting that text reported one —
|
|
194
|
+
// letting the naming rule pass on a control that announces nothing.
|
|
195
|
+
if (node.getAttribute('aria-hidden') === 'true')
|
|
196
|
+
return;
|
|
197
|
+
if (node.getAttribute('hidden') !== null)
|
|
198
|
+
return;
|
|
199
|
+
for (let index = 0; index < node.childNodes.length; index += 1) {
|
|
200
|
+
const child = node.childNodes[index];
|
|
201
|
+
if (child !== undefined)
|
|
202
|
+
walk(child);
|
|
203
|
+
}
|
|
204
|
+
};
|
|
205
|
+
walk(element);
|
|
206
|
+
return out.trim();
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Whether a `<label>` names this control, by either association HTML defines.
|
|
210
|
+
*
|
|
211
|
+
* WRAPPING FIRST, then `for`. `scale/render.ts` uses the wrapping form and says
|
|
212
|
+
* why in as many words — two search boxes side by side with one `id` would make
|
|
213
|
+
* `for` resolve to whichever came first, so one would silently lose its label.
|
|
214
|
+
*
|
|
215
|
+
* THE `for` LOOKUP WALKS AND COMPARES rather than building `label[for="..."]`:
|
|
216
|
+
* an id is host data, and interpolating one into a selector is the injection
|
|
217
|
+
* this module already had to remove once.
|
|
218
|
+
*/
|
|
219
|
+
function labelNames(root, element) {
|
|
220
|
+
const wrapping = element.closest('label');
|
|
221
|
+
if (wrapping !== null && visibleText(wrapping) !== '')
|
|
222
|
+
return true;
|
|
223
|
+
const id = element.getAttribute('id');
|
|
224
|
+
if (id === null || id === '')
|
|
225
|
+
return false;
|
|
226
|
+
for (const label of root.querySelectorAll('label')) {
|
|
227
|
+
if (label.getAttribute('for') === id && visibleText(label) !== '')
|
|
228
|
+
return true;
|
|
229
|
+
}
|
|
230
|
+
return false;
|
|
231
|
+
}
|
|
232
|
+
function nameSource(root, element) {
|
|
233
|
+
// AN EMPTY VALUE IS NOT A NAME, and it is not the same as no attribute.
|
|
234
|
+
// `renderMarkup` omits only `undefined`, `null` and `false`, so
|
|
235
|
+
// `aria-label=""` does reach the markup — the case the "omitted, never empty"
|
|
236
|
+
// rule `holdLine` states for `data-code` exists to prevent.
|
|
237
|
+
// `aria-labelledby` FIRST, because accessible-name computation resolves it
|
|
238
|
+
// BEFORE `aria-label` — so a host supplying both, with the reference pointing
|
|
239
|
+
// at an empty element, is exposed with no name while a reader that took the
|
|
240
|
+
// label would have reported one. Order is the whole of the fix; each branch
|
|
241
|
+
// was already right on its own.
|
|
242
|
+
//
|
|
243
|
+
// AN IDREF THAT RESOLVES IS NOT YET A NAME. `aria-labelledby` naming an
|
|
244
|
+
// element that exists but carries no text computes to an empty accessible
|
|
245
|
+
// name, and reading the attribute alone reported a supplied name for a
|
|
246
|
+
// control that announces nothing. This is the same defect as `aria-label=""`,
|
|
247
|
+
// which is already answered `empty` — fixing one half and not the other
|
|
248
|
+
// would have left the rule inconsistent about the same mistake.
|
|
249
|
+
//
|
|
250
|
+
// `ariaOf` keeps answering `resolves` for the same attribute, and the two are
|
|
251
|
+
// not in conflict: it reports whether the reference POINTS at something,
|
|
252
|
+
// which is its own defect when it does not. Whether the thing it points at
|
|
253
|
+
// says anything is this function's question.
|
|
254
|
+
const labelledBy = element.getAttribute('aria-labelledby');
|
|
255
|
+
if (labelledBy !== null) {
|
|
256
|
+
const named = labelledBy.split(/\s+/).filter((token) => token !== '');
|
|
257
|
+
let text = '';
|
|
258
|
+
for (const target of root.querySelectorAll('[id]')) {
|
|
259
|
+
if (named.includes(target.getAttribute('id') ?? ''))
|
|
260
|
+
text += visibleText(target);
|
|
261
|
+
}
|
|
262
|
+
return text.trim() === '' ? 'empty' : 'aria-labelledby';
|
|
263
|
+
}
|
|
264
|
+
const label = element.getAttribute('aria-label');
|
|
265
|
+
// TRIMMED, because a name made of spaces is not a name: the browser exposes
|
|
266
|
+
// an empty accessible name for `aria-label=" "`, which a template padding an
|
|
267
|
+
// empty translation produces easily.
|
|
268
|
+
if (label !== null)
|
|
269
|
+
return label.trim() === '' ? 'empty' : 'aria-label';
|
|
270
|
+
if (visibleText(element) !== '')
|
|
271
|
+
return 'text';
|
|
272
|
+
return labelNames(root, element) ? 'label' : 'none';
|
|
273
|
+
}
|
|
274
|
+
function tabStopOf(element) {
|
|
275
|
+
// DISABLED AND INERT ARE NOT TAB STOPS, whatever the tag says. The mount sets
|
|
276
|
+
// `inert` on every zone while the first-pass overlay is up — "the zones go
|
|
277
|
+
// inert under it" — so without this every covered control recorded `tab` and
|
|
278
|
+
// passed a rule asserting the keyboard can reach it, while the browser was
|
|
279
|
+
// refusing focus to all of them.
|
|
280
|
+
if (element.getAttribute('disabled') !== null)
|
|
281
|
+
return 'none';
|
|
282
|
+
if (element.closest('[inert]') !== null)
|
|
283
|
+
return 'none';
|
|
284
|
+
// `hidden` TOO, and for the same reason: the browser takes a hidden subtree
|
|
285
|
+
// out of the tab order, so a native button inside one is not reachable
|
|
286
|
+
// however focusable its tag is. Nothing in this package renders it today —
|
|
287
|
+
// which is exactly why it is worth handling here rather than later, since
|
|
288
|
+
// `controlSurface` is exported for hosts to run over their own chrome.
|
|
289
|
+
if (element.closest('[hidden]') !== null)
|
|
290
|
+
return 'none';
|
|
291
|
+
const raw = element.getAttribute('tabindex');
|
|
292
|
+
if (raw === null) {
|
|
293
|
+
// NATIVELY IN THE TAB ORDER. `a` only with an `href`: without one it is not
|
|
294
|
+
// focusable, and recording it as a tab stop would claim a reachability the
|
|
295
|
+
// markup does not have.
|
|
296
|
+
const tag = element.tagName.toLowerCase();
|
|
297
|
+
if (tag === 'button' || tag === 'input' || tag === 'select' || tag === 'textarea')
|
|
298
|
+
return 'tab';
|
|
299
|
+
return tag === 'a' && element.getAttribute('href') !== null ? 'tab' : 'none';
|
|
300
|
+
}
|
|
301
|
+
const index = Number(raw);
|
|
302
|
+
if (!Number.isInteger(index))
|
|
303
|
+
return 'none';
|
|
304
|
+
return index < 0 ? 'programmatic' : 'tab';
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Every id under `root`, gathered once.
|
|
308
|
+
*
|
|
309
|
+
* A SET RATHER THAN A QUERY PER TOKEN, and that is a correctness fix, not a
|
|
310
|
+
* speed one. An earlier revision resolved each IDREF with
|
|
311
|
+
* `` querySelectorAll(`[id="${id}"]`) `` — interpolating a value read off the
|
|
312
|
+
* DOM straight into a selector, which is verbatim the class the mount's focus
|
|
313
|
+
* token was written to avoid. Measured: `aria-describedby='a"]'` made this
|
|
314
|
+
* function THROW, and a token containing `],[id` resolved against the wrong
|
|
315
|
+
* elements. `controlSurface` is exported for hosts to run over their own
|
|
316
|
+
* chrome, where ids are tracker-derived, so the injection was reachable.
|
|
317
|
+
*
|
|
318
|
+
* SCOPED TO `root`, which is a real bound and is stated rather than hidden: an
|
|
319
|
+
* IDREF pointing into the host's own chrome outside the mounted surface reads
|
|
320
|
+
* as `dangling` here. That is the right default for a record ABOUT this
|
|
321
|
+
* surface, and a host taking it over its whole page gets the wider answer.
|
|
322
|
+
*/
|
|
323
|
+
function idsUnder(root) {
|
|
324
|
+
const ids = new Set();
|
|
325
|
+
// `root` ITSELF IS NOT IN ITS OWN `querySelectorAll`, so it is added by hand:
|
|
326
|
+
// a host may well hang the surface off an element that carries an id.
|
|
327
|
+
const own = root.getAttribute('id');
|
|
328
|
+
if (own !== null)
|
|
329
|
+
ids.add(own);
|
|
330
|
+
for (const element of root.querySelectorAll('[id]')) {
|
|
331
|
+
const id = element.getAttribute('id');
|
|
332
|
+
if (id !== null)
|
|
333
|
+
ids.add(id);
|
|
334
|
+
}
|
|
335
|
+
return ids;
|
|
336
|
+
}
|
|
337
|
+
function ariaOf(ids, element) {
|
|
338
|
+
const out = {};
|
|
339
|
+
for (const attribute of ENUMERATED_ARIA) {
|
|
340
|
+
const value = element.getAttribute(attribute);
|
|
341
|
+
if (value !== null)
|
|
342
|
+
out[attribute] = value;
|
|
343
|
+
}
|
|
344
|
+
// `aria-hidden` IS INHERITED, and the rule that reads it is about exactly
|
|
345
|
+
// that. A wrapper carrying it takes its descendants out of the accessibility
|
|
346
|
+
// tree WITHOUT taking them out of the tab order — which is the "focusable and
|
|
347
|
+
// announces nothing" case the rule exists to reject. Read off the element
|
|
348
|
+
// alone, the rule could not see the shape it was written for: the entry
|
|
349
|
+
// carried no `aria-hidden` at all and passed.
|
|
350
|
+
//
|
|
351
|
+
// The other enumerated states are properties of the element that carries
|
|
352
|
+
// them, so only this one is resolved through ancestry.
|
|
353
|
+
if (element.closest('[aria-hidden="true"]') !== null)
|
|
354
|
+
out['aria-hidden'] = 'true';
|
|
355
|
+
for (const attribute of IDREF_ARIA) {
|
|
356
|
+
const value = element.getAttribute(attribute);
|
|
357
|
+
if (value === null)
|
|
358
|
+
continue;
|
|
359
|
+
// EVERY id IT NAMES, because the attribute takes a list and a half-resolved
|
|
360
|
+
// reference is still broken for the token that dangles.
|
|
361
|
+
const named = value.split(/\s+/).filter((token) => token !== '');
|
|
362
|
+
out[attribute] =
|
|
363
|
+
named.length > 0 && named.every((id) => ids.has(id)) ? 'resolves' : 'dangling';
|
|
364
|
+
}
|
|
365
|
+
return out;
|
|
366
|
+
}
|
|
367
|
+
/**
|
|
368
|
+
* Every control under `root`, in document order, sorted so the record is stable.
|
|
369
|
+
*
|
|
370
|
+
* Pure and total. SORTED RATHER THAN LEFT IN DOCUMENT ORDER: a baseline is
|
|
371
|
+
* diffed by people, and a control moving between zones should read as one
|
|
372
|
+
* changed line rather than as a reordering of the whole file. Document order is
|
|
373
|
+
* a fact about layout, which the zone already carries.
|
|
374
|
+
*
|
|
375
|
+
* The bound it does NOT cover is stated where it can be checked: a control
|
|
376
|
+
* publishing itself on some fourth attribute is invisible here, and
|
|
377
|
+
* {@link CONTROL_ATTRIBUTES} is the list to extend.
|
|
378
|
+
*/
|
|
379
|
+
export function controlSurface(root) {
|
|
380
|
+
const entries = [];
|
|
381
|
+
const seen = new Set();
|
|
382
|
+
const ids = idsUnder(root);
|
|
383
|
+
for (const channel of CONTROL_ATTRIBUTES) {
|
|
384
|
+
for (const element of root.querySelectorAll(`[${channel}]`)) {
|
|
385
|
+
const control = element.getAttribute(channel);
|
|
386
|
+
// ONE ENTRY PER ELEMENT, even where a control carries two channels: the
|
|
387
|
+
// record is about the control, and a second row for one button would
|
|
388
|
+
// make every rule below count it twice.
|
|
389
|
+
if (control === null || seen.has(element))
|
|
390
|
+
continue;
|
|
391
|
+
seen.add(element);
|
|
392
|
+
const tag = element.tagName.toLowerCase();
|
|
393
|
+
entries.push({
|
|
394
|
+
zone: element.closest('.ig-zone')?.getAttribute('data-zone') ?? null,
|
|
395
|
+
channel,
|
|
396
|
+
control,
|
|
397
|
+
target: element.getAttribute('data-ig-target'),
|
|
398
|
+
value: element.getAttribute('data-ig-value') ?? element.getAttribute('data-ig-kind'),
|
|
399
|
+
tag,
|
|
400
|
+
role: element.getAttribute('role') ??
|
|
401
|
+
IMPLICIT_ROLES[tag] ??
|
|
402
|
+
conditionalRole(root, element, tag),
|
|
403
|
+
tabStop: tabStopOf(element),
|
|
404
|
+
inert: element.closest('[inert]') !== null,
|
|
405
|
+
name: nameSource(root, element),
|
|
406
|
+
aria: ariaOf(ids, element),
|
|
407
|
+
});
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
// FIELD BY FIELD, BY CODE POINT. `localeCompare` was both non-total and
|
|
411
|
+
// machine-dependent here: it treats `\u0000` as ignorable, so the separator
|
|
412
|
+
// bought nothing and distinct keys compared EQUAL, and with no locale it
|
|
413
|
+
// follows the runner's own ICU build — which would reorder a committed
|
|
414
|
+
// artifact that is compared with an order-sensitive `deepEqual`.
|
|
415
|
+
const key = (entry) => [
|
|
416
|
+
entry.zone ?? '',
|
|
417
|
+
entry.channel,
|
|
418
|
+
entry.control,
|
|
419
|
+
entry.target ?? '',
|
|
420
|
+
entry.value ?? '',
|
|
421
|
+
];
|
|
422
|
+
return [...entries].sort((a, b) => {
|
|
423
|
+
const left = key(a);
|
|
424
|
+
const right = key(b);
|
|
425
|
+
for (let index = 0; index < left.length; index += 1) {
|
|
426
|
+
const one = left[index] ?? '';
|
|
427
|
+
const other = right[index] ?? '';
|
|
428
|
+
if (one !== other)
|
|
429
|
+
return one < other ? -1 : 1;
|
|
430
|
+
}
|
|
431
|
+
return 0;
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
//# sourceMappingURL=baseline.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.js","sourceRoot":"","sources":["../../src/a11y/baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AAyFH;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAsB,MAAM,CAAC,MAAM,CAAC;IACjE,iBAAiB;IACjB,gBAAgB;IAChB,sBAAsB;IACtB,4EAA4E;IAC5E,0EAA0E;IAC1E,2EAA2E;IAC3E,qBAAqB;CACtB,CAAC,CAAC;AAEH;;;;;;;;GAQG;AACH,MAAM,cAAc,GAAqC,MAAM,CAAC,MAAM,CAAC;IACrE,MAAM,EAAE,QAAQ;IAChB,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,SAAS;IACb,EAAE,EAAE,UAAU;IACd,EAAE,EAAE,MAAM;IACV,EAAE,EAAE,MAAM;CACX,CAAC,CAAC;AAEH;;;;;;;GAOG;AACH;;;;;;;;GAQG;AACH,MAAM,WAAW,GAAqC,MAAM,CAAC,MAAM,CAAC;IAClE,MAAM,EAAE,QAAQ;IAChB,QAAQ,EAAE,UAAU;IACpB,KAAK,EAAE,OAAO;IACd,KAAK,EAAE,QAAQ;IACf,MAAM,EAAE,WAAW;IACnB,MAAM,EAAE,QAAQ;IAChB,IAAI,EAAE,SAAS;CAChB,CAAC,CAAC;AAEH,SAAS,eAAe,CACtB,IAAoB,EACpB,OAAuB,EACvB,GAAW;IAEX,IAAI,GAAG,KAAK,GAAG;QAAE,OAAO,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;IAC9E,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,QAAQ,CAAC;IACrF,yEAAyE;IACzE,yDAAyD;IACzD,IAAI,GAAG,KAAK,OAAO;QAAE,OAAO,WAAW,CAAC,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,IAAI,CAAC;IACxF,OAAO,IAAI,CAAC;AACd,CAAC;AAED,mFAAmF;AACnF,MAAM,eAAe,GAAsB,MAAM,CAAC,MAAM,CAAC;IACvD,WAAW;IACX,cAAc;IACd,cAAc;IACd,eAAe;IACf,aAAa;IACb,WAAW;IACX,YAAY;IACZ,cAAc;IACd,eAAe;CAChB,CAAC,CAAC;AAEH,6EAA6E;AAC7E,MAAM,UAAU,GAAsB,MAAM,CAAC,MAAM,CAAC;IAClD,eAAe;IACf,kBAAkB;IAClB,iBAAiB;CAClB,CAAC,CAAC;AAEH,MAAM,SAAS,GAAG,CAAC,CAAC;AAEpB,SAAS,SAAS,CAAC,IAAiB;IAClC,OAAO,SAAS,IAAI,IAAI,CAAC;AAC3B,CAAC;AAED;;;;;;;;GAQG;AACH,SAAS,WAAW,CAAC,OAAuB;IAC1C,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,MAAM,IAAI,GAAG,CAAC,IAAiB,EAAQ,EAAE;QACvC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,EAAE,CAAC;YACrB,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS;gBAAE,GAAG,IAAI,IAAI,CAAC,SAAS,IAAI,EAAE,CAAC;YAC7D,OAAO;QACT,CAAC;QACD,sEAAsE;QACtE,0EAA0E;QAC1E,yEAAyE;QACzE,yEAAyE;QACzE,oEAAoE;QACpE,IAAI,IAAI,CAAC,YAAY,CAAC,aAAa,CAAC,KAAK,MAAM;YAAE,OAAO;QACxD,IAAI,IAAI,CAAC,YAAY,CAAC,QAAQ,CAAC,KAAK,IAAI;YAAE,OAAO;QACjD,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;YAC/D,MAAM,KAAK,GAAG,IAAI,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YACrC,IAAI,KAAK,KAAK,SAAS;gBAAE,IAAI,CAAC,KAAK,CAAC,CAAC;QACvC,CAAC;IACH,CAAC,CAAC;IACF,IAAI,CAAC,OAAO,CAAC,CAAC;IACd,OAAO,GAAG,CAAC,IAAI,EAAE,CAAC;AACpB,CAAC;AAED;;;;;;;;;;GAUG;AACH,SAAS,UAAU,CAAC,IAAoB,EAAE,OAAuB;IAC/D,MAAM,QAAQ,GAAG,OAAO,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IAC1C,IAAI,QAAQ,KAAK,IAAI,IAAI,WAAW,CAAC,QAAQ,CAAC,KAAK,EAAE;QAAE,OAAO,IAAI,CAAC;IACnE,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACtC,IAAI,EAAE,KAAK,IAAI,IAAI,EAAE,KAAK,EAAE;QAAE,OAAO,KAAK,CAAC;IAC3C,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,EAAE,CAAC;QACnD,IAAI,KAAK,CAAC,YAAY,CAAC,KAAK,CAAC,KAAK,EAAE,IAAI,WAAW,CAAC,KAAK,CAAC,KAAK,EAAE;YAAE,OAAO,IAAI,CAAC;IACjF,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,UAAU,CAAC,IAAoB,EAAE,OAAuB;IAC/D,wEAAwE;IACxE,gEAAgE;IAChE,8EAA8E;IAC9E,4DAA4D;IAC5D,2EAA2E;IAC3E,8EAA8E;IAC9E,4EAA4E;IAC5E,4EAA4E;IAC5E,gCAAgC;IAChC,EAAE;IACF,wEAAwE;IACxE,0EAA0E;IAC1E,uEAAuE;IACvE,8EAA8E;IAC9E,wEAAwE;IACxE,gEAAgE;IAChE,EAAE;IACF,8EAA8E;IAC9E,yEAAyE;IACzE,2EAA2E;IAC3E,6CAA6C;IAC7C,MAAM,UAAU,GAAG,OAAO,CAAC,YAAY,CAAC,iBAAiB,CAAC,CAAC;IAC3D,IAAI,UAAU,KAAK,IAAI,EAAE,CAAC;QACxB,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;QACtE,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC;YACnD,IAAI,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;gBAAE,IAAI,IAAI,WAAW,CAAC,MAAM,CAAC,CAAC;QACnF,CAAC;QACD,OAAO,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,iBAAiB,CAAC;IAC1D,CAAC;IAED,MAAM,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC,YAAY,CAAC,CAAC;IACjD,4EAA4E;IAC5E,6EAA6E;IAC7E,qCAAqC;IACrC,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,YAAY,CAAC;IACxE,IAAI,WAAW,CAAC,OAAO,CAAC,KAAK,EAAE;QAAE,OAAO,MAAM,CAAC;IAC/C,OAAO,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC;AACtD,CAAC;AAED,SAAS,SAAS,CAAC,OAAuB;IACxC,8EAA8E;IAC9E,2EAA2E;IAC3E,6EAA6E;IAC7E,2EAA2E;IAC3E,iCAAiC;IACjC,IAAI,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAC7D,IAAI,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IACvD,4EAA4E;IAC5E,uEAAuE;IACvE,2EAA2E;IAC3E,0EAA0E;IAC1E,uEAAuE;IACvE,IAAI,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IACxD,MAAM,GAAG,GAAG,OAAO,CAAC,YAAY,CAAC,UAAU,CAAC,CAAC;IAC7C,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QACjB,4EAA4E;QAC5E,2EAA2E;QAC3E,wBAAwB;QACxB,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;QAC1C,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,UAAU;YAAE,OAAO,KAAK,CAAC;QAChG,OAAO,GAAG,KAAK,GAAG,IAAI,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,KAAK,IAAI,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC;IAC/E,CAAC;IACD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC1B,IAAI,CAAC,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC;IAC5C,OAAO,KAAK,GAAG,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC;AAC5C,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,SAAS,QAAQ,CAAC,IAAoB;IACpC,MAAM,GAAG,GAAG,IAAI,GAAG,EAAU,CAAC;IAC9B,8EAA8E;IAC9E,sEAAsE;IACtE,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;IACpC,IAAI,GAAG,KAAK,IAAI;QAAE,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC/B,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,gBAAgB,CAAC,MAAM,CAAC,EAAE,CAAC;QACpD,MAAM,EAAE,GAAG,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QACtC,IAAI,EAAE,KAAK,IAAI;YAAE,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,MAAM,CAAC,GAAwB,EAAE,OAAuB;IAC/D,MAAM,GAAG,GAA2B,EAAE,CAAC;IACvC,KAAK,MAAM,SAAS,IAAI,eAAe,EAAE,CAAC;QACxC,MAAM,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QAC9C,IAAI,KAAK,KAAK,IAAI;YAAE,GAAG,CAAC,SAAS,CAAC,GAAG,KAAK,CAAC;IAC7C,CAAC;IACD,0EAA0E;IAC1E,6EAA6E;IAC7E,8EAA8E;IAC9E,0EAA0E;IAC1E,wEAAwE;IACxE,8CAA8C;IAC9C,EAAE;IACF,yEAAyE;IACzE,uDAAuD;IACvD,IAAI,OAAO,CAAC,OAAO,CAAC,sBAAsB,CAAC,KAAK,IAAI;QAAE,GAAG,CAAC,aAAa,CAAC,GAAG,MAAM,CAAC;IAClF,KAAK,MAAM,SAAS,IAAI,UAAU,EAAE,CAAC;QACnC,MAAM,KAAK,GAAG,OAAO,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC;QAC9C,IAAI,KAAK,KAAK,IAAI;YAAE,SAAS;QAC7B,4EAA4E;QAC5E,wDAAwD;QACxD,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;QACjE,GAAG,CAAC,SAAS,CAAC;YACZ,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,CAAC;IACnF,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,cAAc,CAAC,IAAoB;IACjD,MAAM,OAAO,GAAmB,EAAE,CAAC;IACnC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAkB,CAAC;IACvC,MAAM,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC;IAC3B,KAAK,MAAM,OAAO,IAAI,kBAAkB,EAAE,CAAC;QACzC,KAAK,MAAM,OAAO,IAAI,IAAI,CAAC,gBAAgB,CAAC,IAAI,OAAO,GAAG,CAAC,EAAE,CAAC;YAC5D,MAAM,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC,OAAO,CAAC,CAAC;YAC9C,wEAAwE;YACxE,qEAAqE;YACrE,wCAAwC;YACxC,IAAI,OAAO,KAAK,IAAI,IAAI,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC;gBAAE,SAAS;YACpD,IAAI,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;YAClB,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC;YAC1C,OAAO,CAAC,IAAI,CAAC;gBACX,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,EAAE,YAAY,CAAC,WAAW,CAAC,IAAI,IAAI;gBACpE,OAAO;gBACP,OAAO;gBACP,MAAM,EAAE,OAAO,CAAC,YAAY,CAAC,gBAAgB,CAAC;gBAC9C,KAAK,EAAE,OAAO,CAAC,YAAY,CAAC,eAAe,CAAC,IAAI,OAAO,CAAC,YAAY,CAAC,cAAc,CAAC;gBACpF,GAAG;gBACH,IAAI,EACF,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC;oBAC5B,cAAc,CAAC,GAAG,CAAC;oBACnB,eAAe,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC;gBACrC,OAAO,EAAE,SAAS,CAAC,OAAO,CAAC;gBAC3B,KAAK,EAAE,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,KAAK,IAAI;gBAC1C,IAAI,EAAE,UAAU,CAAC,IAAI,EAAE,OAAO,CAAC;gBAC/B,IAAI,EAAE,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC;aAC3B,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,wEAAwE;IACxE,4EAA4E;IAC5E,yEAAyE;IACzE,uEAAuE;IACvE,iEAAiE;IACjE,MAAM,GAAG,GAAG,CAAC,KAAmB,EAAqB,EAAE,CAAC;QACtD,KAAK,CAAC,IAAI,IAAI,EAAE;QAChB,KAAK,CAAC,OAAO;QACb,KAAK,CAAC,OAAO;QACb,KAAK,CAAC,MAAM,IAAI,EAAE;QAClB,KAAK,CAAC,KAAK,IAAI,EAAE;KAClB,CAAC;IACF,OAAO,CAAC,GAAG,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QAChC,MAAM,IAAI,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACpB,MAAM,KAAK,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC;QACrB,KAAK,IAAI,KAAK,GAAG,CAAC,EAAE,KAAK,GAAG,IAAI,CAAC,MAAM,EAAE,KAAK,IAAI,CAAC,EAAE,CAAC;YACpD,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YAC9B,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;YACjC,IAAI,GAAG,KAAK,KAAK;gBAAE,OAAO,GAAG,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QACjD,CAAC;QACD,OAAO,CAAC,CAAC;IACX,CAAC,CAAC,CAAC;AACL,CAAC"}
|
package/dist/audit/findings.d.ts
CHANGED
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
*
|
|
27
27
|
* @see https://github.com/autnmy/issuegraph/blob/main/SPEC.md
|
|
28
28
|
*/
|
|
29
|
-
import type { GraphDocument, IssueRef } from '@issuegraph/store';
|
|
29
|
+
import type { EdgeId, GraphDocument, IssueRef } from '@issuegraph/store';
|
|
30
30
|
/**
|
|
31
31
|
* The four classes, in the order §17d states them — which is also the order
|
|
32
32
|
* findings are reported in, so a list is stable between runs.
|
|
@@ -117,6 +117,23 @@ export declare const AUDIT_CLASS_SPECS: Readonly<{
|
|
|
117
117
|
* four-row builder table, and it still could not reach a value deserialized
|
|
118
118
|
* from a wire. Removing the boundary was cheaper than either.
|
|
119
119
|
*/
|
|
120
|
+
/**
|
|
121
|
+
* The edge a finding's remedy acts on, with the ends it joins.
|
|
122
|
+
*
|
|
123
|
+
* THE ENDS ARE CARRIED, NOT JUST THE ID, and that is what lets `./panel.ts`
|
|
124
|
+
* withhold a remedy the reader could not complete. A remedy publishes
|
|
125
|
+
* `select-edge`, which the host resolves against the DRAWN document — and the
|
|
126
|
+
* audit reads a document that is not always the one on screen, so an edge
|
|
127
|
+
* whose ends are off the page selects nothing and the reader presses a button
|
|
128
|
+
* that silently does nothing. `known` answers for issues; these are the issues
|
|
129
|
+
* to ask about. The id alone would have to be parsed to recover them, and its
|
|
130
|
+
* spelling is `@issuegraph/core`'s business rather than this module's.
|
|
131
|
+
*/
|
|
132
|
+
export interface AuditEdge {
|
|
133
|
+
readonly id: EdgeId;
|
|
134
|
+
readonly from: IssueRef;
|
|
135
|
+
readonly to: IssueRef;
|
|
136
|
+
}
|
|
120
137
|
export interface AuditFinding {
|
|
121
138
|
readonly kind: AuditClass;
|
|
122
139
|
readonly severity: AuditSeverity;
|
|
@@ -136,16 +153,79 @@ export interface AuditFinding {
|
|
|
136
153
|
readonly members: readonly IssueRef[];
|
|
137
154
|
/** What the reader can be told, in one sentence. Never parsed, never a code. */
|
|
138
155
|
readonly detail: string;
|
|
156
|
+
/**
|
|
157
|
+
* A cycle's ordered `blocked-by` walk — `a -> b` reads "a waits on b" — with
|
|
158
|
+
* each member named ONCE and the return edge to the head implied.
|
|
159
|
+
*
|
|
160
|
+
* Only ever on a `cycle` finding — this module builds it nowhere else, and
|
|
161
|
+
* `./panel.ts` gates the line it draws on the class as well as on the field,
|
|
162
|
+
* so a host that hand-builds a finding cannot put a walk on a stale-blocker
|
|
163
|
+
* card. Absent whenever §6.6 declines to name a walk for the component, or the
|
|
164
|
+
* host wired no reader answer for it. See {@link AuditGraph.cycleWalk}.
|
|
165
|
+
*
|
|
166
|
+
* NOT FOLDED INTO {@link AuditFinding.members}, which is documented one field
|
|
167
|
+
* up as a sorted SET: sorting is exactly what destroys the ORDER, and the
|
|
168
|
+
* order is the only thing a walk carries that `members` does not. (A
|
|
169
|
+
* conforming walk names no ref twice either, so the set's other rule is not
|
|
170
|
+
* what keeps them apart.)
|
|
171
|
+
*
|
|
172
|
+
* NOT FOLDED INTO `detail` either, which is left byte-identical by this
|
|
173
|
+
* field's arrival. `./panel.ts` records why the card draws the walk as its own
|
|
174
|
+
* line rather than composing it into the sentence.
|
|
175
|
+
*/
|
|
176
|
+
readonly walk?: readonly IssueRef[] | undefined;
|
|
177
|
+
/**
|
|
178
|
+
* The ONE edge this finding is about, when it is about an edge at all.
|
|
179
|
+
*
|
|
180
|
+
* §3 of `RULINGS.md` says a remedy *"selects the finding's issue or edge and
|
|
181
|
+
* hands the work to the inspector"*. `members` cannot answer that: it is
|
|
182
|
+
* documented one field up as a SORTED SET, and sorting is exactly what
|
|
183
|
+
* destroys the direction — `['#201', '#533']` cannot say which end declared
|
|
184
|
+
* the `blocked-by`. The edge is in hand where the finding is built, so it is
|
|
185
|
+
* carried rather than reconstructed.
|
|
186
|
+
*
|
|
187
|
+
* PRESENT ON THE TWO EDGE CLASSES ONLY. A cycle is about a COMPONENT and
|
|
188
|
+
* names no single edge — picking one here would be this module choosing which
|
|
189
|
+
* edge a reader should drop, which is the judgment §17d keeps with the owner.
|
|
190
|
+
* An encoding refusal is about an issue whose edges were never read, so there
|
|
191
|
+
* is no edge to name.
|
|
192
|
+
*/
|
|
193
|
+
readonly edge?: AuditEdge | undefined;
|
|
139
194
|
}
|
|
140
195
|
/**
|
|
141
196
|
* An issue whose declaration the reader refused.
|
|
142
197
|
*
|
|
143
198
|
* The `diagnostic` is the reader's own words when the host has them — this
|
|
144
199
|
* package never produces one, because it never sees a body.
|
|
200
|
+
*
|
|
201
|
+
* BOTH OPTIONAL FIELDS ARE FACTS ABOUT THE RAW BODY, which is the whole reason
|
|
202
|
+
* they live HERE rather than on {@link AuditFinding}. §17d draws the refusal as
|
|
203
|
+
* its own block — the ref, the reader's diagnostic, and the source line the
|
|
204
|
+
* reader stopped on — and none of those is reconstructible from a finding:
|
|
205
|
+
* `detail` is documented as "what the reader can be told, in one sentence.
|
|
206
|
+
* Never parsed, never a code", so recovering a source line out of it would make
|
|
207
|
+
* a sentence a wire format.
|
|
208
|
+
*
|
|
209
|
+
* AND A FINDING IS ONE SHAPE OVER FOUR CLASSES. Three of them can never carry
|
|
210
|
+
* either field, so declaring them there would render an absence as a value in
|
|
211
|
+
* the one module whose fourth class exists to refuse exactly that — while
|
|
212
|
+
* re-opening the caller-supplied surface {@link AuditFinding} records six review
|
|
213
|
+
* rounds closing. `./refused.ts` reads these refusals directly, and the audit
|
|
214
|
+
* still turns each one into a finding for the count and the rail's bar.
|
|
145
215
|
*/
|
|
146
216
|
export interface EncodingRefusal {
|
|
147
217
|
readonly ref: IssueRef;
|
|
148
218
|
readonly diagnostic?: string | undefined;
|
|
219
|
+
/**
|
|
220
|
+
* The line of the issue's own body the reader stopped on — the frame's
|
|
221
|
+
* `blocked-by: [231, 234`, drawn muted and in mono under the diagnostic.
|
|
222
|
+
*
|
|
223
|
+
* RAW BODY TEXT, so it can carry anything a body can, markup included.
|
|
224
|
+
* Nothing in this package writes a tag around it: `./refused.ts` builds an
|
|
225
|
+
* {@link ElementSpec} and the viewer's `renderMarkup` owns the escaping,
|
|
226
|
+
* which is the same rule `./panel.ts` follows for `detail`.
|
|
227
|
+
*/
|
|
228
|
+
readonly sourceLine?: string | undefined;
|
|
149
229
|
}
|
|
150
230
|
/**
|
|
151
231
|
* The two answers the audit needs from a reader, and does not compute.
|
|
@@ -197,6 +277,32 @@ export interface AuditGraph {
|
|
|
197
277
|
* as naming its canonical.
|
|
198
278
|
*/
|
|
199
279
|
readonly duplicateCanonical: (ref: IssueRef) => IssueRef | null;
|
|
280
|
+
/**
|
|
281
|
+
* `Model.cycleWalk` — the ordered `blocked-by` walk of the stuck group a ref
|
|
282
|
+
* belongs to, or `null` where §6.6 declines to name one.
|
|
283
|
+
*
|
|
284
|
+
* A FUNCTION, the same shape as `duplicateCanonical` one field up, and for the
|
|
285
|
+
* same reason: the host owns the translation between a store reference and a
|
|
286
|
+
* model key, so the question is asked in the STORE's own spelling. An array
|
|
287
|
+
* running alongside `cycles` would have been the alternative and it does not
|
|
288
|
+
* survive this file — `cycleFindings` below opens by FILTERING components out,
|
|
289
|
+
* which shifts every index after the first drop — let alone a host that cannot
|
|
290
|
+
* translate one component and drops it.
|
|
291
|
+
*
|
|
292
|
+
* OPTIONAL, UNLIKE THE TWO FIELDS ABOVE, and the ground for the difference is
|
|
293
|
+
* SPEC's rather than a judgement about severity. §6.6 says a reader that omits
|
|
294
|
+
* the walk is still conforming, so this port has to be able to express one.
|
|
295
|
+
* The paragraph above refuses a thinner AUDIT read as a complete one; an
|
|
296
|
+
* absent walk removes no finding, changes no count and weakens no class — the
|
|
297
|
+
* card simply draws no arrow, which is what shipped before this field existed.
|
|
298
|
+
*
|
|
299
|
+
* WHAT ABSENCE CANNOT SAY, stated because it is a real shortfall rather than a
|
|
300
|
+
* detail. Not wired by the host, refused by §6.6, and answered `null` for a
|
|
301
|
+
* ref the reader does not hold are three different facts, and they arrive here
|
|
302
|
+
* as one. Nothing downstream can tell them apart, so nothing downstream can
|
|
303
|
+
* explain to a reader why one cycle card has an arrow and the next does not.
|
|
304
|
+
*/
|
|
305
|
+
readonly cycleWalk?: ((ref: IssueRef) => readonly IssueRef[] | null) | undefined;
|
|
200
306
|
}
|
|
201
307
|
export interface AuditInput {
|
|
202
308
|
readonly document: GraphDocument;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"findings.d.ts","sourceRoot":"","sources":["../../src/audit/findings.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,QAAQ,EAAc,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"findings.d.ts","sourceRoot":"","sources":["../../src/audit/findings.ts"],"names":[],"mappings":"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"}
|