@namiml/web-sdk 3.4.0-dev.202605241634 → 3.4.0-dev.202605261547

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.
@@ -1,88 +1,66 @@
1
1
  /**
2
- * Cross-platform text-to-speech for the Nami Web SDK.
2
+ * Text-to-speech for the Nami Web SDK.
3
3
  *
4
- * Targets:
5
- * - VIZIO → window.VIZIO.Chromevox.play(text)
6
- * - Other CTV (Samsung Tizen, LG webOS, Xbox) → window.speechSynthesis
7
- * - Browser (desktop / mobile / tablet) → no SDK-driven TTS; the OS screen
8
- * reader (VoiceOver, TalkBack, NVDA/JAWS, Orca) reads the ARIA path
9
- * directly. Firing `speechSynthesis` here would duplicate the
10
- * announcement and cause overlapping audio.
11
- * - Headless / no API available → no-op; callers should also set `aria-label`
12
- * on the focused element so the platform screenreader picks it up via the
13
- * ARIA path.
4
+ * Speech requests are SDK-controlled and must never break paywall rendering.
14
5
  *
15
- * Selection precedence on `speak()`:
16
- * 1. VIZIO Chromevox (when present and enabled by the user).
17
- * 2. `window.speechSynthesis` (CTV form factors only Bowser
18
- * `getPlatform().type === 'tv'`). Browsers rely on the ARIA path.
19
- * 3. No-op; the caller's ARIA label remains the final fallback.
20
- *
21
- * The CTV gate exists because no Web API reports OS-level screen-reader
22
- * state. On a CTV runtime (typically no integrated screen reader) we are
23
- * the AT path; on a browser, the OS AT already reads ARIA and our extra
24
- * utterance would duplicate it. See NAM-1102 for the full rationale.
25
- */
26
- /**
27
- * Attach platform-specific TTS event listeners. Idempotent. Currently only
28
- * VIZIO surfaces enable/disable events at runtime; other engines are queried
29
- * on demand inside `speak()`.
6
+ * The feature layer below is platform neutral: it builds initial paywall
7
+ * composites, speaks focused labels, gates speech during media playback, and
8
+ * cleans up on navigation. Actual speech is owned by explicit engine adapters
9
+ * where a programmable engine is reliable. Some TV runtimes also speak focused
10
+ * controls through system accessibility; for those runtimes the SDK aligns
11
+ * labels and lets the platform own focus speech instead of starting a second
12
+ * programmable utterance for the same focus event.
30
13
  */
14
+ import { type TComponent, type TPages } from '@namiml/sdk-core';
15
+ type TtsRequestPriority = 'low' | 'normal' | 'high';
16
+ type SpeakOptions = {
17
+ interrupt?: boolean;
18
+ reason?: 'composite' | 'focus' | 'manual';
19
+ transactionId?: number;
20
+ element?: Element | null;
21
+ focusPath?: Element[];
22
+ focusedLabel?: string;
23
+ priority?: TtsRequestPriority;
24
+ };
25
+ export type FocusContext = {
26
+ composedPath?: EventTarget[];
27
+ target?: EventTarget | null;
28
+ currentTarget?: EventTarget | null;
29
+ relatedTarget?: EventTarget | null;
30
+ };
31
+ export type TtsEngineName = 'webos-luna-tts' | 'web-speech' | 'vizio-chromevox';
32
+ export declare function setTtsDebugEnabled(enabled: boolean): void;
33
+ export declare function hasActiveCompositeAccessibilityAlignment(element: Element): boolean;
34
+ export declare const hasActiveWebOsLunaCompositeLabelAlignment: typeof hasActiveCompositeAccessibilityAlignment;
35
+ export declare function getEstimatedSpeechRestoreDelayMs(text: string): number;
36
+ export declare function restoreCompositeAccessibilityAlignment(element?: Element, transactionId?: number, reason?: string, force?: boolean): void;
37
+ export declare const restoreWebOsLunaCompositeLabelAlignment: typeof restoreCompositeAccessibilityAlignment;
38
+ export declare function scheduleCompositeAccessibilityAlignmentRestoreAfterBlur(element: Element, relatedTarget?: EventTarget | null): void;
39
+ export declare const scheduleWebOsLunaCompositeLabelAlignmentRestoreAfterBlur: typeof scheduleCompositeAccessibilityAlignmentRestoreAfterBlur;
40
+ export declare function isTtsSupported(): boolean;
41
+ export declare function setTtsEnabled(enabled: boolean): void;
31
42
  export declare function attachTtsEventListeners(): void;
32
- /**
33
- * Set whether a page's media is currently producing audio. Toggled by
34
- * `VideoService` (`onplay`/`onpause`/`onended`). When set to `false`, any
35
- * deferred speech is flushed in this call.
36
- */
37
43
  export declare function setMediaPlaying(playing: boolean): void;
38
- /** Whether media is currently flagged as playing (test/debug helper). */
39
44
  export declare function isMediaPlaying(): boolean;
