@heartlandone/vega 2.95.0 → 2.96.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/dist/cjs/{app-globals-76815382.js → app-globals-38afea36.js} +7 -7
- package/dist/cjs/{code-block-493c7ce7.js → code-block-4126dc3b.js} +1501 -55
- package/dist/cjs/{component-value-history-controller-slimmer.abstract-ab21fcbf.js → component-value-history-controller-slimmer.abstract-38ba956e.js} +2 -2
- package/dist/cjs/{content-state-fc34a814.js → content-state-50e8a03c.js} +142 -580
- package/dist/cjs/{element-appender-slimmer-1d6323c2.js → element-appender-slimmer-7773fdab.js} +115 -0
- package/dist/cjs/{image-annotation-action-7c4cd316.js → image-annotation-action-89363de8.js} +2 -2
- package/dist/cjs/{image-extension-30a7c10f.js → image-extension-c881c454.js} +2 -2
- package/dist/cjs/index.cjs.js +10 -10
- package/dist/cjs/{link-extension-92a63cb7.js → link-extension-b7f99a52.js} +2 -2
- package/dist/cjs/loader.cjs.js +7 -7
- package/dist/cjs/{public-rules-9e1ea1d9.js → public-rules-72894208.js} +3 -3
- package/dist/cjs/{range-30219330.js → range-95d41dea.js} +1 -1
- package/dist/cjs/{rich-text-editor-required-rule-23bf7485.js → rich-text-editor-required-rule-4d6d2493.js} +1 -1
- package/dist/cjs/{split-cell-operation-43752212.js → split-cell-operation-70be116d.js} +2 -2
- package/dist/cjs/{token-extension-d123d9d0.js → token-extension-eb828948.js} +35 -11
- package/dist/cjs/vega-code-block.cjs.entry.js +3 -3
- package/dist/cjs/vega-date-picker_2.cjs.entry.js +1 -1
- package/dist/cjs/vega-dropdown_5.cjs.entry.js +27 -4
- package/dist/cjs/vega-env-manager-23b8b23c.js +2 -2
- package/dist/cjs/vega-popover_2.cjs.entry.js +1 -1
- package/dist/cjs/vega-rich-text-content.cjs.entry.js +58 -9
- package/dist/cjs/vega-rich-text-editor_4.cjs.entry.js +608 -90
- package/dist/cjs/vega-rich-text-table-properties_3.cjs.entry.js +3 -3
- package/dist/cjs/vega-section-title.cjs.entry.js +896 -14
- package/dist/cjs/vega-time-picker_2.cjs.entry.js +1 -1
- package/dist/cjs/vega-tooltip_2.cjs.entry.js +1 -1
- package/dist/cjs/vega.cjs.js +7 -7
- package/dist/collection/components/vega-combo-box/vega-combo-box.js +2 -2
- package/dist/collection/components/vega-dropdown/vega-dropdown.js +30 -7
- package/dist/collection/components/vega-input-select/vega-input-select.js +2 -2
- package/dist/collection/components/vega-rich-text-content/vega-rich-text-content.css +4 -0
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/block-delete-node-content-strategy.js +29 -0
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/block-delete-text-or-decorator-node-strategy.js +10 -1
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/block-transform-to-list-strategy.js +8 -3
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-annotation-strategy.abstract.js +60 -0
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-item-replace-nest-list-strategy.js +36 -4
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-items-strategy.abstract.js +27 -0
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-nest-items-strategy.js +74 -0
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-remove-list-item-strategy.js +81 -1
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-transform-selection.js +30 -0
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-transform-to-list-strategy.js +160 -9
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-transform-to-paragraph-strategy.js +376 -10
- package/dist/collection/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-unnest-items-strategy.js +104 -0
- package/dist/collection/components/vega-rich-text-editor/dto/actions/modify-content-action.abstract.js +2 -0
- package/dist/collection/components/vega-rich-text-editor/dto/actions/nest-list-items-action.js +12 -0
- package/dist/collection/components/vega-rich-text-editor/dto/actions/unnest-list-items-action.js +12 -0
- package/dist/collection/components/vega-rich-text-editor/dto/annotations/list-annotation.js +12 -0
- package/dist/collection/components/vega-rich-text-editor/dto/blocks/list-block.js +10 -1
- package/dist/collection/components/vega-rich-text-editor/dto/blocks/list-item-block.js +11 -3
- package/dist/collection/components/vega-rich-text-editor/dto/content-state.js +7 -1
- package/dist/collection/components/vega-rich-text-editor/dto/filter-styles-strategies/list-block/list-block-filter-styles-strategy.js +8 -4
- package/dist/collection/components/vega-rich-text-editor/dto/renderers/blocks/list-block-renderer.js +34 -1
- package/dist/collection/components/vega-rich-text-editor/dto/renderers/blocks/list-item-block-renderer.js +20 -3
- package/dist/collection/components/vega-rich-text-editor/extensions/table/annotation-handler/table-annotation-handler-base.js +25 -1
- package/dist/collection/components/vega-rich-text-editor/slimmers/controllers/helper/element-to-dto-strategy/li-to-rte-list-item-block-strategy.js +2 -0
- package/dist/collection/components/vega-rich-text-editor/slimmers/controllers/helper/input-event-handler/abstract-delete-content-handler.js +29 -0
- package/dist/collection/components/vega-rich-text-editor/slimmers/renderers/tools/bullets-numbers/bullets-numbers-toolbar-button-slimmer.js +30 -1
- package/dist/collection/components/vega-rich-text-editor/slimmers/renderers/tools/bullets-numbers/helper/abstract-list-keyboard-handler.js +204 -0
- package/dist/collection/components/vega-rich-text-editor/slimmers/renderers/tools/bullets-numbers/helper/list-shift-tab-outdent-handler.js +42 -0
- package/dist/collection/components/vega-rich-text-editor/slimmers/renderers/tools/bullets-numbers/helper/list-tab-indent-handler.js +34 -0
- package/dist/collection/components/vega-rich-text-editor/slimmers/renderers/tools/bullets-numbers/list-keyboard-slimmer.js +180 -0
- package/dist/collection/components/vega-section-title/slimmers/a11y-tree/build-section-title-a11y-tree.js +54 -0
- package/dist/collection/components/vega-section-title/slimmers/renderers/vega-section-title-renderer.js +4 -4
- package/dist/collection/components/vega-section-title/vega-section-title.js +18 -2
- package/dist/collection/helpers/a11y-tree/a11y-node-dto.js +210 -0
- package/dist/collection/helpers/a11y-tree/a11y-tree-applier.js +189 -0
- package/dist/collection/helpers/a11y-tree/a11y-tree.types.js +48 -0
- package/dist/collection/helpers/a11y-tree/strategies/baseline-strategy.js +78 -0
- package/dist/collection/helpers/a11y-tree/strategies/firefox-nvda-strategy.js +56 -0
- package/dist/collection/helpers/a11y-tree/strategies/webkit-voiceover-strategy.js +140 -0
- package/dist/collection/helpers/a11y-tree/strategy-resolver.js +172 -0
- package/dist/collection/helpers/slimmers/a11y-tree-slimmer.js +54 -0
- package/dist/collection/helpers/slimmers/element-appender/element-appender-slimmer.js +29 -0
- package/dist/collection/helpers/slimmers/element-appender/visibility-modifiers/clip-on-scroll-modifier.js +70 -0
- package/dist/collection/helpers/slimmers/element-appender/visibility-modifiers/visibility-modifier.interface.js +1 -0
- package/dist/collection/helpers/ui/element-appender.js +16 -0
- package/dist/esm/{app-globals-eafcc7a1.js → app-globals-d7b651c1.js} +7 -7
- package/dist/esm/{code-block-e12588fe.js → code-block-0a88b481.js} +1495 -51
- package/dist/esm/{component-value-history-controller-slimmer.abstract-4e8eb313.js → component-value-history-controller-slimmer.abstract-6846edfa.js} +2 -2
- package/dist/esm/{content-state-8032467a.js → content-state-79ca99e5.js} +105 -541
- package/dist/esm/{element-appender-slimmer-7fcb944b.js → element-appender-slimmer-73ad10ae.js} +115 -0
- package/dist/esm/{image-annotation-action-08a20d5e.js → image-annotation-action-bb299ef1.js} +2 -2
- package/dist/esm/{image-extension-b6c7e4a7.js → image-extension-48a96f98.js} +2 -2
- package/dist/esm/index.js +8 -8
- package/dist/esm/{link-extension-97fcb8fb.js → link-extension-09534514.js} +2 -2
- package/dist/esm/loader.js +7 -7
- package/dist/esm/{public-rules-c06f0d2a.js → public-rules-5eb03ef0.js} +3 -3
- package/dist/esm/{range-e999837f.js → range-d4deb7df.js} +1 -1
- package/dist/esm/{rich-text-editor-required-rule-71f8ee98.js → rich-text-editor-required-rule-5bbbce23.js} +1 -1
- package/dist/esm/{split-cell-operation-359f8082.js → split-cell-operation-7f3058ad.js} +2 -2
- package/dist/esm/{token-extension-f5bf9c21.js → token-extension-40c32bcc.js} +30 -6
- package/dist/esm/vega-code-block.entry.js +3 -3
- package/dist/esm/vega-date-picker_2.entry.js +1 -1
- package/dist/esm/vega-dropdown_5.entry.js +27 -4
- package/dist/esm/vega-env-manager-8f8dc473.js +2 -2
- package/dist/esm/vega-popover_2.entry.js +1 -1
- package/dist/esm/vega-rich-text-content.entry.js +58 -9
- package/dist/esm/vega-rich-text-editor_4.entry.js +604 -86
- package/dist/esm/vega-rich-text-table-properties_3.entry.js +3 -3
- package/dist/esm/vega-section-title.entry.js +897 -15
- package/dist/esm/vega-time-picker_2.entry.js +1 -1
- package/dist/esm/vega-tooltip_2.entry.js +1 -1
- package/dist/esm/vega.js +7 -7
- package/dist/sri/vega-sri-manifest.json +94 -94
- package/dist/types/components/vega-combo-box/vega-combo-box.d.ts +6 -6
- package/dist/types/components/vega-dropdown/types.d.ts +2 -2
- package/dist/types/components/vega-dropdown/vega-dropdown.d.ts +32 -9
- package/dist/types/components/vega-input-select/vega-input-select.d.ts +6 -6
- package/dist/types/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-annotation-strategy.abstract.d.ts +36 -0
- package/dist/types/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-item-replace-nest-list-strategy.d.ts +14 -2
- package/dist/types/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-items-strategy.abstract.d.ts +22 -0
- package/dist/types/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-nest-items-strategy.d.ts +21 -0
- package/dist/types/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-remove-list-item-strategy.d.ts +14 -0
- package/dist/types/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-transform-selection.d.ts +15 -0
- package/dist/types/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-transform-to-list-strategy.d.ts +30 -3
- package/dist/types/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-transform-to-paragraph-strategy.d.ts +99 -3
- package/dist/types/components/vega-rich-text-editor/dto/action-handle-strategies/modify-content-strategies/list-unnest-items-strategy.d.ts +22 -0
- package/dist/types/components/vega-rich-text-editor/dto/actions/modify-content-action.abstract.d.ts +2 -0
- package/dist/types/components/vega-rich-text-editor/dto/actions/nest-list-items-action.d.ts +11 -0
- package/dist/types/components/vega-rich-text-editor/dto/actions/unnest-list-items-action.d.ts +11 -0
- package/dist/types/components/vega-rich-text-editor/dto/annotations/list-annotation.d.ts +8 -0
- package/dist/types/components/vega-rich-text-editor/dto/blocks/list-block.d.ts +6 -1
- package/dist/types/components/vega-rich-text-editor/dto/blocks/list-item-block.d.ts +5 -1
- package/dist/types/components/vega-rich-text-editor/dto/content-state.d.ts +2 -2
- package/dist/types/components/vega-rich-text-editor/dto/renderers/blocks/list-block-renderer.d.ts +2 -0
- package/dist/types/components/vega-rich-text-editor/dto/renderers/blocks/list-item-block-renderer.d.ts +8 -0
- package/dist/types/components/vega-rich-text-editor/extensions/table/annotation-handler/table-annotation-handler-base.d.ts +18 -0
- package/dist/types/components/vega-rich-text-editor/slimmers/renderers/tools/bullets-numbers/bullets-numbers-toolbar-button-slimmer.d.ts +8 -0
- package/dist/types/components/vega-rich-text-editor/slimmers/renderers/tools/bullets-numbers/helper/abstract-list-keyboard-handler.d.ts +99 -0
- package/dist/types/components/vega-rich-text-editor/slimmers/renderers/tools/bullets-numbers/helper/list-shift-tab-outdent-handler.d.ts +15 -0
- package/dist/types/components/vega-rich-text-editor/slimmers/renderers/tools/bullets-numbers/helper/list-tab-indent-handler.d.ts +15 -0
- package/dist/types/components/vega-rich-text-editor/slimmers/renderers/tools/bullets-numbers/list-keyboard-slimmer.d.ts +73 -0
- package/dist/types/components/vega-section-title/slimmers/a11y-tree/build-section-title-a11y-tree.d.ts +41 -0
- package/dist/types/components/vega-section-title/vega-section-title.d.ts +9 -0
- package/dist/types/components.d.ts +10 -10
- package/dist/types/helpers/a11y-tree/a11y-node-dto.d.ts +173 -0
- package/dist/types/helpers/a11y-tree/a11y-tree-applier.d.ts +28 -0
- package/dist/types/helpers/a11y-tree/a11y-tree.types.d.ts +137 -0
- package/dist/types/helpers/a11y-tree/strategies/baseline-strategy.d.ts +18 -0
- package/dist/types/helpers/a11y-tree/strategies/firefox-nvda-strategy.d.ts +16 -0
- package/dist/types/helpers/a11y-tree/strategies/webkit-voiceover-strategy.d.ts +23 -0
- package/dist/types/helpers/a11y-tree/strategy-resolver.d.ts +59 -0
- package/dist/types/helpers/slimmers/a11y-tree-slimmer.d.ts +49 -0
- package/dist/types/helpers/slimmers/element-appender/element-appender-slimmer.d.ts +9 -0
- package/dist/types/helpers/slimmers/element-appender/visibility-modifiers/clip-on-scroll-modifier.d.ts +37 -0
- package/dist/types/helpers/slimmers/element-appender/visibility-modifiers/visibility-modifier.interface.d.ts +56 -0
- package/dist/types/helpers/ui/element-appender.d.ts +7 -0
- package/dist/vega/index.esm.js +1 -1
- package/dist/vega/{p-9c39f90b.entry.js → p-1dfbc542.entry.js} +1 -1
- package/dist/vega/{p-e235020b.entry.js → p-22cc174e.entry.js} +1 -1
- package/dist/vega/{p-b187b82d.entry.js → p-2df5d59b.entry.js} +1 -1
- package/dist/vega/{p-f7e459cd.js → p-312d8205.js} +1 -1
- package/dist/vega/{p-a7209700.js → p-3345d903.js} +1 -1
- package/dist/vega/{p-02355de8.js → p-3c23ec90.js} +1 -1
- package/dist/vega/p-55e0014a.js +3 -0
- package/dist/vega/p-5f377954.js +1 -1
- package/dist/vega/p-65fee02f.entry.js +1 -0
- package/dist/vega/p-6a51d720.js +1 -0
- package/dist/vega/{p-fe24b459.js → p-6c7b6d45.js} +1 -1
- package/dist/vega/{p-579cdeae.js → p-84b7a775.js} +1 -1
- package/dist/vega/{p-df828d90.entry.js → p-96da7d30.entry.js} +1 -1
- package/dist/vega/{p-88be9ec5.js → p-9d3f69cb.js} +1 -1
- package/dist/vega/{p-8a001caa.js → p-9f11b579.js} +1 -1
- package/dist/vega/{p-ba2b9663.entry.js → p-acaff62d.entry.js} +1 -1
- package/dist/vega/p-b63319e9.entry.js +1 -0
- package/dist/vega/{p-c079c935.entry.js → p-b660f38c.entry.js} +1 -1
- package/dist/vega/p-b8ffa42f.entry.js +1 -0
- package/dist/vega/{p-4fe40b76.js → p-c4564904.js} +1 -1
- package/dist/vega/p-d9158512.js +1 -0
- package/dist/vega/p-e62d41ee.js +1 -0
- package/dist/vega/{p-9f7154d2.js → p-ec5dee32.js} +1 -1
- package/dist/vega/{p-05d04c07.entry.js → p-f29d9707.entry.js} +1 -1
- package/dist/vega/vega.esm.js +1 -1
- package/package.json +3 -2
- package/dist/vega/p-0be4df0a.js +0 -1
- package/dist/vega/p-38c9d782.js +0 -1
- package/dist/vega/p-513bc0d9.js +0 -1
- package/dist/vega/p-8d4b85e7.entry.js +0 -1
- package/dist/vega/p-c5d3b008.entry.js +0 -1
- package/dist/vega/p-d25ca0c3.entry.js +0 -1
- package/dist/vega/p-f4da615a.js +0 -3
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Semantic DTO layer for the A11y Tree.
|
|
3
|
+
*
|
|
4
|
+
* Authoring an A11y Tree with raw `A11yNode` literals asks the developer to
|
|
5
|
+
* think in ARIA terms (`role: 'group'`, `decorative: true`, etc.). The DTO
|
|
6
|
+
* classes here provide a higher-level vocabulary that names the *content's
|
|
7
|
+
* meaning* — "this is a container", "this is a heading", "this is a
|
|
8
|
+
* decorative icon" — and lets the strategy layer translate intent into ARIA.
|
|
9
|
+
*
|
|
10
|
+
* ## Design (Visitor / Strategy + typed semantic DTOs)
|
|
11
|
+
*
|
|
12
|
+
* - DTOs are *type-holders for semantic intent*. They do NOT contain ARIA
|
|
13
|
+
* generation logic themselves.
|
|
14
|
+
* - Each DTO compiles to an `A11yNode` whose `kind` field carries the
|
|
15
|
+
* semantic discriminator. Strategies can then dispatch on `kind`
|
|
16
|
+
* (preferred) or fall back to `role` (for plain `A11yNode` authors).
|
|
17
|
+
* - This keeps strategies pure and independently testable, while letting
|
|
18
|
+
* authors write:
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* new A11yContainerNode('st-root', { label, description })
|
|
22
|
+
* .childrenWith(
|
|
23
|
+
* new A11yHeadingNode('st-heading', { level: 2, label }),
|
|
24
|
+
* new A11yIconNode('st-icon'),
|
|
25
|
+
* );
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* instead of hand-shaping `{ role: 'group', label, description, … }`.
|
|
29
|
+
*
|
|
30
|
+
* ## Migration path
|
|
31
|
+
*
|
|
32
|
+
* Phase 1 (this file): DTOs are an additive layer on top of `A11yNode`.
|
|
33
|
+
* Existing strategies continue to work unchanged because each DTO emits
|
|
34
|
+
* a fully-populated `A11yNode` with `role` set correctly.
|
|
35
|
+
*
|
|
36
|
+
* Phase 2 (follow-up): strategies grow `compileNode(node)` methods that
|
|
37
|
+
* dispatch on `node.kind` for cases where semantic intent is richer than
|
|
38
|
+
* `role` alone can express.
|
|
39
|
+
*/
|
|
40
|
+
/**
|
|
41
|
+
* Base class for all semantic DTO nodes. Provides the `childrenWith()`
|
|
42
|
+
* composition helper and the abstract `toA11yNode()` contract.
|
|
43
|
+
*/
|
|
44
|
+
export class A11yNodeDto {
|
|
45
|
+
constructor() {
|
|
46
|
+
this.childDtos = [];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Declare the semantic children of this node in reading order.
|
|
50
|
+
*
|
|
51
|
+
* @param {...A11yNodeDto} children - Semantic child DTOs in reading order.
|
|
52
|
+
* @returns {this} `this`, for fluent chaining.
|
|
53
|
+
*/
|
|
54
|
+
childrenWith(...children) {
|
|
55
|
+
this.childDtos = children;
|
|
56
|
+
return this;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Compile the declared child DTOs into their `A11yNode` representations.
|
|
60
|
+
*
|
|
61
|
+
* @returns {A11yNode[] | undefined} The compiled children, or `undefined` when none.
|
|
62
|
+
*/
|
|
63
|
+
compileChildren() {
|
|
64
|
+
if (this.childDtos.length === 0)
|
|
65
|
+
return undefined;
|
|
66
|
+
return this.childDtos.map((c) => c.toA11yNode());
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* A generic container that groups related content. Maps to `role="group"`
|
|
71
|
+
* by default; strategies may upgrade to `role="region"` when a description
|
|
72
|
+
* is present and the platform benefits (e.g. Firefox+NVDA landmark
|
|
73
|
+
* navigation — see `firefox-nvda-strategy.ts`).
|
|
74
|
+
*/
|
|
75
|
+
export class A11yContainerNode extends A11yNodeDto {
|
|
76
|
+
constructor(id, opts = {}) {
|
|
77
|
+
super();
|
|
78
|
+
this.id = id;
|
|
79
|
+
this.opts = opts;
|
|
80
|
+
this.kind = 'container';
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Compile this DTO into its `A11yNode` representation.
|
|
84
|
+
*
|
|
85
|
+
* @returns {A11yNode} The compiled semantic node.
|
|
86
|
+
*/
|
|
87
|
+
toA11yNode() {
|
|
88
|
+
return {
|
|
89
|
+
id: this.id,
|
|
90
|
+
kind: this.kind,
|
|
91
|
+
role: 'group',
|
|
92
|
+
label: this.opts.label,
|
|
93
|
+
description: this.opts.description,
|
|
94
|
+
children: this.compileChildren(),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* A navigable / discoverable section (landmark). Always renders as
|
|
100
|
+
* `role="region"` so it appears in AT landmark navigation.
|
|
101
|
+
*/
|
|
102
|
+
export class A11yLandmarkNode extends A11yNodeDto {
|
|
103
|
+
constructor(id, opts = {}) {
|
|
104
|
+
super();
|
|
105
|
+
this.id = id;
|
|
106
|
+
this.opts = opts;
|
|
107
|
+
this.kind = 'landmark';
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Compile this DTO into its `A11yNode` representation.
|
|
111
|
+
*
|
|
112
|
+
* @returns {A11yNode} The compiled semantic node.
|
|
113
|
+
*/
|
|
114
|
+
toA11yNode() {
|
|
115
|
+
return {
|
|
116
|
+
id: this.id,
|
|
117
|
+
kind: this.kind,
|
|
118
|
+
role: 'region',
|
|
119
|
+
label: this.opts.label,
|
|
120
|
+
description: this.opts.description,
|
|
121
|
+
children: this.compileChildren(),
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* A heading at the given level (1–6, defaults to 2).
|
|
127
|
+
*
|
|
128
|
+
* Use this instead of a raw `{ role: 'heading', level: 2 }` literal to
|
|
129
|
+
* benefit from the default-level guard and the `kind` discriminator.
|
|
130
|
+
*/
|
|
131
|
+
export class A11yHeadingNode extends A11yNodeDto {
|
|
132
|
+
constructor(id, opts = {}) {
|
|
133
|
+
super();
|
|
134
|
+
this.id = id;
|
|
135
|
+
this.opts = opts;
|
|
136
|
+
this.kind = 'heading';
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Compile this DTO into its `A11yNode` representation.
|
|
140
|
+
*
|
|
141
|
+
* @returns {A11yNode} The compiled semantic node.
|
|
142
|
+
*/
|
|
143
|
+
toA11yNode() {
|
|
144
|
+
var _a;
|
|
145
|
+
return {
|
|
146
|
+
id: this.id,
|
|
147
|
+
kind: this.kind,
|
|
148
|
+
role: 'heading',
|
|
149
|
+
level: (_a = this.opts.level) !== null && _a !== void 0 ? _a : 2,
|
|
150
|
+
label: this.opts.label,
|
|
151
|
+
children: this.compileChildren(),
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* A purely decorative icon. `decorative` is always `true` so authors never
|
|
157
|
+
* have to remember the flag — that is the whole point of this DTO class.
|
|
158
|
+
*
|
|
159
|
+
* Produces `aria-hidden="true"` via the baseline strategy; webkit-voiceover
|
|
160
|
+
* additionally sets `role="none"` as a belt-and-suspenders guard.
|
|
161
|
+
*/
|
|
162
|
+
export class A11yIconNode extends A11yNodeDto {
|
|
163
|
+
constructor(id) {
|
|
164
|
+
super();
|
|
165
|
+
this.id = id;
|
|
166
|
+
this.kind = 'icon';
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Compile this DTO into its `A11yNode` representation.
|
|
170
|
+
*
|
|
171
|
+
* @returns {A11yNode} The compiled semantic node.
|
|
172
|
+
*/
|
|
173
|
+
toA11yNode() {
|
|
174
|
+
return {
|
|
175
|
+
id: this.id,
|
|
176
|
+
kind: this.kind,
|
|
177
|
+
role: 'img',
|
|
178
|
+
decorative: true,
|
|
179
|
+
children: this.compileChildren(),
|
|
180
|
+
};
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* An informative image that requires an accessible label (alt text).
|
|
185
|
+
*
|
|
186
|
+
* Produces `role="img"` + `aria-label` via the baseline strategy.
|
|
187
|
+
* Contrast with `A11yIconNode` which is always decorative.
|
|
188
|
+
*/
|
|
189
|
+
export class A11yImageNode extends A11yNodeDto {
|
|
190
|
+
constructor(id, opts) {
|
|
191
|
+
super();
|
|
192
|
+
this.id = id;
|
|
193
|
+
this.opts = opts;
|
|
194
|
+
this.kind = 'image';
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Compile this DTO into its `A11yNode` representation.
|
|
198
|
+
*
|
|
199
|
+
* @returns {A11yNode} The compiled semantic node.
|
|
200
|
+
*/
|
|
201
|
+
toA11yNode() {
|
|
202
|
+
return {
|
|
203
|
+
id: this.id,
|
|
204
|
+
kind: this.kind,
|
|
205
|
+
role: 'img',
|
|
206
|
+
label: this.opts.label,
|
|
207
|
+
children: this.compileChildren(),
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import { resolveStrategy } from './strategy-resolver';
|
|
2
|
+
import { tryGetDocument } from '../../utils/try-get-document';
|
|
3
|
+
/**
|
|
4
|
+
* Applies an A11y Tree to a shadow root by:
|
|
5
|
+
*
|
|
6
|
+
* 1. Resolving the `A11yTreeStrategy` that best matches the current
|
|
7
|
+
* browser engine / AT environment.
|
|
8
|
+
* 2. Compiling the semantic tree into a flat list of `AriaPatch`
|
|
9
|
+
* instructions.
|
|
10
|
+
* 3. Clearing any previously managed ARIA attributes (`data-a11y-managed`).
|
|
11
|
+
* 4. Applying each patch to the `data-a11y-id` target node in the shadow.
|
|
12
|
+
* 5. Materialising one visually-hidden description `<span>` per patch that
|
|
13
|
+
* references `aria-describedby` and whose node carries description text.
|
|
14
|
+
* 6. Stamping `data-a11y-strategy-applied` on the host for observability.
|
|
15
|
+
*
|
|
16
|
+
* This is the generic, component-agnostic projection step. Components
|
|
17
|
+
* interact with it indirectly through `A11yTreeSlimmer`.
|
|
18
|
+
*
|
|
19
|
+
* @param {A11yNode} root - The root `A11yNode` of the semantic tree.
|
|
20
|
+
* @param {HTMLElement} host - The component's host element. Must have a `shadowRoot`.
|
|
21
|
+
* If `shadowRoot` is null (e.g. light-DOM component or early call before first
|
|
22
|
+
* render), the function returns immediately without throwing.
|
|
23
|
+
* @example
|
|
24
|
+
* ```ts
|
|
25
|
+
* // Called once per render from A11yTreeSlimmer.projectA11yTree():
|
|
26
|
+
* applyA11yTree(buildSectionTitleA11yTree({ titleText, description }), this.host);
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export function applyA11yTree(root, host) {
|
|
30
|
+
const shadow = host.shadowRoot;
|
|
31
|
+
if (!shadow)
|
|
32
|
+
return;
|
|
33
|
+
const strategy = resolveStrategy(host);
|
|
34
|
+
const patches = strategy.compile(root);
|
|
35
|
+
clearPreviousPatches(shadow);
|
|
36
|
+
for (const patch of patches) {
|
|
37
|
+
applyPatch(shadow, patch);
|
|
38
|
+
}
|
|
39
|
+
// Materialise a visually-hidden description node when any patch
|
|
40
|
+
// references aria-describedby. This node is what the AT reads via
|
|
41
|
+
// the describedby relationship.
|
|
42
|
+
maybeRenderDescriptionNode(root, shadow, patches);
|
|
43
|
+
host.setAttribute('data-a11y-strategy-applied', strategy.name);
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Remove all ARIA attributes that were set by a previous `applyA11yTree`
|
|
47
|
+
* call. The `data-a11y-managed` attribute on each node acts as a manifest
|
|
48
|
+
* of which attributes were set so we can clean up exactly those — and only
|
|
49
|
+
* those — without disturbing hand-authored ARIA.
|
|
50
|
+
*
|
|
51
|
+
* @param {ShadowRoot} shadow - The shadow root to clean previously managed ARIA from.
|
|
52
|
+
*/
|
|
53
|
+
function clearPreviousPatches(shadow) {
|
|
54
|
+
shadow.querySelectorAll('[data-a11y-managed]').forEach((el) => {
|
|
55
|
+
var _a;
|
|
56
|
+
/* istanbul ignore next -- the [data-a11y-managed] selector guarantees the attribute is present, so the `?? ''` fallback is unreachable defensive code */
|
|
57
|
+
const attrs = ((_a = el.getAttribute('data-a11y-managed')) !== null && _a !== void 0 ? _a : '').split(' ').filter(Boolean);
|
|
58
|
+
attrs.forEach((a) => el.removeAttribute(a));
|
|
59
|
+
el.removeAttribute('data-a11y-managed');
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Apply a single `AriaPatch` to the matching `data-a11y-id` element.
|
|
64
|
+
* Records every attribute written in `data-a11y-managed` so
|
|
65
|
+
* `clearPreviousPatches` can undo it on the next render.
|
|
66
|
+
*
|
|
67
|
+
* When `patch.value === null` the attribute is removed instead of set.
|
|
68
|
+
*
|
|
69
|
+
* @param {ShadowRoot} shadow - The shadow root containing the target element.
|
|
70
|
+
* @param {AriaPatch} patch - The ARIA patch to apply.
|
|
71
|
+
*/
|
|
72
|
+
function applyPatch(shadow, patch) {
|
|
73
|
+
var _a;
|
|
74
|
+
const target = shadow.querySelector(`[data-a11y-id="${patch.targetId}"]`);
|
|
75
|
+
if (!target)
|
|
76
|
+
return;
|
|
77
|
+
if (patch.value === null) {
|
|
78
|
+
target.removeAttribute(patch.attribute);
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
target.setAttribute(patch.attribute, patch.value);
|
|
82
|
+
}
|
|
83
|
+
// Track which attributes we wrote so we can clean them up later.
|
|
84
|
+
const prev = (_a = target.getAttribute('data-a11y-managed')) !== null && _a !== void 0 ? _a : '';
|
|
85
|
+
if (!prev.split(' ').includes(patch.attribute)) {
|
|
86
|
+
target.setAttribute('data-a11y-managed', (prev + ' ' + patch.attribute).trim());
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Materialise a visually-hidden `<span>` description node for every strategy
|
|
91
|
+
* patch that references `aria-describedby`. Each `<span>` carries the
|
|
92
|
+
* description text so assistive technology can read it via the describedby
|
|
93
|
+
* relationship.
|
|
94
|
+
*
|
|
95
|
+
* Spans are created once and reused on subsequent renders; their text content
|
|
96
|
+
* is updated in place. Spans whose `aria-describedby` reference is dropped on
|
|
97
|
+
* a later render are pruned by `pruneStaleDescriptionNodes`.
|
|
98
|
+
*
|
|
99
|
+
* Visually-hidden styles follow the `.sr-only` pattern (not `display:none`
|
|
100
|
+
* which would suppress AT reading).
|
|
101
|
+
*
|
|
102
|
+
* @param {A11yNode} root - The root of the semantic tree (source of description text).
|
|
103
|
+
* @param {ShadowRoot} shadow - The shadow root to append/update description nodes in.
|
|
104
|
+
* @param {AriaPatch[]} patches - The compiled patch list to scan for `aria-describedby`.
|
|
105
|
+
*/
|
|
106
|
+
function maybeRenderDescriptionNode(root, shadow, patches) {
|
|
107
|
+
// Collect every aria-describedby patch that has a live string target id —
|
|
108
|
+
// one hidden span is required per described node.
|
|
109
|
+
const describedByPatches = patches.filter((p) => p.attribute === 'aria-describedby' && typeof p.value === 'string');
|
|
110
|
+
const keepIds = new Set(describedByPatches.map((p) => p.value));
|
|
111
|
+
// Remove any previously-materialised description spans that are no longer
|
|
112
|
+
// referenced by a live aria-describedby patch — e.g. the `description` prop
|
|
113
|
+
// was cleared, or the description moved to a different node. Without this,
|
|
114
|
+
// orphaned visually-hidden spans linger in the shadow DOM and are still
|
|
115
|
+
// announced by AT during linear (non-describedby) traversal.
|
|
116
|
+
pruneStaleDescriptionNodes(shadow, keepIds);
|
|
117
|
+
for (const patch of describedByPatches) {
|
|
118
|
+
const node = findById(root, patch.targetId);
|
|
119
|
+
/* istanbul ignore next -- describedby patches are only emitted for nodes that carry a description, so this guard is unreachable defensive code */
|
|
120
|
+
if (!(node === null || node === void 0 ? void 0 : node.description))
|
|
121
|
+
continue;
|
|
122
|
+
ensureDescriptionSpan(shadow, patch.value, node.description);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Create (or update in place) a visually-hidden `<span>` carrying description
|
|
127
|
+
* text for a single `aria-describedby` reference.
|
|
128
|
+
*
|
|
129
|
+
* @param {ShadowRoot} shadow - The shadow root to append/update the span in.
|
|
130
|
+
* @param {string} id - The span id, matching the `aria-describedby` value.
|
|
131
|
+
* @param {string} text - The description text to expose to AT.
|
|
132
|
+
*/
|
|
133
|
+
function ensureDescriptionSpan(shadow, id, text) {
|
|
134
|
+
let descEl = shadow.getElementById(id);
|
|
135
|
+
if (!descEl) {
|
|
136
|
+
const doc = tryGetDocument();
|
|
137
|
+
/* istanbul ignore next -- document is always present in the browser render path */
|
|
138
|
+
if (!doc)
|
|
139
|
+
return;
|
|
140
|
+
descEl = doc.createElement('span');
|
|
141
|
+
descEl.id = id;
|
|
142
|
+
// Stamp the span so pruneStaleDescriptionNodes can find and clean it up
|
|
143
|
+
// on a later render when it is no longer referenced.
|
|
144
|
+
descEl.setAttribute('data-a11y-desc', '');
|
|
145
|
+
descEl.style.position = 'absolute';
|
|
146
|
+
descEl.style.width = '1px';
|
|
147
|
+
descEl.style.height = '1px';
|
|
148
|
+
descEl.style.overflow = 'hidden';
|
|
149
|
+
// `clip` is kept for legacy engines; `clip-path` + `white-space: nowrap`
|
|
150
|
+
// cover modern engines and prevent a 1px phantom scrollbar.
|
|
151
|
+
descEl.style.clip = 'rect(0 0 0 0)';
|
|
152
|
+
descEl.style.clipPath = 'inset(50%)';
|
|
153
|
+
descEl.style.whiteSpace = 'nowrap';
|
|
154
|
+
shadow.appendChild(descEl);
|
|
155
|
+
}
|
|
156
|
+
descEl.textContent = text;
|
|
157
|
+
}
|
|
158
|
+
/**
|
|
159
|
+
* Remove visually-hidden description `<span>`s (stamped `data-a11y-desc`)
|
|
160
|
+
* that are no longer referenced by a live `aria-describedby` patch.
|
|
161
|
+
*
|
|
162
|
+
* @param {ShadowRoot} shadow - The shadow root to prune stale description nodes from.
|
|
163
|
+
* @param {Set<string>} keepIds - Ids of spans still referenced this render; all
|
|
164
|
+
* other stamped spans are removed.
|
|
165
|
+
*/
|
|
166
|
+
function pruneStaleDescriptionNodes(shadow, keepIds) {
|
|
167
|
+
shadow.querySelectorAll('[data-a11y-desc]').forEach((el) => {
|
|
168
|
+
if (!keepIds.has(el.id))
|
|
169
|
+
el.remove();
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Depth-first search for a node by `id`.
|
|
174
|
+
*
|
|
175
|
+
* @param {A11yNode} node - Node to search from.
|
|
176
|
+
* @param {string} id - The `id` to find.
|
|
177
|
+
* @returns {A11yNode | undefined} The matching node, or `undefined` when not found.
|
|
178
|
+
*/
|
|
179
|
+
function findById(node, id) {
|
|
180
|
+
var _a;
|
|
181
|
+
if (node.id === id)
|
|
182
|
+
return node;
|
|
183
|
+
for (const c of (_a = node.children) !== null && _a !== void 0 ? _a : []) {
|
|
184
|
+
const hit = findById(c, id);
|
|
185
|
+
if (hit)
|
|
186
|
+
return hit;
|
|
187
|
+
}
|
|
188
|
+
return undefined;
|
|
189
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A11y Tree — generic type definitions.
|
|
3
|
+
*
|
|
4
|
+
* The A11y Tree is a browser-agnostic description of the *semantic* a11y
|
|
5
|
+
* structure of a component. Each node captures the intent (role, label,
|
|
6
|
+
* description, level, etc.) and a stable `id` that the strategy layer
|
|
7
|
+
* can use to project ARIA attributes onto the real DOM.
|
|
8
|
+
*
|
|
9
|
+
* ## Architecture overview
|
|
10
|
+
*
|
|
11
|
+
* ```
|
|
12
|
+
* Component (TSX)
|
|
13
|
+
* ↓ builds semantic tree via DTO helpers (a11y-node-dto.ts)
|
|
14
|
+
* A11yNode tree (this file)
|
|
15
|
+
* ↓ compiled by an A11yTreeStrategy
|
|
16
|
+
* AriaPatch[]
|
|
17
|
+
* ↓ applied by a11y-tree-applier.ts
|
|
18
|
+
* Shadow DOM (ARIA attributes on data-a11y-id elements)
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* The A11y Tree is NOT rendered itself. It is consumed by an
|
|
22
|
+
* `A11yTreeStrategy` that decides, per browser/AT, which ARIA attributes
|
|
23
|
+
* to attach to which DOM nodes.
|
|
24
|
+
*
|
|
25
|
+
* ## Browser-engine strategy contract
|
|
26
|
+
*
|
|
27
|
+
* Assistive technology (AT) cannot be detected from JavaScript (by design,
|
|
28
|
+
* for user privacy). The strategy resolver therefore branches on *browser
|
|
29
|
+
* engine* as a coarse proxy for the most likely AT pairing:
|
|
30
|
+
*
|
|
31
|
+
* | Engine | Assumed AT | Confidence |
|
|
32
|
+
* |-----------|-------------------|---------------------|
|
|
33
|
+
* | WebKit | VoiceOver (macOS/iOS) | medium on Apple, low elsewhere |
|
|
34
|
+
* | Gecko | NVDA (Windows), Orca (Linux) | medium on Win, low elsewhere |
|
|
35
|
+
* | Chromium | JAWS / NVDA / TalkBack | high on non-Apple, medium on Apple |
|
|
36
|
+
*
|
|
37
|
+
* **Confidence semantics** (see `A11yStrategyConfidence`):
|
|
38
|
+
* - `high` — direct signal (explicit override or confirmed UA).
|
|
39
|
+
* - `medium` — plausible proxy; AT pairing is an assumption that covers the
|
|
40
|
+
* majority of users on that engine+platform combination.
|
|
41
|
+
* - `low` — unknown UA or atypical engine/platform combo; dev-mode warning
|
|
42
|
+
* fires once per (strategy, reason) tuple via `resolveStrategy`.
|
|
43
|
+
*
|
|
44
|
+
* Consumers who know the AT environment (e.g. automated tests, a storybook
|
|
45
|
+
* knob) can pin a strategy explicitly via `data-a11y-strategy` on the host
|
|
46
|
+
* element — this is always treated as `high` confidence.
|
|
47
|
+
*/
|
|
48
|
+
export {};
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Baseline strategy: spec-compliant ARIA, works on evergreen Chromium,
|
|
3
|
+
* Firefox, and Safari with their default AT pairings (NVDA, Orca,
|
|
4
|
+
* VoiceOver-macOS). Used as the default when no engine-specific override
|
|
5
|
+
* matches.
|
|
6
|
+
*
|
|
7
|
+
* ## Mapping rules
|
|
8
|
+
*
|
|
9
|
+
* | Node role | ARIA output |
|
|
10
|
+
* |---------------|----------------------------------------------------|
|
|
11
|
+
* | heading | `role="heading"` + `aria-level` |
|
|
12
|
+
* | group/region | `role` + `aria-label` (if present) + `aria-describedby` (if description) |
|
|
13
|
+
* | img (decorative) | `aria-hidden="true"` |
|
|
14
|
+
* | img (labelled) | `role="img"` + `aria-label` |
|
|
15
|
+
* | presentation/none | `role="presentation"` |
|
|
16
|
+
*/
|
|
17
|
+
export const baselineStrategy = {
|
|
18
|
+
name: 'baseline',
|
|
19
|
+
/**
|
|
20
|
+
* Compile the semantic tree into spec-compliant ARIA patches.
|
|
21
|
+
*
|
|
22
|
+
* @param {A11yNode} root - Root of the semantic A11y Tree to compile.
|
|
23
|
+
* @returns {AriaPatch[]} Flat list of ARIA attribute patches to apply.
|
|
24
|
+
*/
|
|
25
|
+
compile(root) {
|
|
26
|
+
const patches = [];
|
|
27
|
+
walk(root, patches);
|
|
28
|
+
return patches;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Recursively translate a node and its children into ARIA patches.
|
|
33
|
+
*
|
|
34
|
+
* @param {A11yNode} node - Current node being translated.
|
|
35
|
+
* @param {AriaPatch[]} patches - Accumulator the produced patches are pushed onto.
|
|
36
|
+
*/
|
|
37
|
+
function walk(node, patches) {
|
|
38
|
+
var _a, _b;
|
|
39
|
+
switch (node.role) {
|
|
40
|
+
case 'heading':
|
|
41
|
+
patches.push({ targetId: node.id, attribute: 'role', value: 'heading' });
|
|
42
|
+
patches.push({
|
|
43
|
+
targetId: node.id,
|
|
44
|
+
attribute: 'aria-level',
|
|
45
|
+
value: String((_a = node.level) !== null && _a !== void 0 ? _a : 2),
|
|
46
|
+
});
|
|
47
|
+
break;
|
|
48
|
+
case 'group':
|
|
49
|
+
case 'region':
|
|
50
|
+
patches.push({ targetId: node.id, attribute: 'role', value: node.role });
|
|
51
|
+
if (node.label) {
|
|
52
|
+
patches.push({ targetId: node.id, attribute: 'aria-label', value: node.label });
|
|
53
|
+
}
|
|
54
|
+
if (node.description) {
|
|
55
|
+
// Baseline AT: describedby works reliably on group/region.
|
|
56
|
+
patches.push({
|
|
57
|
+
targetId: node.id,
|
|
58
|
+
attribute: 'aria-describedby',
|
|
59
|
+
value: `${node.id}__desc`,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
break;
|
|
63
|
+
case 'img':
|
|
64
|
+
if (node.decorative) {
|
|
65
|
+
patches.push({ targetId: node.id, attribute: 'aria-hidden', value: 'true' });
|
|
66
|
+
}
|
|
67
|
+
else if (node.label) {
|
|
68
|
+
patches.push({ targetId: node.id, attribute: 'role', value: 'img' });
|
|
69
|
+
patches.push({ targetId: node.id, attribute: 'aria-label', value: node.label });
|
|
70
|
+
}
|
|
71
|
+
break;
|
|
72
|
+
case 'presentation':
|
|
73
|
+
case 'none':
|
|
74
|
+
patches.push({ targetId: node.id, attribute: 'role', value: 'presentation' });
|
|
75
|
+
break;
|
|
76
|
+
}
|
|
77
|
+
(_b = node.children) === null || _b === void 0 ? void 0 : _b.forEach((c) => walk(c, patches));
|
|
78
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { baselineStrategy } from './baseline-strategy';
|
|
2
|
+
/**
|
|
3
|
+
* Firefox + NVDA strategy.
|
|
4
|
+
*
|
|
5
|
+
* Firefox/NVDA generally handles spec-compliant ARIA correctly. The one
|
|
6
|
+
* known quirk for the Vega component family:
|
|
7
|
+
*
|
|
8
|
+
* **NVDA browse-mode may skip `aria-describedby` on `role="group"`.**
|
|
9
|
+
* When a group container carries a description, NVDA in browse (reading)
|
|
10
|
+
* mode sometimes does not announce it. Workaround: promote the container to
|
|
11
|
+
* `role="region"`, which is part of the landmark navigation set and is
|
|
12
|
+
* reliably announced by NVDA when encountered during linear reading.
|
|
13
|
+
*
|
|
14
|
+
* All other node types fall back to baseline behaviour.
|
|
15
|
+
*/
|
|
16
|
+
export const firefoxNvdaStrategy = {
|
|
17
|
+
name: 'firefox-nvda',
|
|
18
|
+
/**
|
|
19
|
+
* Compile ARIA patches, applying the NVDA group-description workaround.
|
|
20
|
+
*
|
|
21
|
+
* @param {A11yNode} root - Root of the semantic A11y Tree to compile.
|
|
22
|
+
* @returns {AriaPatch[]} ARIA patches with group promoted to region when needed.
|
|
23
|
+
*/
|
|
24
|
+
compile(root) {
|
|
25
|
+
const patches = baselineStrategy.compile(root);
|
|
26
|
+
// Promote all described groups to region (not just the first one).
|
|
27
|
+
for (const group of collectDescribedGroups(root)) {
|
|
28
|
+
for (const p of patches) {
|
|
29
|
+
if (p.targetId === group.id && p.attribute === 'role' && p.value === 'group') {
|
|
30
|
+
p.value = 'region';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return patches;
|
|
35
|
+
},
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Collect every `group` node in the tree that carries a description,
|
|
39
|
+
* in depth-first order. Includes nested described groups.
|
|
40
|
+
*
|
|
41
|
+
* @param {A11yNode} node - Node to search from.
|
|
42
|
+
* @returns {A11yNode[]} All described group nodes, including nested ones.
|
|
43
|
+
*/
|
|
44
|
+
function collectDescribedGroups(node) {
|
|
45
|
+
var _a;
|
|
46
|
+
const out = [];
|
|
47
|
+
// Check if this node is a described group
|
|
48
|
+
if (node.role === 'group' && node.description) {
|
|
49
|
+
out.push(node);
|
|
50
|
+
}
|
|
51
|
+
// Always recurse into children to find nested described groups
|
|
52
|
+
for (const c of (_a = node.children) !== null && _a !== void 0 ? _a : []) {
|
|
53
|
+
out.push(...collectDescribedGroups(c));
|
|
54
|
+
}
|
|
55
|
+
return out;
|
|
56
|
+
}
|