ally-widget 1.0.0 → 1.0.2

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 CHANGED
@@ -355,23 +355,58 @@ Supported attributes: `data-ally-lang`, `data-ally-position`, `data-ally-offset`
355
355
 
356
356
  ## Framework Integration
357
357
 
358
- ### Next.js (App Router)
358
+ The widget is browser-only. `AllyWidget.init()` returns `null` when called server-side, so it is always safe to import — but you must ensure it mounts after the DOM is ready.
359
+
360
+ | Stack | Approach |
361
+ |---|---|
362
+ | Plain HTML | Script tag — just works |
363
+ | React / Vite | `useEffect` dynamic import |
364
+ | Next.js App Router | `'use client'` component + `useEffect` |
365
+ | Next.js Pages Router | `useEffect` in `_app.jsx` |
366
+ | Vue 3 | `onMounted` in `App.vue` |
367
+ | Nuxt 3 | `.client.js` plugin |
368
+ | SvelteKit | `onMount` in `+layout.svelte` |
369
+ | Astro | `<script>` with `is:inline` in Layout |
370
+ | WordPress | `wp_enqueue_script` in `functions.php` |
371
+ | Laravel / Blade | Script tag in layout |
372
+ | Angular | `ngAfterViewInit` in `AppComponent` |
373
+ | PHP (plain) | Script tag before `</body>` |
359
374
 
360
- The widget is browser-only. Use `AllyWidget.init()` inside `useEffect` so it never runs on the server.
375
+ ---
376
+
377
+ ### Plain HTML
378
+
379
+ ```html
380
+ <!DOCTYPE html>
381
+ <html lang="en">
382
+ <head>...</head>
383
+ <body>
384
+ <!-- your content -->
385
+
386
+ <script>
387
+ window.AllyWidgetOptions = {
388
+ position: 'bottom-right',
389
+ primaryColor: '#6366f1',
390
+ lang: 'en'
391
+ };
392
+ </script>
393
+ <script src="https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js"></script>
394
+ </body>
395
+ </html>
396
+ ```
397
+
398
+ ---
399
+
400
+ ### React (Vite / CRA)
361
401
 
362
402
  ```jsx
363
- // components/AllyWidgetLoader.jsx
364
- 'use client'
403
+ // src/components/AllyWidget.jsx
365
404
  import { useEffect } from 'react'
366
405
 
367
- export default function AllyWidgetLoader() {
406
+ export default function AllyWidget() {
368
407
  useEffect(() => {
369
408
  import('ally-widget').then(({ default: AllyWidget }) => {
370
- AllyWidget.init({
371
- position: 'bottom-right',
372
- primaryColor: '#6366f1',
373
- lang: 'en'
374
- })
409
+ AllyWidget.init({ primaryColor: '#6366f1' })
375
410
  })
376
411
  }, [])
377
412
 
@@ -380,39 +415,56 @@ export default function AllyWidgetLoader() {
380
415
  ```
381
416
 
382
417
  ```jsx
383
- // app/layout.jsx
384
- import AllyWidgetLoader from '@/components/AllyWidgetLoader'
418
+ // src/App.jsx
419
+ import AllyWidget from './components/AllyWidget'
385
420
 
386
- export default function RootLayout({ children }) {
421
+ export default function App() {
387
422
  return (
388
- <html lang="en">
389
- <body>
390
- {children}
391
- <AllyWidgetLoader />
392
- </body>
393
- </html>
423
+ <>
424
+ <YourApp />
425
+ <AllyWidget />
426
+ </>
394
427
  )
395
428
  }
396
429
  ```
397
430
 
398
- ### Next.js (Pages Router / `_document`)
431
+ ---
432
+
433
+ ### Next.js (App Router)
399
434
 
