@qaiddev/thumbs-embed 1.0.16 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/a11y.d.ts +84 -0
- package/dist/element-selector.d.ts +96 -0
- package/dist/embed.d.ts +30 -0
- package/dist/qaid.js +546 -221
- package/dist/qaid.js.map +1 -1
- package/dist/qaid.umd.cjs +10 -10
- package/dist/qaid.umd.cjs.map +1 -1
- package/dist/types.d.ts +12 -0
- package/package.json +2 -1
package/dist/a11y.d.ts
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared accessibility primitives for the qaid embeds.
|
|
3
|
+
*
|
|
4
|
+
* These helpers are intentionally framework-free and self-contained (no CSS
|
|
5
|
+
* dependency) so they can be dropped into any open shadow root. They cover the
|
|
6
|
+
* cross-cutting WCAG 2.2 AA infrastructure called for in the embed
|
|
7
|
+
* accessibility plan: live-region announcements, focus management (trap,
|
|
8
|
+
* save/restore), dialog semantics, and background isolation.
|
|
9
|
+
*/
|
|
10
|
+
/** Host for the live regions: either a shadow root or a plain element. */
|
|
11
|
+
export type AnnounceRoot = ShadowRoot | HTMLElement;
|
|
12
|
+
/** Options accepted by {@link announce}. */
|
|
13
|
+
export interface AnnounceOptions {
|
|
14
|
+
/** Route the message through the assertive (role="alert") region. */
|
|
15
|
+
assertive?: boolean;
|
|
16
|
+
}
|
|
17
|
+
/** Options accepted by {@link applyDialog}. */
|
|
18
|
+
export interface ApplyDialogOptions {
|
|
19
|
+
/** id of the element that labels the dialog (wired via aria-labelledby). */
|
|
20
|
+
labelledbyId?: string;
|
|
21
|
+
/** id of the element that describes the dialog (wired via aria-describedby). */
|
|
22
|
+
describedbyId?: string;
|
|
23
|
+
/** Fallback accessible name when no labelling element exists. */
|
|
24
|
+
label?: string;
|
|
25
|
+
}
|
|
26
|
+
/** Handle returned by {@link createFocusTrap}. */
|
|
27
|
+
export interface FocusTrap {
|
|
28
|
+
/** Remove the trap's key handler and clean up any tabindex it added. */
|
|
29
|
+
release(): void;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Announce a message to assistive technology via a visually-hidden live region
|
|
33
|
+
* mounted inside `root`. The two regions (polite role="status" and assertive
|
|
34
|
+
* role="alert") are created lazily on first use and reused thereafter.
|
|
35
|
+
*
|
|
36
|
+
* The target region is cleared before the new text is written so that repeated
|
|
37
|
+
* identical messages are re-announced rather than coalesced.
|
|
38
|
+
*/
|
|
39
|
+
export declare function announce(root: AnnounceRoot, message: string, opts?: AnnounceOptions): void;
|
|
40
|
+
/**
|
|
41
|
+
* Collect the focusable descendants of `container` in DOM order, excluding
|
|
42
|
+
* disabled controls, elements opted out with tabindex="-1", and elements that
|
|
43
|
+
* are hidden (via the hidden attribute or an inline display/visibility rule on
|
|
44
|
+
* themselves or an ancestor up to the document root).
|
|
45
|
+
*/
|
|
46
|
+
export declare function getFocusable(container: HTMLElement): HTMLElement[];
|
|
47
|
+
/**
|
|
48
|
+
* Trap keyboard focus inside `container`: Tab / Shift+Tab cycle between the
|
|
49
|
+
* first and last focusable descendants and can never leave the container. On
|
|
50
|
+
* creation focus moves to the first focusable element, or to the container
|
|
51
|
+
* itself (made programmatically focusable) when it has no focusable children.
|
|
52
|
+
*
|
|
53
|
+
* Call {@link FocusTrap.release} to remove the handler; pair it with
|
|
54
|
+
* {@link restoreFocus} to return focus to the invoking control.
|
|
55
|
+
*/
|
|
56
|
+
export declare function createFocusTrap(container: HTMLElement): FocusTrap;
|
|
57
|
+
/**
|
|
58
|
+
* Capture the currently focused element (drilling through shadow roots) so it
|
|
59
|
+
* can be restored later with {@link restoreFocus}. Returns null when focus is
|
|
60
|
+
* on nothing focusable.
|
|
61
|
+
*/
|
|
62
|
+
export declare function saveFocus(): HTMLElement | null;
|
|
63
|
+
/**
|
|
64
|
+
* Restore focus to a previously saved element. Safe to call with null or an
|
|
65
|
+
* element that has since been detached (a missing/throwing focus is ignored).
|
|
66
|
+
*/
|
|
67
|
+
export declare function restoreFocus(el: HTMLElement | null): void;
|
|
68
|
+
/**
|
|
69
|
+
* Apply dialog semantics to `el`: role="dialog", aria-modal="true", and the
|
|
70
|
+
* naming/description wiring described by `opts`. Prefer aria-labelledby /
|
|
71
|
+
* aria-describedby pointing at visible title/subtitle elements; fall back to
|
|
72
|
+
* aria-label when no visible label element exists.
|
|
73
|
+
*/
|
|
74
|
+
export declare function applyDialog(el: HTMLElement, opts?: ApplyDialogOptions): void;
|
|
75
|
+
/**
|
|
76
|
+
* Isolate the background from assistive technology while a dialog is open by
|
|
77
|
+
* marking every top-level sibling of the dialog inert (with an aria-hidden
|
|
78
|
+
* fallback). `except` is the open dialog (or any element inside it); the
|
|
79
|
+
* top-level element that contains it is left interactive.
|
|
80
|
+
*
|
|
81
|
+
* Returns a restore function that reverts every attribute this call changed,
|
|
82
|
+
* leaving elements that were already inert/hidden untouched.
|
|
83
|
+
*/
|
|
84
|
+
export declare function setBackgroundInert(except: HTMLElement): () => void;
|
|
@@ -31,3 +31,99 @@ export declare function generateSelector(element: Element): string;
|
|
|
31
31
|
* Generate complete element info for feedback
|
|
32
32
|
*/
|
|
33
33
|
export declare function generateElementInfo(element: Element): ElementInfo;
|
|
34
|
+
/**
|
|
35
|
+
* Default set of "targetable" elements for keyboard navigation: focusable
|
|
36
|
+
* controls, ARIA-role'd nodes, common semantic content, and anything already
|
|
37
|
+
* carrying one of the stable data attributes we key selectors off of.
|
|
38
|
+
*/
|
|
39
|
+
export declare const TARGETABLE_SELECTOR: string;
|
|
40
|
+
/**
|
|
41
|
+
* Best-effort visibility test used to keep invisible nodes out of the keyboard
|
|
42
|
+
* candidate list. Deliberately layout-free (no getBoundingClientRect) so it is
|
|
43
|
+
* stable in headless/test environments: it only rejects nodes that are
|
|
44
|
+
* explicitly hidden via the `hidden` attribute, `aria-hidden`, or a computed
|
|
45
|
+
* `display:none` / `visibility:hidden`.
|
|
46
|
+
*/
|
|
47
|
+
export declare function isElementVisible(element: Element): boolean;
|
|
48
|
+
export interface CollectTargetableOptions {
|
|
49
|
+
/** Where to search for candidates (default `document.body`). */
|
|
50
|
+
root?: ParentNode;
|
|
51
|
+
/** CSS selector for candidate elements (default {@link TARGETABLE_SELECTOR}). */
|
|
52
|
+
selector?: string;
|
|
53
|
+
/** Return true to drop an element (e.g. the embed's own shadow hosts). */
|
|
54
|
+
isExcluded?: (element: Element) => boolean;
|
|
55
|
+
/** Visibility predicate (default {@link isElementVisible}). */
|
|
56
|
+
isVisible?: (element: Element) => boolean;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Collect the ordered list of elements a keyboard user can target. Document
|
|
60
|
+
* order matches the natural Tab order closely enough for feedback targeting.
|
|
61
|
+
*/
|
|
62
|
+
export declare function collectTargetableElements(options?: CollectTargetableOptions): Element[];
|
|
63
|
+
export interface KeyboardTargetingOptions {
|
|
64
|
+
/**
|
|
65
|
+
* Explicit candidate list. When omitted, candidates are collected via
|
|
66
|
+
* {@link collectTargetableElements} using `root` / `selector` / `isExcluded`
|
|
67
|
+
* / `isVisible`.
|
|
68
|
+
*/
|
|
69
|
+
candidates?: Element[];
|
|
70
|
+
/** Passed through to {@link collectTargetableElements} when `candidates` is omitted. */
|
|
71
|
+
root?: ParentNode;
|
|
72
|
+
/** Passed through to {@link collectTargetableElements} when `candidates` is omitted. */
|
|
73
|
+
selector?: string;
|
|
74
|
+
/** Passed through to {@link collectTargetableElements} when `candidates` is omitted. */
|
|
75
|
+
isExcluded?: (element: Element) => boolean;
|
|
76
|
+
/** Passed through to {@link collectTargetableElements} when `candidates` is omitted. */
|
|
77
|
+
isVisible?: (element: Element) => boolean;
|
|
78
|
+
/**
|
|
79
|
+
* Element to start the highlight on. Defaults to `document.activeElement`
|
|
80
|
+
* when it is one of the candidates, otherwise the first candidate.
|
|
81
|
+
*/
|
|
82
|
+
initial?: Element | null;
|
|
83
|
+
/**
|
|
84
|
+
* Move native focus to the highlighted element (default true). This is what
|
|
85
|
+
* keeps a screen reader / focus ring on the highlight; non-focusable nodes
|
|
86
|
+
* get a temporary `tabindex="-1"` that is removed on {@link stop}. The cursor
|
|
87
|
+
* is never hidden.
|
|
88
|
+
*/
|
|
89
|
+
moveFocus?: boolean;
|
|
90
|
+
/** Where the keydown listener is attached (default `document`). */
|
|
91
|
+
eventTarget?: Document | HTMLElement;
|
|
92
|
+
/** Fired whenever the highlighted candidate changes (and once on start). */
|
|
93
|
+
onHighlight?: (element: Element, index: number) => void;
|
|
94
|
+
/** Fired when the user commits a selection (Enter / Space). Auto-stops first. */
|
|
95
|
+
onSelect: (element: Element) => void;
|
|
96
|
+
/** Fired when the user cancels (Escape). Auto-stops first. */
|
|
97
|
+
onCancel?: () => void;
|
|
98
|
+
}
|
|
99
|
+
export interface KeyboardTargetingController {
|
|
100
|
+
/** The (frozen) candidate list being navigated. */
|
|
101
|
+
readonly candidates: readonly Element[];
|
|
102
|
+
/** Index of the highlighted candidate, or -1 when there are none. */
|
|
103
|
+
getIndex(): number;
|
|
104
|
+
/** The highlighted candidate, or null when there are none. */
|
|
105
|
+
getCurrent(): Element | null;
|
|
106
|
+
/** Highlight the next candidate (wraps to the first). */
|
|
107
|
+
next(): void;
|
|
108
|
+
/** Highlight the previous candidate (wraps to the last). */
|
|
109
|
+
prev(): void;
|
|
110
|
+
/** Highlight a specific index (wraps out-of-range values). */
|
|
111
|
+
moveTo(index: number): void;
|
|
112
|
+
/** Commit the current candidate (fires onSelect) and stop. */
|
|
113
|
+
select(): void;
|
|
114
|
+
/** Cancel (fires onCancel) and stop. */
|
|
115
|
+
cancel(): void;
|
|
116
|
+
/**
|
|
117
|
+
* Handle a keydown. Wired automatically to `eventTarget`; exposed so a host
|
|
118
|
+
* can forward events from another surface (e.g. the embed's shadow root).
|
|
119
|
+
*/
|
|
120
|
+
handleKey(event: KeyboardEvent): void;
|
|
121
|
+
/** Remove the listener and restore any temporary tabindex. Idempotent. */
|
|
122
|
+
stop(): void;
|
|
123
|
+
}
|
|
124
|
+
/**
|
|
125
|
+
* Start a keyboard element-targeting session. The returned controller wires its
|
|
126
|
+
* own capture-phase keydown listener on `eventTarget` and emits the initial
|
|
127
|
+
* highlight synchronously before returning.
|
|
128
|
+
*/
|
|
129
|
+
export declare function startKeyboardTargeting(options: KeyboardTargetingOptions): KeyboardTargetingController;
|
package/dist/embed.d.ts
CHANGED
|
@@ -42,6 +42,13 @@ export declare class QaidFeedback {
|
|
|
42
42
|
private backdrop;
|
|
43
43
|
private dismissBtn;
|
|
44
44
|
private cssVars;
|
|
45
|
+
private keyboardController;
|
|
46
|
+
private activeThumbBtn;
|
|
47
|
+
private keyboardActivation;
|
|
48
|
+
private dialogTrigger;
|
|
49
|
+
private dialogTrap;
|
|
50
|
+
private dialogRestoreInert;
|
|
51
|
+
private readonly uid;
|
|
45
52
|
private boundKeyDown;
|
|
46
53
|
private boundMouseMove;
|
|
47
54
|
private boundClick;
|
|
@@ -52,6 +59,21 @@ export declare class QaidFeedback {
|
|
|
52
59
|
private boundBeforeSwap;
|
|
53
60
|
constructor(config: FeedbackConfig);
|
|
54
61
|
private applyVars;
|
|
62
|
+
/**
|
|
63
|
+
* Announce a message via the shared visually-hidden live regions.
|
|
64
|
+
* Prefer the overlay shadow root (which hosts every transient surface and
|
|
65
|
+
* is never inerted by its own dialogs) so announcements are not suppressed
|
|
66
|
+
* while a dialog aria-hides the main button host.
|
|
67
|
+
*/
|
|
68
|
+
private announceMsg;
|
|
69
|
+
/**
|
|
70
|
+
* Turn a transient surface into an accessible modal dialog: save the
|
|
71
|
+
* invoking control, apply dialog semantics, trap focus, and inert the
|
|
72
|
+
* background. Paired with closeDialogA11y() on every close path.
|
|
73
|
+
*/
|
|
74
|
+
private openDialogA11y;
|
|
75
|
+
private closeDialogA11y;
|
|
76
|
+
private clearActiveThumb;
|
|
55
77
|
private init;
|
|
56
78
|
/**
|
|
57
79
|
* Watch for the shadow hosts being removed from the DOM by framework
|
|
@@ -81,6 +103,14 @@ export declare class QaidFeedback {
|
|
|
81
103
|
private handleThumbClick;
|
|
82
104
|
private submitDirectFeedback;
|
|
83
105
|
private startTargeting;
|
|
106
|
+
/**
|
|
107
|
+
* Keyboard-driven targeting. Mirrors startTargeting minus the mouse
|
|
108
|
+
* plumbing: no `qaid-targeting` body class (keeps the cursor visible for
|
|
109
|
+
* keyboard users), no mouse reticle, and no document mouse/click listeners.
|
|
110
|
+
* The KeyboardTargetingController owns Tab/Arrow/Enter/Space/Escape.
|
|
111
|
+
*/
|
|
112
|
+
private startKeyboardTargetingFlow;
|
|
113
|
+
private selectKeyboardTarget;
|
|
84
114
|
private createTargetingOverlay;
|
|
85
115
|
private handleKeyDown;
|
|
86
116
|
private handleMouseMove;
|