40
- /**
41
- * Cancel any in-flight or queued speech across every available engine.
42
- * Used implicitly by {@link speak} before starting a new utterance so that
43
- * a focus change mid-announcement interrupts the previous announcement
44
- * instead of letting it run concurrently. Also called when a page changes
45
- * and the previous announcement is no longer relevant.
46
- *
47
- * VIZIO Chromevox does not document a single cancel method across firmware
48
- * revisions, so we try the names that have appeared in the wild (`cancel`,
49
- * `stop`) and ignore any errors — silence on cancel is a strict improvement
50
- * over the overlap the user otherwise hears.
51
- */
45
+ export declare function stopSpeech(preserveTransactionId?: number): void;
52
46
  export declare function cancelSpeech(): void;
53
- /**
54
- * Speak `text` via the highest-priority TTS engine available. Empty / falsy
55
- * input is a no-op. While media is playing the latest request is buffered
56
- * and flushed on `setMediaPlaying(false)`. Errors are swallowed — TTS should
57
- * never break the paywall.
58
- *
59
- * NOTE: This function does NOT cancel any in-flight speech on its own.
60
- * Cancellation is the caller's responsibility — call {@link cancelSpeech}
61
- * at semantic interaction boundaries (e.g. inside a focus-change handler)
62
- * rather than on every utterance. Cancelling unconditionally inside
63
- * `speak()` is destructive on some VIZIO Chromevox firmware revisions
64
- * where the cancel + play sequence aborts the very utterance we just
65
- * tried to start.
66
- */
47
+ export declare function markTtsEngineUnavailable(engine?: TtsEngineName): void;
48
+ export declare function speakText(text: string, options?: SpeakOptions): boolean;
67
49
  export declare function speak(text: string): void;
68
- import type { TPages } from '@namiml/sdk-core';
69
- /** Inform the screenreader subsystem which page is currently rendered.
70
- * Resets the announced flag when the page actually changes. Safe to call
71
- * on every render.
72
- *
73
- * Comparison is by object identity, NOT by `page.name`. Every paywall in
74
- * the `a_onboarding_flow` staging payload contains a single page named
75
- * literally `'page1'` comparing by name would let Page 2's first focus
76
- * inherit Page 1's `pageAnnounced=true` state and silently drop the
77
- * composite. Page objects are sourced from each paywall's own
78
- * `template.pages` array, so reference equality cleanly distinguishes
79
- * pages across paywalls. */
50
+ export declare function diagnoseTtsEngine(text?: string): boolean;
51
+ export declare function focusEventReadsLabel(): boolean;
52
+ export declare function getSpeechLabelFromElement(element: Element | null | undefined): string;
53
+ export declare function getSpeechLabelFromComponent(component: TComponent | null | undefined): string;
54
+ export declare function applySpeechAttributes(element: Element, label: string): void;
55
+ export declare function buildInitialScreenSpeech(page: TPages | null | undefined, renderedRoot?: ParentNode | null, includeControls?: boolean): string;
56
+ export declare function speakInitialScreen(page: TPages | null | undefined, renderedRoot?: ParentNode | null): void;
57
+ export declare function preparePrimaryCtaCompositeFocus(element: Element, context?: FocusEvent | FocusContext): boolean;
58
+ export declare function attachFocusSpeechListeners(root?: Element | Document | DocumentFragment): () => void;
59
+ export declare function detachFocusSpeechListeners(): void;
80
60
  export declare function setActivePage(page: TPages | null | undefined): void;
81
- /** Get the currently active page (or null if none). */
61
+ export declare function setActivePageRenderRoot(root: ParentNode | null | undefined): void;
82
62
  export declare function getActivePage(): TPages | null;
83
- /** Whether the page-level composite has not yet been announced for the active page. */
84
63
  export declare function shouldAnnounceComposite(): boolean;
85
- /** Mark the active page as having had its composite announced. */
86
64
  export declare function markPageAnnounced(): void;
87
- /** Clear all screenreader state — called when the paywall closes. */
88
65
  export declare function resetScreenreaderState(): void;
66
+ export {};
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@namiml/web-sdk",
3
3
  "type": "module",
4
- "version": "3.4.0-dev.202605241634",
4
+ "version": "3.4.0-dev.202605261547",
5
5
  "source": "src/nami-web.ts",
6
6
  "description": "Subscription monetization infrastructure — drop-in SDK with no-code paywalls, onboarding flows, A/B testing for web",
7
7
  "scripts": {
@@ -100,7 +100,7 @@
100
100
  "dependencies": {
101
101
  "@lit-labs/ssr-dom-shim": "^1.2.1",
102
102
  "@lit/context": "^1.1.3",
103
- "@namiml/sdk-core": "3.4.0-dev.202605241634",
103
+ "@namiml/sdk-core": "3.4.0-dev.202605261547",
104
104
  "bowser": "^2.11.0",
105
105
  "hls.js": "^1.5.18",
106
106
  "lit": "^3.3.1",