400
435
  ```jsx
401
- // pages/_app.jsx
436
+ // components/AllyWidget.jsx
437
+ 'use client'
402
438
  import { useEffect } from 'react'
403
439
 
404
- export default function App({ Component, pageProps }) {
440
+ export default function AllyWidget() {
405
441
  useEffect(() => {
406
442
  import('ally-widget').then(({ default: AllyWidget }) => {
407
- AllyWidget.init({ primaryColor: '#6366f1' })
443
+ AllyWidget.init({ primaryColor: '#6366f1', lang: 'en' })
408
444
  })
409
445
  }, [])
410
446
 
411
- return <Component {...pageProps} />
447
+ return null
448
+ }
449
+ ```
450
+
451
+ ```jsx
452
+ // app/layout.jsx
453
+ import AllyWidget from '@/components/AllyWidget'
454
+
455
+ export default function RootLayout({ children }) {
456
+ return (
457
+ <html lang="en">
458
+ <body>
459
+ {children}
460
+ <AllyWidget />
461
+ </body>
462
+ </html>
463
+ )
412
464
  }
413
465
  ```
414
466
 
415
- ### Next.js — CDN via `next/script` (zero npm install)
467
+ **Or — CDN via `next/script` (zero npm install):**
416
468
 
417
469
  ```jsx
418
470
  // app/layout.jsx
@@ -423,7 +475,7 @@ export default function RootLayout({ children }) {
423
475
  <html lang="en">
424
476
  <body>
425
477
  {children}
426
- <Script id="ally-config" strategy="beforeInteractive">{`
478
+ <Script id="ally-cfg" strategy="beforeInteractive">{`
427
479
  window.AllyWidgetOptions = { primaryColor: '#6366f1' };
428
480
  `}</Script>
429
481
  <Script
@@ -436,20 +488,126 @@ export default function RootLayout({ children }) {
436
488
  }
437
489
  ```
438
490
 
491
+ ---
492
+
493
+ ### Next.js (Pages Router)
494
+
495
+ ```jsx
496
+ // pages/_app.jsx
497
+ import { useEffect } from 'react'
498
+
499
+ export default function App({ Component, pageProps }) {
500
+ useEffect(() => {
501
+ import('ally-widget').then(({ default: AllyWidget }) => {
502
+ AllyWidget.init({ primaryColor: '#6366f1' })
503
+ })
504
+ }, [])
505
+
506
+ return <Component {...pageProps} />
507
+ }
508
+ ```
509
+
510
+ ---
511
+
512
+ ### Vue 3 (Vite)
513
+
514
+ ```js
515
+ // src/App.vue
516
+ <script setup>
517
+ import { onMounted } from 'vue'
518
+
519
+ onMounted(async () => {
520
+ const { default: AllyWidget } = await import('ally-widget')
521
+ AllyWidget.init({ primaryColor: '#6366f1' })
522
+ })
523
+ </script>
524
+ ```
525
+
526
+ ---
527
+
439
528
  ### Nuxt 3
440
529
 
441
530
  ```js
442
- // plugins/ally-widget.client.js ← the .client suffix means browser-only
531
+ // plugins/ally-widget.client.js
532
+ // The .client suffix tells Nuxt this is browser-only — no SSR guard needed
443
533
  import AllyWidget from 'ally-widget'
444
534
 
445
535
  export default defineNuxtPlugin(() => {
446
- AllyWidget.init({
447
- position: 'bottom-right',
448
- primaryColor: '#6366f1'
449
- })
536
+ AllyWidget.init({ primaryColor: '#6366f1' })
450
537
  })
