ally-widget 1.0.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 ADDED
@@ -0,0 +1,527 @@
1
+ # Ally Widget
2
+
3
+ A lightweight, framework-free accessibility widget for any website. Drop it in with a single `<script>` tag or import it as an npm package — it adds a floating panel of accessibility controls that users can customize per-session.
4
+
5
+ - **No dependencies** — vanilla JS, ~154 KB minified
6
+ - **Three delivery formats** — CDN (IIFE), ESM (bundlers), CJS (Node/SSR)
7
+ - **Languages** — English and Nepali built-in
8
+ - **WCAG 2.x aligned** — focus trapping, ARIA roles, keyboard navigation throughout
9
+
10
+ ---
11
+
12
+ ## Installation
13
+
14
+ ### CDN / Direct script tag
15
+
16
+ ```html
17
+ <script src="https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js"></script>
18
+ ```
19
+
20
+ Or download `dist/ally-widget.min.js` and host it yourself.
21
+
22
+ ### npm
23
+
24
+ ```bash
25
+ npm install ally-widget
26
+ ```
27
+
28
+ ```js
29
+ // ESM bundler (Vite, webpack, Rollup…)
30
+ import AllyWidget from 'ally-widget';
31
+
32
+ // CommonJS (Node / SSR)
33
+ const AllyWidget = require('ally-widget');
34
+ ```
35
+
36
+ ---
37
+
38
+ ## Quick Start
39
+
40
+ ### Script tag (zero config)
41
+
42
+ The widget auto-initialises on `DOMContentLoaded`. Just include the script:
43
+
44
+ ```html
45
+ <script src="dist/ally-widget.min.js"></script>
46
+ ```
47
+
48
+ A button appears at the bottom-right corner. Press **Alt + A** to toggle the panel.
49
+
50
+ ### Configure before loading
51
+
52
+ Set `window.AllyWidgetOptions` **before** the script tag:
53
+
54
+ ```html
55
+ <script>
56
+ window.AllyWidgetOptions = {
57
+ position: 'bottom-right',
58
+ lang: 'ne', // 'en' | 'ne'
59
+ primaryColor: '#e11d48',
60
+ poweredByText: 'My Company',
61
+ poweredByUrl: 'https://mycompany.com'
62
+ };
63
+ </script>
64
+ <script src="dist/ally-widget.min.js"></script>
65
+ ```
66
+
67
+ ### ESM / npm
68
+
69
+ ```js
70
+ import AllyWidget from 'ally-widget';
71
+
72
+ const widget = new AllyWidget({
73
+ position: 'bottom-left',
74
+ primaryColor: '#0ea5e9',
75
+ disableFeatures: ['hide-images', 'annotations'],
76
+ onFeatureToggle(key, enabled) {
77
+ analytics.track('a11y_toggle', { key, enabled });
78
+ }
79
+ });
80
+
81
+ widget.startAccessibleWebWidget();
82
+ ```
83
+
84
+ ---
85
+
86
+ ## All Options
87
+
88
+ | Option | Type | Default | Description |
89
+ |---|---|---|---|
90
+ | `position` | `string` | `'bottom-right'` | `'bottom-right'` \| `'bottom-left'` \| `'top-right'` \| `'top-left'` |
91
+ | `offset` | `[number, number]` | `[20, 20]` | `[horizontal, vertical]` px from edge |
92
+ | `size` | `string` | `'52px'` | Toggle button size — px, em, rem, or % |
93
+ | `lang` | `string` | browser lang | `'en'` or `'ne'` |
94
+ | `storageKey` | `string` | `'ally-wgt'` | localStorage key — change to avoid collisions when multiple instances share a domain |
95
+ | `keyboardShortcut` | `boolean` | `true` | `Alt+A` toggles the panel. Set `false` to disable |
96
+ | `icon` | `string` | `'default'` | Built-in variant name or a raw SVG string for the toggle button icon |
97
+ | `icons` | `object` | — | Override any icon in the full icon set (see [Icon Keys](#icon-keys)) |
98
+ | `poweredByText` | `string` | `'Ally Widget'` | Footer link label |
99
+ | `poweredByUrl` | `string` | widget repo URL | Footer link href |
100
+ | `disableFeatures` | `string[]` | `[]` | Feature keys to hide from the panel (see [Feature Keys](#feature-keys)) |
101
+ | `featureOverrides` | `object` | `{}` | Override `label` or `icon` for any feature button |
102
+ | `theme` | `object` | — | All theme tokens nested under one key (alternative to passing tokens at the top level) |
103
+ | `ttsNativeVoiceName` | `string` | — | Exact browser voice name to prefer for TTS |
104
+ | `ttsNativeVoiceLang` | `string` | — | BCP-47 language tag for TTS voice selection (e.g. `'ne-NP'`) |
105
+ | `ttsRate` | `number` | `1` | Speech rate — `0.5` to `2` |
106
+ | `ttsPitch` | `number` | `1` | Speech pitch — `0` to `2` |
107
+ | `onOpen` | `function` | — | Called when the panel opens |
108
+ | `onClose` | `function` | — | Called when the panel closes |
109
+ | `onFeatureToggle` | `function(key, enabled)` | — | Called on every feature toggle |
110
+ | `onReset` | `function` | — | Called when the user resets all settings |
111
+
112
+ ---
113
+
114
+ ## Theme Customization
115
+
116
+ Pass any of these tokens directly in options, or group them under `theme: { … }`:
117
+
118
+ ```js
119
+ window.AllyWidgetOptions = {
120
+ // Option A — flat
121
+ primaryColor: '#e11d48',
122
+ borderRadius: '8px',
123
+
124
+ // Option B — grouped (same result)
125
+ theme: {
126
+ primaryColor: '#e11d48',
127
+ borderRadius: '8px'
128
+ }
129
+ };
130
+ ```
131
+
132
+ ### Theme tokens
133
+
134
+ | Token | Default | Notes |
135
+ |---|---|---|
136
+ | `primaryColor` | `#4f46e5` | Main brand color — button, active states |
137
+ | `primaryColorLight` | `#818cf8` | Lighter tint |
138
+ | `primaryColorDark` | `#3730a3` | Darker shade |
139
+ | `backgroundColor` | `#ffffff` | Panel background |
140
+ | `textColor` | `#1f2937` | Panel text |
141
+ | `textColorInverted` | `#ffffff` | Text on primary background |
142
+ | `cardBackground` | `#f9fafb` | Feature card background |
143
+ | `borderColor` | `#e5e7eb` | Dividers and borders |
144
+ | `focusRingColor` | `#4f46e5` | Keyboard focus ring |
145
+ | `hoverColor` | `#f3f4f6` | Button hover background |
146
+ | `activeColor` | `#ede9fe` | Active/selected button background |
147
+ | `borderRadius` | `12px` | Panel corner radius |
148
+ | `buttonBorderRadius` | `50%` | Toggle button shape |
149
+ | `headerHeight` | `56px` | Panel header height |
150
+ | `focusBorderWidth` | `2px` | Focus ring width |
151
+ | `focusOutlineOffset` | `2px` | Focus ring offset |
152
+ | `zIndex` | `9999` | Stacking context |
153
+ | `buttonSize` | `52px` | Alias for `size` |
154
+
155
+ ---
156
+
157
+ ## Feature Keys
158
+
159
+ Use these keys with `disableFeatures` and `featureOverrides`.
160
+
161
+ ### Text
162
+
163
+ | Key | Label |
164
+ |---|---|
165
+ | `text-scale` | Font Size (slider) |
166
+ | `bold-text` | Font Weight |
167
+ | `line-spacing` | Line Height |
168
+ | `letter-spacing` | Letter Spacing |
169
+ | `readable-text` | Dyslexia Font |
170
+
171
+ ### Color & Contrast
172
+
173
+ | Key | Label |
174
+ |---|---|
175
+ | `contrast-toggle` | Contrast (cycles light → dark) |
176
+ | `saturation-toggle` | Saturation (cycles low → high) |
177
+ | `invert-colors` | Invert Colors |
178
+
179
+ ### Reading Aids
180
+
181
+ | Key | Label |
182
+ |---|---|
183
+ | `highlight-links` | Highlight Links |
184
+ | `highlight-title` | Highlight Titles |
185
+ | `reading-aid` | Reading Guide |
186
+ | `simple-layout` | Simplify Layout |
187
+
188
+ ### Interaction
189
+
190
+ | Key | Label |
191
+ |---|---|
192
+ | `large-pointer` | Big Cursor |
193
+ | `pause-motion` | Stop Animations |
194
+ | `hide-images` | Hide Images |
195
+ | `high-contrast-mode` | High Contrast |
196
+
197
+ ### Speech
198
+
199
+ | Key | Label |
200
+ |---|---|
201
+ | `text-to-speech` | Text to Speech |
202
+
203
+ ### Dev mode only (`?ally-dev=true`)
204
+
205
+ | Key | Label |
206
+ |---|---|
207
+ | `annotations` | Annotations (axe-core overlays) |
208
+ | `accessibility-report` | Accessibility Report |
209
+
210
+ ---
211
+
212
+ ## Disabling Features
213
+
214
+ ```js
215
+ window.AllyWidgetOptions = {
216
+ disableFeatures: [
217
+ 'hide-images',
218
+ 'readable-text', // remove Dyslexia Font button
219
+ 'annotations',
220
+ 'accessibility-report'
221
+ ]
222
+ };
223
+ ```
224
+
225
+ ---
226
+
227
+ ## Feature Label & Icon Overrides
228
+
229
+ ```js
230
+ window.AllyWidgetOptions = {
231
+ featureOverrides: {
232
+ 'bold-text': {
233
+ label: 'Bold'
234
+ },
235
+ 'text-to-speech': {
236
+ label: 'Read Aloud',
237
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">…</svg>'
238
+ }
239
+ }
240
+ };
241
+ ```
242
+
243
+ ---
244
+
245
+ ## Icon Keys
246
+
247
+ Pass a partial `icons` object to swap out any icon in the set:
248
+
249
+ ```js
250
+ window.AllyWidgetOptions = {
251
+ icons: {
252
+ largePointer: '<svg>…</svg>',
253
+ pauseMotion: '<svg>…</svg>',
254
+ readingAid: '<svg>…</svg>',
255
+ textToSpeech: '<svg>…</svg>',
256
+ highContrast: '<svg>…</svg>',
257
+ simplifyLayout: '<svg>…</svg>',
258
+ boldText: '<svg>…</svg>',
259
+ lineSpacing: '<svg>…</svg>',
260
+ letterSpacing: '<svg>…</svg>',
261
+ hideImages: '<svg>…</svg>',
262
+ dyslexiaFont: '<svg>…</svg>',
263
+ highlightLinks: '<svg>…</svg>',
264
+ highlightTitle: '<svg>…</svg>',
265
+ adjustFontSize: '<svg>…</svg>',
266
+ contrast: '<svg>…</svg>',
267
+ invertColors: '<svg>…</svg>',
268
+ saturation: '<svg>…</svg>',
269
+ annotations: '<svg>…</svg>',
270
+ accessibilityReport: '<svg>…</svg>',
271
+ reset: '<svg>…</svg>',
272
+ accessibility: '<svg>…</svg>' // toggle button default icon
273
+ }
274
+ };
275
+ ```
276
+
277
+ ---
278
+
279
+ ## Language
280
+
281
+ The widget ships with English (`en`) and Nepali (`ne`).
282
+
283
+ ```js
284
+ window.AllyWidgetOptions = { lang: 'ne' };
285
+ ```
286
+
287
+ The language picker in the panel footer lets users switch at runtime. The selection is persisted to localStorage.
288
+
289
+ ---
290
+
291
+ ## Text to Speech
292
+
293
+ The widget uses the browser's native [Web Speech API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API) — no external service or API key required.
294
+
295
+ When TTS is enabled, click any text element on the page to hear it read aloud.
296
+
297
+ **Nepali TTS** — modern browsers (Chrome, Edge) include a `ne-NP` voice. To force a specific voice:
298
+
299
+ ```js
300
+ window.AllyWidgetOptions = {
301
+ ttsNativeVoiceLang: 'ne-NP',
302
+ ttsNativeVoiceName: 'Google नेपाली', // optional — exact voice name
303
+ ttsRate: 0.9,
304
+ ttsPitch: 1.0
305
+ };
306
+ ```
307
+
308
+ If the requested voice is unavailable the widget falls back to the browser's default voice.
309
+
310
+ ---
311
+
312
+ ## Event Callbacks
313
+
314
+ ```js
315
+ window.AllyWidgetOptions = {
316
+ onOpen() {
317
+ console.log('Panel opened');
318
+ },
319
+
320
+ onClose() {
321
+ console.log('Panel closed');
322
+ },
323
+
324
+ onFeatureToggle(key, enabled) {
325
+ // key — feature key string (e.g. 'bold-text')
326
+ // enabled — boolean (true = on, false = off)
327
+ analytics.track('a11y_feature', { key, enabled });
328
+ },
329
+
330
+ onReset() {
331
+ console.log('All settings cleared');
332
+ }
333
+ };
334
+ ```
335
+
336
+ ---
337
+
338
+ ## Data Attributes
339
+
340
+ You can configure the widget via attributes on the script tag instead of `window.AllyWidgetOptions`:
341
+
342
+ ```html
343
+ <script
344
+ src="dist/ally-widget.min.js"
345
+ data-ally-lang="ne"
346
+ data-ally-position="bottom-left"
347
+ data-ally-size="60px"
348
+ data-ally-offset="24,24"
349
+ ></script>
350
+ ```
351
+
352
+ Supported attributes: `data-ally-lang`, `data-ally-position`, `data-ally-offset`, `data-ally-size`, `data-ally-icon`.
353
+
354
+ ---
355
+
356
+ ## Framework Integration
357
+
358
+ ### Next.js (App Router)
359
+
360
+ The widget is browser-only. Use `AllyWidget.init()` inside `useEffect` so it never runs on the server.
361
+
362
+ ```jsx
363
+ // components/AllyWidgetLoader.jsx
364
+ 'use client'
365
+ import { useEffect } from 'react'
366
+
367
+ export default function AllyWidgetLoader() {
368
+ useEffect(() => {
369
+ import('ally-widget').then(({ default: AllyWidget }) => {
370
+ AllyWidget.init({
371
+ position: 'bottom-right',
372
+ primaryColor: '#6366f1',
373
+ lang: 'en'
374
+ })
375
+ })
376
+ }, [])
377
+
378
+ return null
379
+ }
380
+ ```
381
+
382
+ ```jsx
383
+ // app/layout.jsx
384
+ import AllyWidgetLoader from '@/components/AllyWidgetLoader'
385
+
386
+ export default function RootLayout({ children }) {
387
+ return (
388
+ <html lang="en">
389
+ <body>
390
+ {children}
391
+ <AllyWidgetLoader />
392
+ </body>
393
+ </html>
394
+ )
395
+ }
396
+ ```
397
+
398
+ ### Next.js (Pages Router / `_document`)
399
+
400
+ ```jsx
401
+ // pages/_app.jsx
402
+ import { useEffect } from 'react'
403
+
404
+ export default function App({ Component, pageProps }) {
405
+ useEffect(() => {
406
+ import('ally-widget').then(({ default: AllyWidget }) => {
407
+ AllyWidget.init({ primaryColor: '#6366f1' })
408
+ })
409
+ }, [])
410
+
411
+ return <Component {...pageProps} />
412
+ }
413
+ ```
414
+
415
+ ### Next.js — CDN via `next/script` (zero npm install)
416
+
417
+ ```jsx
418
+ // app/layout.jsx
419
+ import Script from 'next/script'
420
+
421
+ export default function RootLayout({ children }) {
422
+ return (
423
+ <html lang="en">
424
+ <body>
425
+ {children}
426
+ <Script id="ally-config" strategy="beforeInteractive">{`
427
+ window.AllyWidgetOptions = { primaryColor: '#6366f1' };
428
+ `}</Script>
429
+ <Script
430
+ src="https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js"
431
+ strategy="afterInteractive"
432
+ />
433
+ </body>
434
+ </html>
435
+ )
436
+ }
437
+ ```
438
+
439
+ ### Nuxt 3
440
+
441
+ ```js
442
+ // plugins/ally-widget.client.js ← the .client suffix means browser-only
443
+ import AllyWidget from 'ally-widget'
444
+
445
+ export default defineNuxtPlugin(() => {
446
+ AllyWidget.init({
447
+ position: 'bottom-right',
448
+ primaryColor: '#6366f1'
449
+ })
450
+ })
451
+ ```
452
+
453
+ ### Laravel / Blade
454
+
455
+ ```html
456
+ {{-- resources/views/layouts/app.blade.php --}}
457
+ <script>
458
+ window.AllyWidgetOptions = {
459
+ position: 'bottom-right',
460
+ primaryColor: '#6366f1',
461
+ poweredByText: '{{ config("app.name") }}'
462
+ };
463
+ </script>
464
+ <script src="{{ asset('js/ally-widget.min.js') }}"></script>
465
+ ```
466
+
467
+ ### `AllyWidget.init()` reference
468
+
469
+ ```js
470
+ // Returns the widget instance (or null when called server-side)
471
+ const instance = AllyWidget.init(options)
472
+ ```
473
+
474
+ Accepts the same options object as the constructor. Handles `DOMContentLoaded` automatically — safe to call before the document is ready.
475
+
476
+ ### Disabling auto-init
477
+
478
+ When importing via npm in a framework, the CDN auto-init still fires if the module loads in a browser context. Suppress it with `autoInit: false`:
479
+
480
+ ```html
481
+ <script>
482
+ window.AllyWidgetOptions = { autoInit: false };
483
+ </script>
484
+ ```
485
+
486
+ Or when the module is loaded purely programmatically (e.g. inside `useEffect`), auto-init is already skipped because the module executes after `DOMContentLoaded` has already fired and you're calling `AllyWidget.init()` yourself.
487
+
488
+ ---
489
+
490
+ ## Dev Mode
491
+
492
+ Append `?ally-dev=true` to any URL to enable the Annotations and Accessibility Report tools. These run an axe-core scan and overlay issue markers on the page — useful during development, hidden in production.
493
+
494
+ ```
495
+ https://yoursite.com/page?ally-dev=true
496
+ ```
497
+
498
+ ---
499
+
500
+ ## Keyboard Shortcut
501
+
502
+ **Alt + A** toggles the panel on any page. Disable it with `keyboardShortcut: false`.
503
+
504
+ ---
505
+
506
+ ## Publishing to npm
507
+
508
+ ```bash
509
+ # Set your token
510
+ export NPM_TOKEN=your_token_here # or put it in .env
511
+
512
+ # Build then publish
513
+ npm run build
514
+ npm publish
515
+ ```
516
+
517
+ The `.npmrc` at the project root reads `NPM_TOKEN` automatically:
518
+
519
+ ```
520
+ //registry.npmjs.org/:_authToken=${NPM_TOKEN}
521
+ ```
522
+
523
+ ---
524
+
525
+ ## License
526
+
527
+ MIT — see [LICENSE](LICENSE)