@issuegraph/editor 0.4.0 → 0.10.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/README.md +31 -6
- package/dist/a11y/baseline.d.ts +182 -0
- package/dist/a11y/baseline.d.ts.map +1 -0
- package/dist/a11y/baseline.js +430 -0
- package/dist/a11y/baseline.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 +1 -1
- package/dist/create/draft.d.ts +17 -0
- package/dist/create/draft.d.ts.map +1 -1
- package/dist/create/draft.js +19 -0
- package/dist/create/draft.js.map +1 -1
- package/dist/create/keys.d.ts +70 -5
- package/dist/create/keys.d.ts.map +1 -1
- package/dist/create/keys.js +78 -17
- 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 +6 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +12 -2
- package/dist/index.js.map +1 -1
- package/dist/overlay/render.d.ts.map +1 -1
- package/dist/overlay/render.js +54 -14
- package/dist/overlay/render.js.map +1 -1
- package/dist/overlay/styles.d.ts +1 -1
- package/dist/overlay/styles.d.ts.map +1 -1
- package/dist/overlay/styles.js +40 -0
- 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/scale/ladder.d.ts.map +1 -1
- package/dist/scale/ladder.js +10 -3
- package/dist/scale/ladder.js.map +1 -1
- package/dist/scale/render.d.ts.map +1 -1
- package/dist/scale/render.js +9 -1
- package/dist/scale/render.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 +200 -10
- package/dist/workspace/host.d.ts.map +1 -1
- package/dist/workspace/host.js +420 -42
- package/dist/workspace/host.js.map +1 -1
- package/dist/workspace/inspector.d.ts +45 -1
- package/dist/workspace/inspector.d.ts.map +1 -1
- package/dist/workspace/inspector.js +11 -0
- package/dist/workspace/inspector.js.map +1 -1
- package/dist/workspace/mount.d.ts +52 -5
- package/dist/workspace/mount.d.ts.map +1 -1
- package/dist/workspace/mount.js +1366 -58
- package/dist/workspace/mount.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 +402 -1
- package/dist/workspace/render.d.ts.map +1 -1
- package/dist/workspace/render.js +941 -35
- package/dist/workspace/render.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 +554 -41
- package/dist/workspace/styles.js.map +1 -1
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -138,7 +138,7 @@ let { draft, proposal } = createReducer(IDLE_CREATE_DRAFT, { kind: 'begin', sour
|
|
|
138
138
|
|
|
139
139
|
**The draft carries no path identity**, deliberately. A `source` filled by a drag and one filled by `R` are the same fact, and a field recording which arrived would be a place for the paths to grow apart. What genuinely differs between them is where the picker is *drawn*, and that is geometry — `pickerPlacement`, from measured bounds — rather than state.
|
|
140
140
|
|
|
141
|
-
**Direction is the gather order**, `from` = source. Nothing infers it: §17b states direction and offers a flip,
|
|
141
|
+
**Direction is the gather order**, `from` = source. Nothing infers it: §17b states direction and offers a flip — both on the selected relationship's own row, drawn by `renderWorkspace`, so a host that wires the published attributes without mounting gets them too — and `pickerView` re-derives after the edit lands, so a wrong guess is one act from correct. That is the same reasoning `picker/view.ts` records for retyping across the directed/symmetric split.
|
|
142
142
|
|
|
143
143
|
**The keyboard is a full loop with no pointer step.** `keyIntent` is a pure key map — a key **press** and a context in, an intent out, no DOM — exactly as the viewer's `navigation.ts` is, so the whole map is exhaustively testable on a runtime with no DOM at all. The digits read `EDGE_FIELDS` from `@issuegraph/core` rather than restating it, so a sixth field gets a `6` for free and the picker and the keyboard cannot disagree. `⌫` binds **both** `Backspace` and `Delete`, because the key §17b draws as `⌫` reports differently across keyboards and binding one would make "no pointer" false on the other. An unbound key answers `none` and is left to the host.
|
|
144
144
|
|
|
@@ -154,7 +154,7 @@ element.addEventListener('keydown', (event) => {
|
|
|
154
154
|
selectedEdge,
|
|
155
155
|
// Which of the create flow's own interactions is the keyboard in?
|
|
156
156
|
// Only the shell can see this.
|
|
157
|
-
interaction: activeInteraction(), // 'canvas' | 'target-search' | 'elsewhere'
|
|
157
|
+
interaction: activeInteraction(), // 'canvas' | 'kind-chooser' | 'target-search' | 'elsewhere'
|
|
158
158
|
});
|
|
159
159
|
if (intent.kind === 'none') return; // someone else's key — let it through
|
|
160
160
|
event.preventDefault();
|
|
@@ -164,17 +164,22 @@ element.addEventListener('keydown', (event) => {
|
|
|
164
164
|
|
|
165
165
|
**It asks about *our* interaction, not about who else might own the key** — and that is the design decision worth reading, because it replaced the obvious one. Four review rounds each found a different owner the map had failed to anticipate: the platform's `Cmd+R`, the target search's digits, an input method's `⏎`, then an unrelated editable control's `Escape`. Every fix was correct and every one invited the next, because they answered an unanswerable question. *Who else might own this press?* is an inventory of the **host's** widgets — unbounded from in here, and one entry longer every time a host grows a control.
|
|
166
166
|
|
|
167
|
-
So `CreateInteraction` enumerates **this design's own flow**, which §17b fixes at
|
|
167
|
+
So `CreateInteraction` enumerates **this design's own flow**, which §17b fixes at four states, and the host says which one it is in:
|
|
168
168
|
|
|
169
169
|
| state | what reaches the map |
|
|
170
170
|
|---|---|
|
|
171
171
|
| `canvas` | every binding |
|
|
172
|
+
| `kind-chooser` | `R`, the digits `1`–`5`, and `Escape` — **not** `⏎` |
|
|
172
173
|
| `target-search` | only `⏎` and `Escape` |
|
|
173
174
|
| `elsewhere` | nothing |
|
|
174
175
|
|
|
175
|
-
`elsewhere` is what closes the set: it is *everything that is not our own
|
|
176
|
+
`elsewhere` is what closes the set: it is *everything that is not one of our own steps* — an inline title, a filter, a modal, a control this package has never heard of. A fifth widget adds no code here, and `Escape` is surrendered along with the rest, because that control needs `Escape` to cancel its own edit.
|
|
176
177
|
|
|
177
|
-
|
|
178
|
+
**`kind-chooser` is a fact about the DRAFT, not about focus** — a source gathered and no kind yet, with a focused text box excluded so a search box keeps its own digits. It was folded into `canvas` until [#152](https://github.com/autnmy/issuegraph/issues/152), on the assumption that focus stays on the row a draft was begun from; activating the add control *replaces* that control with the kind list, so focus lands wherever the host's restore puts it, and where a filter has emptied the list there is no row to land on at all. `canvas` answers only for a focused row, so the chooser's own digits were handed back at the one step whose entire purpose is to be answered with a digit.
|
|
179
|
+
|
|
180
|
+
`⏎` is withheld there deliberately, and it is the reason this is a state rather than an alias for `canvas`: `commit-target` fires on `match`, a target query survives a restarted draft, and a `⏎` at the kind step would commit a target before any kind was chosen — while also being taken from the kind button under focus.
|
|
181
|
+
|
|
182
|
+
Which bindings survive `kind-chooser` and `target-search` is **data on the binding table**, so no call site decides it and a sixth binding is a compile error until the table answers. `⏎` and `Escape` reach it because the search box is focused at exactly the moment `⏎` must commit the target — the middle of `R → digit → search → ⏎`. Its printable keys do not: most issue references carry a digit, so a map that claimed `1`–`5` there would eat nearly every query, and `⌫` deletes a *character* rather than the reader's selected edge.
|
|
178
183
|
|
|
179
184
|
`interaction` is **required, not optional**. Every default is wrong for some host, and the plausible one — assume the canvas — is the one that steals keystrokes.
|
|
180
185
|
|
|
@@ -229,6 +234,20 @@ The selection reaches the **canvas** too, through two additive options on `Scale
|
|
|
229
234
|
|
|
230
235
|
Ranking a unit's members is `heaviestRow`'s job, in the audit module, **because the weights live there**. The shortcut — take the first matching entry in `overlay.rows` — is wrong in a way that looks right: those rows are sorted by `ref`, lexicographically, so a `stale-blocker` on `a` masks a `cycle` on `b`. A row's severity is the heaviest across its **members**, not its lead. A `together-with` unit is one row and several refs, and a finding can name a member that does not lead — read off the lead alone, an affected unit renders clean, which is the audit failing silently on exactly the rows where an encoding error is hardest to see.
|
|
231
236
|
|
|
237
|
+
**An unsettled write the reader can act on gets a card.** §17b draws `failed` as a ghost with a ✕ and `conflict` as a doubled line, and gives each a set of resolutions: `retry` and `discard mine` for a failure, plus `view diff` and `retry on latest` for a conflict — and **never** an auto-merge. `WorkspaceOptions.recoveries` is the narrow input those cards need: the state, the reason or the difference, and the panel it belongs on. Not the write ledger, which carries a mutation and an entire second document per conflict.
|
|
238
|
+
|
|
239
|
+
**The buttons are read from `OVERLAY_TREATMENTS`, which is what makes the prohibition structural.** A card emits one control per affordance the grammar table declares for its state, through a mapping declared `satisfies Record<OverlayAffordance, string>` — so a fourth affordance is a compile error rather than a control that renders and does nothing, and since `OverlayAffordance` has no `merge` member no table entry can produce one. Be exact about what that buys: **no merge button can be drawn**. The behavioural guarantee is one layer down, in a store with no merge call and a `HostEffect` with no merge arm.
|
|
240
|
+
|
|
241
|
+
`retry` and `retryOnLatest` are separate words because they are separate calls. The second re-reads and **adopts the upstream document as the new base** before dispatching again, so the order can move on the press — a single word would make one card understate what its button does.
|
|
242
|
+
|
|
243
|
+
**Every card control carries `data-ig-target` naming its write.** The reducer's `retry` and `discard` arms emit no effect without it, so an omitted attribute leaves three controls that render, read correctly and do nothing. `view-diff` is a `control` case beside them rather than a command arm of its own, because the shell turns every `data-ig-command` into a `control` and an arm outside that shape has no route from a button.
|
|
244
|
+
|
|
245
|
+
**The difference is narrowed by the panel's key set, not by the carrier.** An issue panel speaks for every member of its slot, so a together unit's lead states a partner's conflict — and scoping the difference to the carrier alone would drop the partner's own edge from the one panel entitled to show it. The reader's side of that difference is rebuilt from the mutation through `edgeChangeFor`, because the landed document by contract does not carry an unlanded edit. It covers **issues as well as edges**: §17b's stated cause is that the body changed upstream, and an edges-only difference is empty for the commonest conflict there is.
|
|
246
|
+
|
|
247
|
+
**A recovery with no panel is still drawn.** Where a refusal whose carrier is `null` is dropped — there is nowhere to state it, and the cost is a sentence — a recovery in that position keeps the reader's only retry and discard, so it goes in a region at the foot of the panel under a heading that says why it is not on a relationship.
|
|
248
|
+
|
|
249
|
+
**Not yet drawn: the gold double line.** §17b's conflict *treatment* is a doubled line holding both versions, and `second-version` is a mark for whoever computed the layout. The card is the *affordance* surface; the treatment is [#102](https://github.com/autnmy/issuegraph/issues/102).
|
|
250
|
+
|
|
232
251
|
**Dark only.** The pass-2 brief carries "light + dark" over from pass 1; light was cut after that pass. There is no forked token set and no `prefers-color-scheme` block — the palette is the viewer's, reached through its custom properties.
|
|
233
252
|
|
|
234
253
|
## The mount
|
|
@@ -259,11 +278,17 @@ handle.destroy(); // every listener off, the nodes it built rem
|
|
|
259
278
|
|
|
260
279
|
**The canvas draws each edge's write states.** `renderWorkspace` and `renderScaleLadder` take `projected` — the store's own `ProjectedEdge` list — and the ladder attaches an overlay per edge the tier draws: `pending-write`'s dash, an `invalid` or `failed` cross, a `conflict`'s second version, composed with the selection halo on the same line. The mount passes `snapshot.projected` and reconciles the viewer document the host projected with it — the unsettled edges are added, and a landed edge the store hides behind a pending retype or flip is dropped — because the host projects the **order** from the landed document, which must not move for an edit that did not land, while what to **draw** is the store's projection by the store's own contract. The order's status is published on the surface as `data-order="held"` while a write is in flight, in the store's vocabulary, so a host says so without this package inventing the sentence.
|
|
261
280
|
|
|
281
|
+
**The first pass is composed, and the scanner is not.** `MountWorkspaceOptions.firstPass` takes one bundle — a `CandidateSource`, the queue's `FirstPassWords`, and three strings the mount draws itself (`exit`, `scanning`, `scanFailed`) — all of it or none, because a scan with no words cannot be drawn and words with no scan have nothing to draw. Supplied, §17a's entry opens a modal over the three zones: the scan, then the queue, then whatever the reader answers. Absent, the mount withholds the command and the entry is inert — the button is drawn *inside* the surface the mount rewrites on every render, so unlike a control a host draws beside the element, it cannot be intercepted from outside. Why no default scanner: the evidence §17e asks for lives in issue bodies, comments and the host's own index, which is the tracker's world and precisely what `@issuegraph/store` refuses to fetch; a heuristic in here would be one vendor's idea of a duplicate, shipped with no way to replace it. `demo/src/firstpass.ts` is a worked example of the host half.
|
|
282
|
+
|
|
283
|
+
**Consent and progress are properties of the shape.** Nothing but an `apply` builds a `Proposal` — opening, drawing, rejecting and skipping emit nothing because there is no arm on which they could — and the mount is pinned at the composed level: a drawn queue writes nothing, a whole queue answered `N`/`S` writes nothing, and one `Y` writes exactly one edge. The denominator is the candidates the host found and never the backlog size, and the mount hands the renderer a `QueueState`, a type that does not hold the backlog size at all. `⌫` steps the queue back and calls the store's own `discardMine`; it does **not** propose a compensating `delete`, so a create that already landed stays landed — the store has no undo for one, and inventing a second retraction path is what `firstpass/queue.ts` refuses in terms.
|
|
284
|
+
|
|
285
|
+
**Closing re-scans, and remembers.** A queue held across a close would go on asking about relationships the reader has since made, so it is dropped — but the candidates already applied or rejected ride outside the phase and filter the next scan, or forty rejections would come back on the next open. A `skip` is *not* filtered: §17e's `S` is "not now", and the deferred set is what a second pass is built from. Each scan carries a generation, so a slow scan answering after a close-and-reopen cannot beat the fresh one.
|
|
286
|
+
|
|
262
287
|
**Words are the host's, as everywhere here.** `MountWords` extends `WorkspaceWords` with the picker's vocabulary and the chrome's five strings; `keys` is the one optional entry. A command the reducer does not know changes nothing, so a host's own chrome may publish `data-ig-command` on the same attribute outside the element and read it from its own listener — the demo's theme switch does exactly that, and hands the writes log's `retry` and `discard` to `handle.dispatch`.
|
|
263
288
|
|
|
264
289
|
## Status
|
|
265
290
|
|
|
266
|
-
The first-pass review queue
|
|
291
|
+
The first-pass review queue is composed from `0.6.0`: §17a's `First pass →` entry opens it, and a host supplies the one thing this package refuses to — a `CandidateSource`. §17e's multi-select bulk path is not composed yet ([#140](https://github.com/autnmy/issuegraph/issues/140)); `planBatch` and `resumeBatch` ship and are tested, and what they still need is a workspace that can hold a selection SET. The mount is this package's from `0.3.0`; what a host still supplies is what the specification puts there — the data source, the order, the executor holds, the words, the theme — and every listener is the mount's.
|
|
267
292
|
|
|
268
293
|
## Licence
|
|
269
294
|
|
|
@@ -0,0 +1,182 @@
|
|
|
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
|
+
/** A text node, or anything else with no tag. */
|
|
75
|
+
export interface SurfaceNode {
|
|
76
|
+
readonly nodeType: number;
|
|
77
|
+
readonly nodeValue: string | null;
|
|
78
|
+
}
|
|
79
|
+
/** The slice of `Element` this module reads. */
|
|
80
|
+
export interface SurfaceElement extends SurfaceNode {
|
|
81
|
+
readonly tagName: string;
|
|
82
|
+
readonly childNodes: ArrayLike<SurfaceNode>;
|
|
83
|
+
getAttribute(name: string): string | null;
|
|
84
|
+
closest(selectors: string): SurfaceElement | null;
|
|
85
|
+
querySelectorAll(selectors: string): Iterable<SurfaceElement>;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Where a control's accessible name comes from, or why it has none.
|
|
89
|
+
*
|
|
90
|
+
* `label` IS A REAL NAME AND WAS MISSING. `scale/render.ts` wraps its search
|
|
91
|
+
* input in a `<label>` carrying the text — the input has a genuine accessible
|
|
92
|
+
* name and no text of its own, no `aria-label` and no `aria-labelledby`. Read
|
|
93
|
+
* without this, it came back `none`, so the naming rule reported a false
|
|
94
|
+
* failure on correct markup and the control could not join the baseline at all.
|
|
95
|
+
* An exported reader that is wrong about native HTML is worse than no reader.
|
|
96
|
+
*
|
|
97
|
+
* `empty` IS ITS OWN ANSWER, not a kind of `none`. An `aria-label=""` is an
|
|
98
|
+
* author who meant to supply a name and supplied nothing — a different defect
|
|
99
|
+
* from a control nobody labelled, and one that reads as deliberate in the
|
|
100
|
+
* markup. Recording it separately is what lets a rule name it. THE VALUE IS
|
|
101
|
+
* NEVER RECORDED: a label is routinely the host's own word (`picker/words.ts`
|
|
102
|
+
* and `reevaluate/words.ts` both require a `Record<K, string>` from the host
|
|
103
|
+
* and default none), so pinning the string would fail any host that translated
|
|
104
|
+
* it. Whether one was supplied is a fact about the markup; what it says is not.
|
|
105
|
+
*/
|
|
106
|
+
export type NameSource = 'aria-label' | 'aria-labelledby' | 'label' | 'text' | 'empty' | 'none';
|
|
107
|
+
/**
|
|
108
|
+
* How the keyboard reaches a control.
|
|
109
|
+
*
|
|
110
|
+
* NOT A BOOLEAN, and that is the whole point of the field. This package uses a
|
|
111
|
+
* roving tab stop — the projections render `tabindex: focused ? 0 : -1` — and
|
|
112
|
+
* the viewer's own focusability predicate counts `tabindex="-1"` as focusable.
|
|
113
|
+
* So a boolean "focusable" is satisfied by a control reachable only by pointer
|
|
114
|
+
* and by programmatic focus, which is precisely the defect a keyboard record
|
|
115
|
+
* exists to catch.
|
|
116
|
+
*/
|
|
117
|
+
export type TabStop = 'tab' | 'programmatic' | 'none';
|
|
118
|
+
/** One control's accessible contract. */
|
|
119
|
+
export interface ControlEntry {
|
|
120
|
+
readonly zone: string | null;
|
|
121
|
+
/** Which attribute published it — see {@link CONTROL_ATTRIBUTES}. */
|
|
122
|
+
readonly channel: string;
|
|
123
|
+
/** That attribute's value: the command, the answer, or the filter's state. */
|
|
124
|
+
readonly control: string;
|
|
125
|
+
/**
|
|
126
|
+
* What the control acts on, and which option it is.
|
|
127
|
+
*
|
|
128
|
+
* RECORDED BECAUSE THEY SEPARATE OTHERWISE IDENTICAL ROWS. Every relationship
|
|
129
|
+
* row publishes `select-edge`, and the kind list publishes `kind` several
|
|
130
|
+
* times — without these, N controls collapse to N byte-identical entries and
|
|
131
|
+
* losing one of them is invisible in the artifact's diff.
|
|
132
|
+
*/
|
|
133
|
+
readonly target: string | null;
|
|
134
|
+
readonly value: string | null;
|
|
135
|
+
readonly tag: string;
|
|
136
|
+
/** `null` where this module has no mapping, which is a fact; a guess would not be. */
|
|
137
|
+
readonly role: string | null;
|
|
138
|
+
readonly tabStop: TabStop;
|
|
139
|
+
/**
|
|
140
|
+
* Whether the control sits under an `inert` subtree.
|
|
141
|
+
*
|
|
142
|
+
* DELIBERATE UNREACHABILITY IS NOT A DEFECT, and without recording it the two
|
|
143
|
+
* are indistinguishable. The mount sets `inert` on every zone while the
|
|
144
|
+
* first-pass overlay is up — "the zones go inert under it" — so those
|
|
145
|
+
* controls are correctly out of the tab order, and a rule that simply
|
|
146
|
+
* demanded reachability would fail on the modal working as designed. Recorded
|
|
147
|
+
* rather than filtered out, because "these eight controls are deliberately
|
|
148
|
+
* unreachable right now" is exactly what a reader of a modal state wants to
|
|
149
|
+
* see.
|
|
150
|
+
*/
|
|
151
|
+
readonly inert: boolean;
|
|
152
|
+
readonly name: NameSource;
|
|
153
|
+
/** Enumerated ARIA states with their values, and IDREFs by whether they resolve. */
|
|
154
|
+
readonly aria: Readonly<Record<string, string>>;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Every attribute a control publishes itself on.
|
|
158
|
+
*
|
|
159
|
+
* THREE CHANNELS, NOT ONE, and reading only the first is how a record comes to
|
|
160
|
+
* claim a surface it never saw. `data-ig-command` is the general one, but the
|
|
161
|
+
* first pass's y/n/s answers carry `data-ig-answer` and the audit header's
|
|
162
|
+
* toggle carries `data-ig-audit-filter` — `firstpass/render.ts` and
|
|
163
|
+
* `audit/surface.ts` each say so where they declare theirs. Keyed on the
|
|
164
|
+
* command alone, this would have recorded `null` for the audit toggle and for
|
|
165
|
+
* every answer, and the rules below would have skipped exactly the controls a
|
|
166
|
+
* keyboard reader depends on most.
|
|
167
|
+
*/
|
|
168
|
+
export declare const CONTROL_ATTRIBUTES: readonly string[];
|
|
169
|
+
/**
|
|
170
|
+
* Every control under `root`, in document order, sorted so the record is stable.
|
|
171
|
+
*
|
|
172
|
+
* Pure and total. SORTED RATHER THAN LEFT IN DOCUMENT ORDER: a baseline is
|
|
173
|
+
* diffed by people, and a control moving between zones should read as one
|
|
174
|
+
* changed line rather than as a reordering of the whole file. Document order is
|
|
175
|
+
* a fact about layout, which the zone already carries.
|
|
176
|
+
*
|
|
177
|
+
* The bound it does NOT cover is stated where it can be checked: a control
|
|
178
|
+
* publishing itself on some fourth attribute is invisible here, and
|
|
179
|
+
* {@link CONTROL_ATTRIBUTES} is the list to extend.
|
|
180
|
+
*/
|
|
181
|
+
export declare function controlSurface(root: SurfaceElement): readonly ControlEntry[];
|
|
182
|
+
//# sourceMappingURL=baseline.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"baseline.d.ts","sourceRoot":"","sources":["../../src/a11y/baseline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwEG;AAEH,iDAAiD;AACjD,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,gDAAgD;AAChD,MAAM,WAAW,cAAe,SAAQ,WAAW;IACjD,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,UAAU,EAAE,SAAS,CAAC,WAAW,CAAC,CAAC;IAC5C,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAAC;IAC1C,OAAO,CAAC,SAAS,EAAE,MAAM,GAAG,cAAc,GAAG,IAAI,CAAC;IAClD,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,QAAQ,CAAC,cAAc,CAAC,CAAC;CAC/D;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,MAAM,UAAU,GAAG,YAAY,GAAG,iBAAiB,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC;AAEhG;;;;;;;;;GASG;AACH,MAAM,MAAM,OAAO,GAAG,KAAK,GAAG,cAAc,GAAG,MAAM,CAAC;AAEtD,yCAAyC;AACzC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,qEAAqE;IACrE,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,8EAA8E;IAC9E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB;;;;;;;OAOG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAAC;IAC9B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,sFAAsF;IACtF,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;;;;;;;;;OAWG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;IAC1B,oFAAoF;IACpF,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;CACjD;AAED;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,kBAAkB,EAAE,SAAS,MAAM,EAI9C,CAAC;AA6QH;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,cAAc,GAAG,SAAS,YAAY,EAAE,CAqD5E"}
|