451
538
  ```
452
539
 
540
+ ---
541
+
542
+ ### SvelteKit
543
+
544
+ ```svelte
545
+ <!-- src/routes/+layout.svelte -->
546
+ <script>
547
+ import { onMount } from 'svelte'
548
+
549
+ onMount(async () => {
550
+ const { default: AllyWidget } = await import('ally-widget')
551
+ AllyWidget.init({ primaryColor: '#6366f1' })
552
+ })
553
+ </script>
554
+
555
+ <slot />
556
+ ```
557
+
558
+ ---
559
+
560
+ ### Astro
561
+
562
+ ```astro
563
+ <!-- src/layouts/Layout.astro -->
564
+ ---
565
+ // server-side — nothing here
566
+ ---
567
+ <html lang="en">
568
+ <head>...</head>
569
+ <body>
570
+ <slot />
571
+
572
+ <script>
573
+ // This block runs in the browser only
574
+ import AllyWidget from 'ally-widget'
575
+ AllyWidget.init({ primaryColor: '#6366f1' })
576
+ </script>
577
+ </body>
578
+ </html>
579
+ ```
580
+
581
+ ---
582
+
583
+ ### WordPress
584
+
585
+ ```php
586
+ // functions.php (child theme or plugin)
587
+ function enqueue_ally_widget() {
588
+ wp_enqueue_script(
589
+ 'ally-widget',
590
+ 'https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js',
591
+ [],
592
+ '1.0.0',
593
+ true // load in footer
594
+ );
595
+
596
+ wp_add_inline_script(
597
+ 'ally-widget',
598
+ "window.AllyWidgetOptions = {
599
+ position: 'bottom-right',
600
+ primaryColor: '#6366f1',
601
+ poweredByText: '" . get_bloginfo('name') . "'
602
+ };",
603
+ 'before'
604
+ );
605
+ }
606
+ add_action('wp_enqueue_scripts', 'enqueue_ally_widget');
607
+ ```
608
+
609
+ ---
610
+
453
611
  ### Laravel / Blade
454
612
 
455
613
  ```html
