@qaiddev/quests-embed 1.1.1 → 1.2.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 ADDED
@@ -0,0 +1,93 @@
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
+ * The API is deliberately identical to @qaiddev/thumbs-embed's a11y module so
11
+ * both embeds consume one shared surface. In quests-embed specifically:
12
+ * - {@link announce} is a GENERAL-PURPOSE announcer for step-change,
13
+ * loading, and thank-you messages. It is separate from the pre-existing
14
+ * per-step `.qaid-q-error` region (aria-live="polite") in embed.ts — that
15
+ * region stays the owner of field-validation errors. Callers should route
16
+ * step/status text through announce()'s polite region and only use the
17
+ * assertive region for load failures, so the two never clobber each other.
18
+ */
19
+ /** Host for the live regions: either a shadow root or a plain element. */
20
+ export type AnnounceRoot = ShadowRoot | HTMLElement;
21
+ /** Options accepted by {@link announce}. */
22
+ export interface AnnounceOptions {
23
+ /** Route the message through the assertive (role="alert") region. */
24
+ assertive?: boolean;
25
+ }
26
+ /** Options accepted by {@link applyDialog}. */
27
+ export interface ApplyDialogOptions {
28
+ /** id of the element that labels the dialog (wired via aria-labelledby). */
29
+ labelledbyId?: string;
30
+ /** id of the element that describes the dialog (wired via aria-describedby). */
31
+ describedbyId?: string;
32
+ /** Fallback accessible name when no labelling element exists. */
33
+ label?: string;
34
+ }
35
+ /** Handle returned by {@link createFocusTrap}. */
36
+ export interface FocusTrap {
37
+ /** Remove the trap's key handler and clean up any tabindex it added. */
38
+ release(): void;
39
+ }
40
+ /**
41
+ * Announce a message to assistive technology via a visually-hidden live region
42
+ * mounted inside `root`. The two regions (polite role="status" and assertive
43
+ * role="alert") are created lazily on first use and reused thereafter.
44
+ *
45
+ * The target region is cleared before the new text is written so that repeated
46
+ * identical messages are re-announced rather than coalesced.
47
+ */
48
+ export declare function announce(root: AnnounceRoot, message: string, opts?: AnnounceOptions): void;
49
+ /**
50
+ * Collect the focusable descendants of `container` in DOM order, excluding
51
+ * disabled controls, elements opted out with tabindex="-1", and elements that
52
+ * are hidden (via the hidden attribute or an inline display/visibility rule on
53
+ * themselves or an ancestor up to the document root).
54
+ */
55
+ export declare function getFocusable(container: HTMLElement): HTMLElement[];
56
+ /**
57
+ * Trap keyboard focus inside `container`: Tab / Shift+Tab cycle between the
58
+ * first and last focusable descendants and can never leave the container. On
59
+ * creation focus moves to the first focusable element, or to the container
60
+ * itself (made programmatically focusable) when it has no focusable children.
61
+ *
62
+ * Call {@link FocusTrap.release} to remove the handler; pair it with
63
+ * {@link restoreFocus} to return focus to the invoking control.
64
+ */
65
+ export declare function createFocusTrap(container: HTMLElement): FocusTrap;
66
+ /**
67
+ * Capture the currently focused element (drilling through shadow roots) so it
68
+ * can be restored later with {@link restoreFocus}. Returns null when focus is
69
+ * on nothing focusable.
70
+ */
71
+ export declare function saveFocus(): HTMLElement | null;
72
+ /**
73
+ * Restore focus to a previously saved element. Safe to call with null or an
74
+ * element that has since been detached (a missing/throwing focus is ignored).
75
+ */
76
+ export declare function restoreFocus(el: HTMLElement | null): void;
77
+ /**
78
+ * Apply dialog semantics to `el`: role="dialog", aria-modal="true", and the
79
+ * naming/description wiring described by `opts`. Prefer aria-labelledby /
80
+ * aria-describedby pointing at visible title/subtitle elements; fall back to
81
+ * aria-label when no visible label element exists.
82
+ */
83
+ export declare function applyDialog(el: HTMLElement, opts?: ApplyDialogOptions): void;
84
+ /**
85
+ * Isolate the background from assistive technology while a dialog is open by
86
+ * marking every top-level sibling of the dialog inert (with an aria-hidden
87
+ * fallback). `except` is the open dialog (or any element inside it); the
88
+ * top-level element that contains it is left interactive.
89
+ *
90
+ * Returns a restore function that reverts every attribute this call changed,
91
+ * leaving elements that were already inert/hidden untouched.
92
+ */
93
+ export declare function setBackgroundInert(except: HTMLElement): () => void;
package/dist/embed.d.ts CHANGED
@@ -31,6 +31,9 @@ export declare class QaidQuests {
31
31
  private backdropEl;
32
32
  private currentInput;
33
33
  private boundKeyDown;
34
+ private savedOpener;
35
+ private focusTrap;
36
+ private inertRestore;
34
37
  private cssVars;
35
38
  private hostThemeOverrides;
36
39
  private hostInlineVars;
@@ -73,6 +76,13 @@ export declare class QaidQuests {
73
76
  private jsonHeaders;
74
77
  private showSaving;
75
78
  private handleKeyDown;
79
+ /**
80
+ * Modal mode only: trap Tab within the card and mark the rest of the
81
+ * page inert while the dialog is open. Runs once (guarded) after the
82
+ * first step renders. Inline mode keeps normal page tab flow and is
83
+ * never trapped or isolated.
84
+ */
85
+ private setupModalA11y;
76
86
  private close;
77
87
  /** Destroy the embed and clean up all resources */
78
88
  destroy(): void;
package/dist/inputs.d.ts CHANGED
@@ -15,6 +15,15 @@ export interface QuestionInput {
15
15
  focus(): void;
16
16
  getValue(): AnswerValue;
17
17
  isValid(): boolean;
18
+ /**
19
+ * Mark the control invalid: sets aria-invalid="true" and links the
20
+ * error text (identified by `errorId`) via aria-describedby so a
21
+ * screen reader reads the error as the field's description. Do not
22
+ * rely on colour alone (WCAG 3.3.1 / 1.3.1 / 4.1.2).
23
+ */
24
+ setInvalid(errorId: string): void;
25
+ /** Clear aria-invalid and unlink the error text once the field is corrected. */
26
+ clearInvalid(): void;
18
27
  }
19
28
  export interface QuestionInputOptions {
20
29
  question: Question;