@@ -461,21 +619,56 @@ export default defineNuxtPlugin(() => {
461
619
  poweredByText: '{{ config("app.name") }}'
462
620
  };
463
621
  </script>
464
- <script src="{{ asset('js/ally-widget.min.js') }}"></script>
622
+ <script src="{{ asset('vendor/ally-widget/ally-widget.min.js') }}"></script>
623
+ {{-- or CDN: src="https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js" --}}
465
624
  ```
466
625
 
626
+ ---
627
+
628
+ ### Angular
629
+
630
+ ```ts
631
+ // src/app/app.component.ts
632
+ import { Component, AfterViewInit } from '@angular/core'
633
+
634
+ @Component({ selector: 'app-root', templateUrl: './app.component.html' })
635
+ export class AppComponent implements AfterViewInit {
636
+ ngAfterViewInit() {
637
+ import('ally-widget').then(({ default: AllyWidget }) => {
638
+ AllyWidget.init({ primaryColor: '#6366f1' })
639
+ })
640
+ }
641
+ }
642
+ ```
643
+
644
+ ---
645
+
646
+ ### PHP (plain)
647
+
648
+ ```php
649
+ <!-- footer.php or before </body> -->
650
+ <script>
651
+ window.AllyWidgetOptions = {
652
+ position: 'bottom-right',
653
+ primaryColor: '#6366f1'
654
+ };
655
+ </script>
656
+ <script src="https://cdn.jsdelivr.net/npm/ally-widget/dist/ally-widget.min.js"></script>
657
+ ```
658
+
659
+ ---
660
+
467
661
  ### `AllyWidget.init()` reference
468
662
 
469
663
  ```js
470
- // Returns the widget instance (or null when called server-side)
471
664
  const instance = AllyWidget.init(options)
665
+ // Returns the widget instance, or null when called server-side.
666
+ // Handles DOMContentLoaded automatically — safe to call at any time.
472
667
  ```
473
668
 
474
- Accepts the same options object as the constructor. Handles `DOMContentLoaded` automatically — safe to call before the document is ready.
475
-
476
669
  ### Disabling auto-init
477
670
 
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`:
671
+ Suppress the automatic CDN init when you are calling `AllyWidget.init()` yourself:
479
672
 
480
673
  ```html
481
674
  <script>
@@ -483,7 +676,174 @@ When importing via npm in a framework, the CDN auto-init still fires if the modu
483
676
  </script>
484
677
  ```
485
678
 
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.
679
+ ---
680
+
681
+ ## Customization Guide
682
+
683
+ ### Minimal setup — just the essentials
684
+
685
+ Hide features your site doesn't need to keep the panel focused:
686
+
687
+ ```js
688
+ AllyWidget.init({
689
+ disableFeatures: [
690
+ 'readable-text', // remove Dyslexia Font
691
+ 'hide-images', // remove Hide Images
692
+ 'annotations', // hide dev tools in prod
693
+ 'accessibility-report'
694
+ ]
695
+ })
696
+ ```
697
+
698
+ ---
699
+
700
+ ### Custom colour theme
701
+
702
+ Pass any CSS value. The entire panel re-derives from these tokens:
703
+
704
+ ```js
705
+ AllyWidget.init({
706
+ // Red brand
707
+ primaryColor: '#e11d48',
708
+ primaryColorLight: '#fb7185',
709
+ primaryColorDark: '#9f1239',
710
+
711
+ // Dark panel
712
+ backgroundColor: '#0f172a',
713
+ textColor: '#e2e8f0',
714
+ cardBackground: '#1e293b',
715
+ borderColor: '#334155',
716
+ hoverColor: '#1e293b',
717
+ activeColor: '#312e81',
718
+
719
+ // Shape
720
+ borderRadius: '8px', // panel corners
721
+ buttonBorderRadius: '12px', // toggle button (50% = circle)
722
+ zIndex: 99999
723
+ })
724
+ ```
725
+
726
+ ---
727
+
728
+ ### Custom branding
729
+
730
+ Replace the footer link with your own:
731
+
732
+ ```js
733
+ AllyWidget.init({
734
+ poweredByText: 'My Company',
735
+ poweredByUrl: 'https://mycompany.com'
736
+ })
737
+ ```
738
+
739
+ ---
740
+
741
+ ### Rename or re-icon any button
742
+
743
+ ```js
744
+ AllyWidget.init({
745
+ featureOverrides: {
746
+ 'text-to-speech': { label: 'Read Page Aloud' },
747
+ 'reading-aid': { label: 'Focus Line' },
748
+ 'bold-text': {
749
+ label: 'Bold',
750
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">…</svg>'
751
+ }
752
+ }
753
+ })
754
+ ```
755
+
756
+ ---
757
+
758
+ ### Custom toggle button icon
759
+
760
+ ```js
761
+ AllyWidget.init({
762
+ // Built-in variants: 'default' | 'person' | 'eye' | 'universal'
763
+ icon: 'universal',
764
+
765
+ // Or pass any SVG string:
766
+ icon: '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24">…</svg>'
767
+ })
768
+ ```
769
+
770
+ ---
771
+
772
+ ### Position and size
773
+
774
+ ```js
775
+ AllyWidget.init({
776
+ position: 'bottom-left', // bottom-right | bottom-left | top-right | top-left
777
+ offset: [24, 32], // [horizontal, vertical] px from edge
778
+ size: '60px' // toggle button size
779
+ })
780
+ ```
781
+
782
+ ---
783
+
784
+ ### Event callbacks for analytics
785
+
786
+ ```js
787
+ AllyWidget.init({
788
+ onOpen() { analytics.track('ally_open') },
789
+ onClose() { analytics.track('ally_close') },
790
+
791
+ onFeatureToggle(key, enabled) {
792
+ analytics.track('ally_feature', { key, enabled })
793
+ // key examples: 'bold-text', 'contrast-toggle', 'text-to-speech'
794
+ },
795
+
796
+ onReset() { analytics.track('ally_reset') }
797
+ })
798
+ ```
799
+
800
+ ---
801
+
802
+ ### Full example — everything configured
803
+
804
+ ```js
805
+ window.AllyWidgetOptions = {
806
+ // Placement
807
+ position: 'bottom-right',
808
+ offset: [20, 20],
809
+ size: '56px',
810
+ keyboardShortcut: true, // Alt+A
811
+
812
+ // Language
813
+ lang: 'ne', // 'en' | 'ne'
814
+
815
+ // Storage
816
+ storageKey: 'myapp-ally',
817
+
818
+ // Branding
819
+ poweredByText: 'My Company',
820
+ poweredByUrl: 'https://mycompany.com',
821
+
822
+ // Theme
823
+ primaryColor: '#6366f1',
824
+ borderRadius: '12px',
825
+ buttonBorderRadius: '50%',
826
+ zIndex: 9999,
827
+
828
+ // Features
829
+ disableFeatures: ['annotations', 'accessibility-report'],
830
+
831
+ featureOverrides: {
832
+ 'text-to-speech': { label: 'Read Aloud' }
833
+ },
834
+
835
+ // TTS
836
+ ttsNativeVoiceLang: 'ne-NP',
837
+ ttsRate: 0.9,
838
+ ttsPitch: 1.0,
839
+
840
+ // Callbacks
841
+ onOpen() { console.log('opened') },
842
+ onClose() { console.log('closed') },
843
+ onFeatureToggle(key, enabled) { console.log(key, enabled) },
844
+ onReset() { console.log('reset') }
845
+ }
846
+ ```
487
847
 
488
848
  ---
489
849
 
@@ -1,7 +1,5 @@
1
1
  /*!
2
- * Ally Widget v1.0.0
3
- * https://github.com/SunilCz/ally
4
- *
2
+ * Ally Widget v1.0.2
5
3
  * Released under the MIT License
6
4
  */
7
5
  'use strict';
@@ -3507,7 +3505,7 @@ const uiMethods = {
3507
3505
 
3508
3506
  // Configurable branding
3509
3507
  const poweredByText = this.options?.poweredByText || 'Ally Widget';
3510
- const poweredByUrl = this.options?.poweredByUrl || 'https://github.com/SunilCz/ally';
3508
+ const poweredByUrl = this.options?.poweredByUrl || '';
3511
3509
 
3512
3510
  const menuTemplate = `
3513
3511
  <div class="acc-menu" role="dialog" aria-labelledby="accessibility-title">
@@ -4007,10 +4005,7 @@ const uiMethods = {
4007
4005
  };
4008
4006
 
4009
4007
  /*!
4010
- * Ally Widget v1.0.0
4011
- * https://github.com/SunilCz/ally
4012
- *
4013
- * Copyright (c) 2025 SunilCz
4008
+ * Ally Widget v1.0.2
4014
4009
  * Released under the MIT License
4015
4010
  *
4016
4011
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
@@ -4443,26 +4438,4 @@ if (typeof window !== 'undefined') {
4443
4438
  window.AllyWidget = AllyWidget;
4444
4439
  }
4445
4440
 
4446
- // Auto-init for CDN / script-tag usage.
4447
- // Skipped when:
4448
- // • running server-side (no document)
4449
- // • window.AllyWidgetOptions.autoInit === false (opt-out for manual control)
4450
- if (typeof document !== 'undefined') {
4451
- const globalAutoInitOptions = (
4452
- typeof window !== 'undefined' &&
4453
- window.AllyWidgetOptions &&
4454
- typeof window.AllyWidgetOptions === 'object'
4455
- ) ? window.AllyWidgetOptions : {};
4456
-
4457
- if (globalAutoInitOptions.autoInit !== false) {
4458
- /** @type {AllyWidgetInstance} */
4459
- const widgetInstance = new AllyWidget(globalAutoInitOptions);
4460
- if (document.readyState === 'complete' || document.readyState === 'interactive') {
4461
- widgetInstance.startAccessibleWebWidget();
4462
- } else {
4463
- document.addEventListener('DOMContentLoaded', () => widgetInstance.startAccessibleWebWidget(), { once: true });
4464
- }
4465
- }
4466
- }
4467
-
4468
4441
  module.exports = AllyWidget;
@@ -1,7 +1,5 @@
1
1
  /*!
2
- * Ally Widget v1.0.0
3
- * https://github.com/SunilCz/ally
4
- *
2
+ * Ally Widget v1.0.2
5
3
  * Released under the MIT License
6
4
  */
7
5
  const WIDGET_THEME = {
@@ -3505,7 +3503,7 @@ const uiMethods = {
3505
3503
 
3506
3504
  // Configurable branding
3507
3505
  const poweredByText = this.options?.poweredByText || 'Ally Widget';
3508
- const poweredByUrl = this.options?.poweredByUrl || 'https://github.com/SunilCz/ally';
3506
+ const poweredByUrl = this.options?.poweredByUrl || '';
3509
3507
 
3510
3508
  const menuTemplate = `
3511
3509
  <div class="acc-menu" role="dialog" aria-labelledby="accessibility-title">
@@ -4005,10 +4003,7 @@ const uiMethods = {
4005
4003
  };
4006
4004
 
4007
4005
  /*!
4008
- * Ally Widget v1.0.0
4009
- * https://github.com/SunilCz/ally
4010
- *
4011
- * Copyright (c) 2025 SunilCz
4006
+ * Ally Widget v1.0.2
4012
4007
  * Released under the MIT License
4013
4008
  *
4014
4009
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
@@ -4441,26 +4436,4 @@ if (typeof window !== 'undefined') {
4441
4436
  window.AllyWidget = AllyWidget;
4442
4437
  }
4443
4438
 
4444
- // Auto-init for CDN / script-tag usage.
4445
- // Skipped when:
4446
- // • running server-side (no document)
4447
- // • window.AllyWidgetOptions.autoInit === false (opt-out for manual control)
4448
- if (typeof document !== 'undefined') {
4449
- const globalAutoInitOptions = (
4450
- typeof window !== 'undefined' &&
4451
- window.AllyWidgetOptions &&
4452
- typeof window.AllyWidgetOptions === 'object'
4453
- ) ? window.AllyWidgetOptions : {};
4454
-
4455
- if (globalAutoInitOptions.autoInit !== false) {
4456
- /** @type {AllyWidgetInstance} */
4457
- const widgetInstance = new AllyWidget(globalAutoInitOptions);
4458
- if (document.readyState === 'complete' || document.readyState === 'interactive') {
4459
- widgetInstance.startAccessibleWebWidget();
4460
- } else {
4461
- document.addEventListener('DOMContentLoaded', () => widgetInstance.startAccessibleWebWidget(), { once: true });
4462
- }
4463
- }
4464
- }
4465
-
4466
4439
  export { AllyWidget as default };
@@ -1,7 +1,5 @@
1
1
  /*!
2
- * Ally Widget v1.0.0
3
- * https://github.com/SunilCz/ally
4
- *
2
+ * Ally Widget v1.0.2
5
3
  * Released under the MIT License
6
4
  */
7
5
  var AllyWidget = (function () {
@@ -3508,7 +3506,7 @@ var AllyWidget = (function () {
3508
3506
 
3509
3507
  // Configurable branding
3510
3508
  const poweredByText = this.options?.poweredByText || 'Ally Widget';
3511
- const poweredByUrl = this.options?.poweredByUrl || 'https://github.com/SunilCz/ally';
3509
+ const poweredByUrl = this.options?.poweredByUrl || '';
3512
3510
 
3513
3511
  const menuTemplate = `
3514
3512
  <div class="acc-menu" role="dialog" aria-labelledby="accessibility-title">
@@ -4008,10 +4006,7 @@ var AllyWidget = (function () {
4008
4006
  };
4009
4007
 
4010
4008
  /*!
4011
- * Ally Widget v1.0.0
4012
- * https://github.com/SunilCz/ally
4013
- *
4014
- * Copyright (c) 2025 SunilCz
4009
+ * Ally Widget v1.0.2
4015
4010
  * Released under the MIT License
4016
4011
  *
4017
4012
  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND.
@@ -4444,10 +4439,9 @@ var AllyWidget = (function () {
4444
4439
  window.AllyWidget = AllyWidget;
4445
4440
  }
4446
4441
 
4447
- // Auto-init for CDN / script-tag usage.
4448
- // Skipped when:
4449
- // running server-side (no document)
4450
- // • window.AllyWidgetOptions.autoInit === false (opt-out for manual control)
4442
+ // Auto-init only in IIFE/CDN builds (true is replaced at build time).
4443
+ // ESM and CJS builds set this to false so importing the module never triggers
4444
+ // a default-themed widget that would conflict with AllyWidget.init() calls.
4451
4445
  if (typeof document !== 'undefined') {
4452
4446
  const globalAutoInitOptions = (
4453
4447
  typeof window !== 'undefined' &&