@a.nemreen/a11y 0.1.2 → 0.1.6
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 +5 -0
- package/dist/index.cjs +179 -76
- package/dist/index.d.cts +20 -2
- package/dist/index.d.ts +20 -2
- package/dist/index.js +179 -76
- package/dist/styles.css +127 -37
- package/package.json +10 -3
package/README.md
CHANGED
|
@@ -37,6 +37,7 @@ const a11y = Accessibility.mount({
|
|
|
37
37
|
position: 'end', // end | start | left | right
|
|
38
38
|
stack: 0,
|
|
39
39
|
locale: 'auto', // auto | ar | en
|
|
40
|
+
// syncDocumentLocale: true, // also flip <html lang/dir> when toggling locale
|
|
40
41
|
});
|
|
41
42
|
|
|
42
43
|
Accessibility.createSkipLink({ href: '#main' });
|
|
@@ -74,6 +75,10 @@ Accessibility.mount();
|
|
|
74
75
|
|
|
75
76
|
[https://nemreen.info/a11y](https://nemreen.info/a11y)
|
|
76
77
|
|
|
78
|
+
## Architecture
|
|
79
|
+
|
|
80
|
+
[arc42](docs/architecture/README.md) (what to document) and [C4](docs/architecture/c4/README.md) (how to visualize it) — goals, context, building blocks, runtime, decisions, and known risks.
|
|
81
|
+
|
|
77
82
|
## License
|
|
78
83
|
|
|
79
84
|
MIT © [Ahmed Nemreen](https://nemreen.info)
|
package/dist/index.cjs
CHANGED
|
@@ -35,6 +35,7 @@ var A11yController = class {
|
|
|
35
35
|
this.mqContrast = null;
|
|
36
36
|
this.localeObserver = null;
|
|
37
37
|
this.localeOverride = null;
|
|
38
|
+
this.syncDocumentLocale = false;
|
|
38
39
|
this.destroyed = false;
|
|
39
40
|
this.isOpen = false;
|
|
40
41
|
this.modes = /* @__PURE__ */ new Set();
|
|
@@ -55,6 +56,7 @@ var A11yController = class {
|
|
|
55
56
|
};
|
|
56
57
|
this.doc = options.document ?? document;
|
|
57
58
|
this.storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
59
|
+
this.syncDocumentLocale = options.syncDocumentLocale === true;
|
|
58
60
|
if (options.locale && options.locale !== "auto") {
|
|
59
61
|
this.localeOverride = options.locale;
|
|
60
62
|
this.locale = options.locale;
|
|
@@ -181,10 +183,13 @@ var A11yController = class {
|
|
|
181
183
|
this.maskBand = Math.max(20, Math.min(160, this.maskBand + delta));
|
|
182
184
|
this.notify();
|
|
183
185
|
}
|
|
184
|
-
setMaskY(y) {
|
|
186
|
+
setMaskY(y, options = {}) {
|
|
185
187
|
const vh = this.doc.defaultView?.innerHeight ?? 800;
|
|
186
188
|
this.maskY = Math.max(0, Math.min(vh, y));
|
|
187
|
-
this.
|
|
189
|
+
this.doc.documentElement.style.setProperty("--a11y-mask-y", `${this.maskY}px`);
|
|
190
|
+
if (options.persist === false) return;
|
|
191
|
+
this.persist();
|
|
192
|
+
this.emit();
|
|
188
193
|
}
|
|
189
194
|
reset() {
|
|
190
195
|
this.modes = /* @__PURE__ */ new Set();
|
|
@@ -208,12 +213,14 @@ var A11yController = class {
|
|
|
208
213
|
}
|
|
209
214
|
toggleLocale() {
|
|
210
215
|
const next = this.locale === "ar" ? "en" : "ar";
|
|
211
|
-
const root = this.doc.documentElement;
|
|
212
|
-
root.lang = next;
|
|
213
|
-
root.dir = next === "ar" ? "rtl" : "ltr";
|
|
214
|
-
root.dataset["locale"] = next;
|
|
215
216
|
this.localeOverride = next;
|
|
216
217
|
this.locale = next;
|
|
218
|
+
if (this.syncDocumentLocale) {
|
|
219
|
+
const root = this.doc.documentElement;
|
|
220
|
+
root.lang = next;
|
|
221
|
+
root.dir = next === "ar" ? "rtl" : "ltr";
|
|
222
|
+
root.dataset["locale"] = next;
|
|
223
|
+
}
|
|
217
224
|
this.emit();
|
|
218
225
|
return next;
|
|
219
226
|
}
|
|
@@ -468,7 +475,8 @@ function createSkipLink(options = {}) {
|
|
|
468
475
|
position: absolute;
|
|
469
476
|
inset-inline-start: 1rem;
|
|
470
477
|
top: 1rem;
|
|
471
|
-
z-index:
|
|
478
|
+
z-index: 2147483647;
|
|
479
|
+
pointer-events: auto;
|
|
472
480
|
padding: 0.5rem 1rem;
|
|
473
481
|
border-radius: 0.375rem;
|
|
474
482
|
background: #fff;
|
|
@@ -653,7 +661,10 @@ function getCopy(locale) {
|
|
|
653
661
|
|
|
654
662
|
// src/icons.ts
|
|
655
663
|
var ICONS = {
|
|
656
|
-
|
|
664
|
+
/**
|
|
665
|
+
* GDS access icon (exact markup from the design system).
|
|
666
|
+
*/
|
|
667
|
+
access: '<path fill="currentColor" d="M16.853 8.265a.75.75 0 0 1 .294 1.47l-4.397.88v2.24l1.946 4.867a.75.75 0 1 1-1.392.556L12 15.02l-1.304 3.258a.75.75 0 1 1-1.392-.556l1.946-4.867v-2.24l-4.397-.88a.75.75 0 1 1 .294-1.47l4.853.97zM12 5.25a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5"/><path fill="currentColor" fill-rule="evenodd" d="M12 1.25c5.937 0 10.75 4.813 10.75 10.75S17.937 22.75 12 22.75 1.25 17.937 1.25 12 6.063 1.25 12 1.25m0 1.5a9.25 9.25 0 1 0 0 18.5 9.25 9.25 0 0 0 0-18.5" clip-rule="evenodd"/>',
|
|
657
668
|
close: '<path d="M6.7 6.7a1 1 0 0 1 1.4 0L12 10.6l3.9-3.9a1 1 0 1 1 1.4 1.4L13.4 12l3.9 3.9a1 1 0 0 1-1.4 1.4L12 13.4l-3.9 3.9a1 1 0 0 1-1.4-1.4L10.6 12 6.7 8.1a1 1 0 0 1 0-1.4Z"/>',
|
|
658
669
|
flash: '<path d="M13 2 4 14h7l-1 8 10-14h-7l1-6Z"/>',
|
|
659
670
|
eye: '<path d="M12 5c-5 0-9 4.5-10 7 1 2.5 5 7 10 7s9-4.5 10-7c-1-2.5-5-7-10-7Zm0 11a4 4 0 1 1 0-8 4 4 0 0 1 0 8Z"/>',
|
|
@@ -676,8 +687,31 @@ var ICONS = {
|
|
|
676
687
|
sun: '<path d="M12 4V2m0 20v-2m8-8h2M2 12h2m13.7 5.7 1.4 1.4M4.9 4.9l1.4 1.4m0 11.4-1.4 1.4M19.1 4.9l-1.4 1.4M12 8a4 4 0 1 1 0 8 4 4 0 0 1 0-8Z"/>',
|
|
677
688
|
colorPicker: '<path d="M12 3a7 7 0 0 1 0 14h-1l-4 4v-5a7 7 0 0 1 5-13Z"/>'
|
|
678
689
|
};
|
|
690
|
+
var STROKE_ICONS = /* @__PURE__ */ new Set([
|
|
691
|
+
"close",
|
|
692
|
+
"heading",
|
|
693
|
+
"linkAlt",
|
|
694
|
+
"pause",
|
|
695
|
+
"textFont",
|
|
696
|
+
"textAlign",
|
|
697
|
+
"textUnderline",
|
|
698
|
+
"letterSpacing",
|
|
699
|
+
"maximize",
|
|
700
|
+
"minus",
|
|
701
|
+
"plus",
|
|
702
|
+
"glasses",
|
|
703
|
+
"sun",
|
|
704
|
+
"touch",
|
|
705
|
+
"mask"
|
|
706
|
+
]);
|
|
679
707
|
function iconSvg(name, size = 18) {
|
|
680
708
|
const path = ICONS[name] ?? ICONS.access;
|
|
709
|
+
if (name === "access") {
|
|
710
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="a11y-icon" width="${size}" height="${size}" aria-hidden="true" focusable="false">${path}</svg>`;
|
|
711
|
+
}
|
|
712
|
+
if (STROKE_ICONS.has(name)) {
|
|
713
|
+
return `<svg class="a11y-icon" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">${path}</svg>`;
|
|
714
|
+
}
|
|
681
715
|
return `<svg class="a11y-icon" width="${size}" height="${size}" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false">${path}</svg>`;
|
|
682
716
|
}
|
|
683
717
|
|
|
@@ -714,14 +748,28 @@ var A11yWidget = class {
|
|
|
714
748
|
this.resetArmed = false;
|
|
715
749
|
this.resetTimer = null;
|
|
716
750
|
this.draggingMask = false;
|
|
751
|
+
this.wasOpen = false;
|
|
752
|
+
this.panelScrollTop = 0;
|
|
717
753
|
this.onKeyDown = (e) => this.handleKey(e);
|
|
718
754
|
this.onPointerMove = (e) => {
|
|
719
755
|
if (!this.draggingMask) return;
|
|
720
|
-
this.ctrl.setMaskY(e.clientY);
|
|
756
|
+
this.ctrl.setMaskY(e.clientY, { persist: false });
|
|
757
|
+
this.syncMaskDom();
|
|
721
758
|
};
|
|
722
759
|
this.onPointerUp = () => {
|
|
760
|
+
if (!this.draggingMask) return;
|
|
723
761
|
this.draggingMask = false;
|
|
762
|
+
this.ctrl.setMaskY(this.ctrl.maskY, { persist: true });
|
|
763
|
+
};
|
|
764
|
+
this.onRootClick = (e) => this.handleClick(e);
|
|
765
|
+
this.onRootPointerDown = (e) => {
|
|
766
|
+
const t = e.target?.closest?.('[data-a11y-action="mask-drag"]');
|
|
767
|
+
if (!t || !this.rootEl?.contains(t)) return;
|
|
768
|
+
e.preventDefault();
|
|
769
|
+
this.draggingMask = true;
|
|
724
770
|
};
|
|
771
|
+
this.topObserver = null;
|
|
772
|
+
this.topFrame = 0;
|
|
725
773
|
this.ctrl = ctrl;
|
|
726
774
|
this.position = options.position ?? "end";
|
|
727
775
|
this.stack = Math.max(0, options.stack ?? 0);
|
|
@@ -733,15 +781,24 @@ var A11yWidget = class {
|
|
|
733
781
|
this.rootEl.className = "a11y-root";
|
|
734
782
|
this.rootEl.setAttribute("data-a11y-ui", "");
|
|
735
783
|
this.mountRoot.appendChild(this.rootEl);
|
|
784
|
+
this.rootEl.addEventListener("click", this.onRootClick);
|
|
785
|
+
this.rootEl.addEventListener("pointerdown", this.onRootPointerDown);
|
|
736
786
|
this.unsub = this.ctrl.subscribe(() => this.render());
|
|
737
787
|
document.addEventListener("keydown", this.onKeyDown);
|
|
738
788
|
document.addEventListener("pointermove", this.onPointerMove);
|
|
739
789
|
document.addEventListener("pointerup", this.onPointerUp);
|
|
790
|
+
this.keepOnTop();
|
|
740
791
|
this.render();
|
|
741
792
|
}
|
|
742
793
|
destroy() {
|
|
743
794
|
this.unsub?.();
|
|
744
795
|
this.unsub = null;
|
|
796
|
+
this.topObserver?.disconnect();
|
|
797
|
+
this.topObserver = null;
|
|
798
|
+
if (this.topFrame) cancelAnimationFrame(this.topFrame);
|
|
799
|
+
this.topFrame = 0;
|
|
800
|
+
this.rootEl?.removeEventListener("click", this.onRootClick);
|
|
801
|
+
this.rootEl?.removeEventListener("pointerdown", this.onRootPointerDown);
|
|
745
802
|
document.removeEventListener("keydown", this.onKeyDown);
|
|
746
803
|
document.removeEventListener("pointermove", this.onPointerMove);
|
|
747
804
|
document.removeEventListener("pointerup", this.onPointerUp);
|
|
@@ -749,6 +806,25 @@ var A11yWidget = class {
|
|
|
749
806
|
this.rootEl?.remove();
|
|
750
807
|
this.rootEl = null;
|
|
751
808
|
}
|
|
809
|
+
/** Stay the last child so equal-z-index overlays appended later cannot cover us. */
|
|
810
|
+
keepOnTop() {
|
|
811
|
+
const parent = this.rootEl?.parentElement;
|
|
812
|
+
if (!parent || !this.rootEl) return;
|
|
813
|
+
const bump = () => {
|
|
814
|
+
if (!this.rootEl || this.rootEl.parentElement !== parent) return;
|
|
815
|
+
if (parent.lastElementChild === this.rootEl) return;
|
|
816
|
+
parent.appendChild(this.rootEl);
|
|
817
|
+
};
|
|
818
|
+
this.topObserver = new MutationObserver(() => {
|
|
819
|
+
if (this.topFrame) return;
|
|
820
|
+
this.topFrame = requestAnimationFrame(() => {
|
|
821
|
+
this.topFrame = 0;
|
|
822
|
+
bump();
|
|
823
|
+
});
|
|
824
|
+
});
|
|
825
|
+
this.topObserver.observe(parent, { childList: true });
|
|
826
|
+
bump();
|
|
827
|
+
}
|
|
752
828
|
copy() {
|
|
753
829
|
return getCopy(this.ctrl.locale);
|
|
754
830
|
}
|
|
@@ -814,7 +890,16 @@ var A11yWidget = class {
|
|
|
814
890
|
const chips = this.activeChips();
|
|
815
891
|
const badge = chips.length;
|
|
816
892
|
const open = this.ctrl.isOpen;
|
|
893
|
+
const opening = open && !this.wasOpen;
|
|
894
|
+
const stayingOpen = open && this.wasOpen;
|
|
817
895
|
const readingMask = this.ctrl.hasEffectiveMode("reading-mask");
|
|
896
|
+
const dir = this.ctrl.locale === "ar" ? "rtl" : "ltr";
|
|
897
|
+
if (stayingOpen) {
|
|
898
|
+
const body = this.rootEl.querySelector(".a11y-panel__body");
|
|
899
|
+
if (body) this.panelScrollTop = body.scrollTop;
|
|
900
|
+
}
|
|
901
|
+
this.rootEl.lang = this.ctrl.locale;
|
|
902
|
+
this.rootEl.dir = dir;
|
|
818
903
|
this.rootEl.innerHTML = `
|
|
819
904
|
<svg class="a11y-deuteranopia-svg" aria-hidden="true" focusable="false">
|
|
820
905
|
<filter id="a11y-deuteranopia">
|
|
@@ -840,23 +925,42 @@ var A11yWidget = class {
|
|
|
840
925
|
${badge > 0 ? `<span class="a11y-fab__badge" aria-hidden="true">${badge > 9 ? "9+" : badge}</span>` : ""}
|
|
841
926
|
</button>
|
|
842
927
|
|
|
843
|
-
${open ? this.renderPanel(c, chips) : ""}
|
|
928
|
+
${open ? this.renderPanel(c, chips, stayingOpen) : ""}
|
|
844
929
|
${readingMask ? this.renderMask(c) : ""}
|
|
845
930
|
`;
|
|
846
|
-
|
|
847
|
-
|
|
931
|
+
if (stayingOpen) {
|
|
932
|
+
const body = this.rootEl.querySelector(".a11y-panel__body");
|
|
933
|
+
if (body) body.scrollTop = this.panelScrollTop;
|
|
934
|
+
}
|
|
935
|
+
if (opening) {
|
|
848
936
|
const closeBtn = this.rootEl.querySelector('[data-a11y-action="close"]');
|
|
849
937
|
queueMicrotask(() => closeBtn?.focus());
|
|
850
938
|
}
|
|
939
|
+
this.wasOpen = open;
|
|
940
|
+
}
|
|
941
|
+
syncMaskDom() {
|
|
942
|
+
if (!this.rootEl) return;
|
|
943
|
+
const shades = this.rootEl.querySelectorAll(".a11y-mask-shade");
|
|
944
|
+
const toolbar = this.rootEl.querySelector(".a11y-mask-toolbar");
|
|
945
|
+
if (shades[0]) {
|
|
946
|
+
shades[0].style.height = "calc(var(--a11y-mask-y) - var(--a11y-mask-band))";
|
|
947
|
+
}
|
|
948
|
+
if (shades[1]) {
|
|
949
|
+
shades[1].style.top = "calc(var(--a11y-mask-y) + var(--a11y-mask-band))";
|
|
950
|
+
}
|
|
951
|
+
if (toolbar) {
|
|
952
|
+
toolbar.style.top = "calc(var(--a11y-mask-y) + var(--a11y-mask-band) + 8px)";
|
|
953
|
+
}
|
|
851
954
|
}
|
|
852
|
-
renderPanel(c, chips) {
|
|
955
|
+
renderPanel(c, chips, staticPanel) {
|
|
853
956
|
const profileCount = this.ctrl.activeModeCount("modes");
|
|
957
|
+
const panelClass = staticPanel ? `a11y-panel a11y-panel--${this.position} a11y-panel--static` : `a11y-panel a11y-panel--${this.position}`;
|
|
854
958
|
return `
|
|
855
959
|
<div class="a11y-layer" role="presentation">
|
|
856
960
|
<div class="a11y-backdrop" data-a11y-action="close" aria-hidden="true"></div>
|
|
857
961
|
<aside
|
|
858
962
|
id="${PANEL_ID}"
|
|
859
|
-
class="
|
|
963
|
+
class="${panelClass}"
|
|
860
964
|
role="dialog"
|
|
861
965
|
aria-modal="true"
|
|
862
966
|
aria-label="${escapeAttr(c.panelLabel)}"
|
|
@@ -1050,67 +1154,65 @@ var A11yWidget = class {
|
|
|
1050
1154
|
</button>
|
|
1051
1155
|
</div>`;
|
|
1052
1156
|
}
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
this.rootEl
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
this.rootEl.
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
});
|
|
1113
|
-
});
|
|
1157
|
+
handleClick(e) {
|
|
1158
|
+
const target = e.target;
|
|
1159
|
+
if (!target || !this.rootEl) return;
|
|
1160
|
+
const actionEl = target.closest("[data-a11y-action]");
|
|
1161
|
+
if (actionEl && this.rootEl.contains(actionEl)) {
|
|
1162
|
+
const action = actionEl.getAttribute("data-a11y-action");
|
|
1163
|
+
if (action === "toggle") {
|
|
1164
|
+
this.ctrl.toggle(actionEl);
|
|
1165
|
+
} else if (action === "close") {
|
|
1166
|
+
this.ctrl.close();
|
|
1167
|
+
} else if (action === "reset") {
|
|
1168
|
+
this.onReset();
|
|
1169
|
+
} else if (action === "mask-smaller") {
|
|
1170
|
+
this.ctrl.adjustMaskBand(-20);
|
|
1171
|
+
} else if (action === "mask-larger") {
|
|
1172
|
+
this.ctrl.adjustMaskBand(20);
|
|
1173
|
+
} else if (action === "mask-close") {
|
|
1174
|
+
this.ctrl.setMode("reading-mask", false);
|
|
1175
|
+
}
|
|
1176
|
+
return;
|
|
1177
|
+
}
|
|
1178
|
+
const bundleEl = target.closest("[data-a11y-bundle]");
|
|
1179
|
+
if (bundleEl && this.rootEl.contains(bundleEl)) {
|
|
1180
|
+
const id = bundleEl.getAttribute("data-a11y-bundle");
|
|
1181
|
+
this.ctrl.setMode(id, !this.ctrl.hasMode(id));
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
const toggleEl = target.closest("[data-a11y-toggle]");
|
|
1185
|
+
if (toggleEl && this.rootEl.contains(toggleEl)) {
|
|
1186
|
+
const mode = toggleEl.getAttribute("data-a11y-toggle");
|
|
1187
|
+
this.ctrl.setMode(mode, !this.ctrl.hasEffectiveMode(mode));
|
|
1188
|
+
return;
|
|
1189
|
+
}
|
|
1190
|
+
const filterEl = target.closest("[data-a11y-filter]");
|
|
1191
|
+
if (filterEl && this.rootEl.contains(filterEl)) {
|
|
1192
|
+
const filter = filterEl.getAttribute("data-a11y-filter");
|
|
1193
|
+
this.ctrl.setVisualFilter(filter);
|
|
1194
|
+
return;
|
|
1195
|
+
}
|
|
1196
|
+
const stepEl = target.closest("[data-a11y-step]");
|
|
1197
|
+
if (stepEl && this.rootEl.contains(stepEl)) {
|
|
1198
|
+
e.preventDefault();
|
|
1199
|
+
const id = stepEl.getAttribute("data-a11y-step");
|
|
1200
|
+
const delta = Number(stepEl.getAttribute("data-delta") || "1");
|
|
1201
|
+
if (id === "font") this.ctrl.stepFontStep(delta);
|
|
1202
|
+
else if (id === "align") this.ctrl.stepTextAlign(delta);
|
|
1203
|
+
else if (id === "line") this.ctrl.stepLineHeight(delta);
|
|
1204
|
+
else if (id === "letter") this.ctrl.stepLetterSpacing(delta);
|
|
1205
|
+
else if (id === "word") this.ctrl.stepWordSpacing(delta);
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
const focusEl = target.closest("[data-a11y-focus]");
|
|
1209
|
+
if (focusEl && this.rootEl.contains(focusEl)) {
|
|
1210
|
+
const id = focusEl.getAttribute("data-a11y-focus");
|
|
1211
|
+
const control = this.rootEl.querySelector(`[data-a11y-control="${id}"]`);
|
|
1212
|
+
control?.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
1213
|
+
const focusable = control?.querySelector("button, [tabindex]");
|
|
1214
|
+
focusable?.focus();
|
|
1215
|
+
}
|
|
1114
1216
|
}
|
|
1115
1217
|
onReset() {
|
|
1116
1218
|
if (!this.resetArmed) {
|
|
@@ -1182,7 +1284,8 @@ function mount(options = {}) {
|
|
|
1182
1284
|
ensureStyles();
|
|
1183
1285
|
const controller = new A11yController({
|
|
1184
1286
|
storageKey: options.storageKey,
|
|
1185
|
-
locale: options.locale
|
|
1287
|
+
locale: options.locale,
|
|
1288
|
+
syncDocumentLocale: options.syncDocumentLocale
|
|
1186
1289
|
});
|
|
1187
1290
|
let widget = null;
|
|
1188
1291
|
if (!options.headless) {
|
package/dist/index.d.cts
CHANGED
|
@@ -16,6 +16,11 @@ interface A11yMountOptions {
|
|
|
16
16
|
stack?: number;
|
|
17
17
|
/** UI locale. Default: `auto` (from `<html lang>` / `data-locale`). */
|
|
18
18
|
locale?: A11yLocale | 'auto';
|
|
19
|
+
/**
|
|
20
|
+
* When true, `toggleLocale()` also updates `<html lang>` / `dir`.
|
|
21
|
+
* Default: `false` (widget UI only — avoids flipping the host page).
|
|
22
|
+
*/
|
|
23
|
+
syncDocumentLocale?: boolean;
|
|
19
24
|
/** localStorage key. Default: `dga-a11y`. */
|
|
20
25
|
storageKey?: string;
|
|
21
26
|
/** Mount root. Default: `document.body`. */
|
|
@@ -48,6 +53,7 @@ declare class A11yController {
|
|
|
48
53
|
private mqContrast;
|
|
49
54
|
private localeObserver;
|
|
50
55
|
private localeOverride;
|
|
56
|
+
private syncDocumentLocale;
|
|
51
57
|
private destroyed;
|
|
52
58
|
isOpen: boolean;
|
|
53
59
|
modes: Set<A11yMode>;
|
|
@@ -63,6 +69,7 @@ declare class A11yController {
|
|
|
63
69
|
constructor(options?: {
|
|
64
70
|
storageKey?: string;
|
|
65
71
|
locale?: A11yLocale | 'auto';
|
|
72
|
+
syncDocumentLocale?: boolean;
|
|
66
73
|
document?: Document;
|
|
67
74
|
});
|
|
68
75
|
subscribe(listener: A11yListener): () => void;
|
|
@@ -84,7 +91,9 @@ declare class A11yController {
|
|
|
84
91
|
stepLetterSpacing(delta: number): void;
|
|
85
92
|
stepWordSpacing(delta: number): void;
|
|
86
93
|
adjustMaskBand(delta: number): void;
|
|
87
|
-
setMaskY(y: number
|
|
94
|
+
setMaskY(y: number, options?: {
|
|
95
|
+
persist?: boolean;
|
|
96
|
+
}): void;
|
|
88
97
|
reset(): void;
|
|
89
98
|
announce(msg: string): void;
|
|
90
99
|
toggleLocale(): A11yLocale;
|
|
@@ -126,20 +135,29 @@ declare class A11yWidget {
|
|
|
126
135
|
private resetArmed;
|
|
127
136
|
private resetTimer;
|
|
128
137
|
private draggingMask;
|
|
138
|
+
private wasOpen;
|
|
139
|
+
private panelScrollTop;
|
|
129
140
|
private onKeyDown;
|
|
130
141
|
private onPointerMove;
|
|
131
142
|
private onPointerUp;
|
|
143
|
+
private onRootClick;
|
|
144
|
+
private onRootPointerDown;
|
|
145
|
+
private topObserver;
|
|
146
|
+
private topFrame;
|
|
132
147
|
constructor(ctrl: A11yController, options?: A11yWidgetOptions);
|
|
133
148
|
mount(): void;
|
|
134
149
|
destroy(): void;
|
|
150
|
+
/** Stay the last child so equal-z-index overlays appended later cannot cover us. */
|
|
151
|
+
private keepOnTop;
|
|
135
152
|
private copy;
|
|
136
153
|
private fabBottom;
|
|
137
154
|
private activeChips;
|
|
138
155
|
private render;
|
|
156
|
+
private syncMaskDom;
|
|
139
157
|
private renderPanel;
|
|
140
158
|
private renderSteppers;
|
|
141
159
|
private renderMask;
|
|
142
|
-
private
|
|
160
|
+
private handleClick;
|
|
143
161
|
private onReset;
|
|
144
162
|
private handleKey;
|
|
145
163
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -16,6 +16,11 @@ interface A11yMountOptions {
|
|
|
16
16
|
stack?: number;
|
|
17
17
|
/** UI locale. Default: `auto` (from `<html lang>` / `data-locale`). */
|
|
18
18
|
locale?: A11yLocale | 'auto';
|
|
19
|
+
/**
|
|
20
|
+
* When true, `toggleLocale()` also updates `<html lang>` / `dir`.
|
|
21
|
+
* Default: `false` (widget UI only — avoids flipping the host page).
|
|
22
|
+
*/
|
|
23
|
+
syncDocumentLocale?: boolean;
|
|
19
24
|
/** localStorage key. Default: `dga-a11y`. */
|
|
20
25
|
storageKey?: string;
|
|
21
26
|
/** Mount root. Default: `document.body`. */
|
|
@@ -48,6 +53,7 @@ declare class A11yController {
|
|
|
48
53
|
private mqContrast;
|
|
49
54
|
private localeObserver;
|
|
50
55
|
private localeOverride;
|
|
56
|
+
private syncDocumentLocale;
|
|
51
57
|
private destroyed;
|
|
52
58
|
isOpen: boolean;
|
|
53
59
|
modes: Set<A11yMode>;
|
|
@@ -63,6 +69,7 @@ declare class A11yController {
|
|
|
63
69
|
constructor(options?: {
|
|
64
70
|
storageKey?: string;
|
|
65
71
|
locale?: A11yLocale | 'auto';
|
|
72
|
+
syncDocumentLocale?: boolean;
|
|
66
73
|
document?: Document;
|
|
67
74
|
});
|
|
68
75
|
subscribe(listener: A11yListener): () => void;
|
|
@@ -84,7 +91,9 @@ declare class A11yController {
|
|
|
84
91
|
stepLetterSpacing(delta: number): void;
|
|
85
92
|
stepWordSpacing(delta: number): void;
|
|
86
93
|
adjustMaskBand(delta: number): void;
|
|
87
|
-
setMaskY(y: number
|
|
94
|
+
setMaskY(y: number, options?: {
|
|
95
|
+
persist?: boolean;
|
|
96
|
+
}): void;
|
|
88
97
|
reset(): void;
|
|
89
98
|
announce(msg: string): void;
|
|
90
99
|
toggleLocale(): A11yLocale;
|
|
@@ -126,20 +135,29 @@ declare class A11yWidget {
|
|
|
126
135
|
private resetArmed;
|
|
127
136
|
private resetTimer;
|
|
128
137
|
private draggingMask;
|
|
138
|
+
private wasOpen;
|
|
139
|
+
private panelScrollTop;
|
|
129
140
|
private onKeyDown;
|
|
130
141
|
private onPointerMove;
|
|
131
142
|
private onPointerUp;
|
|
143
|
+
private onRootClick;
|
|
144
|
+
private onRootPointerDown;
|
|
145
|
+
private topObserver;
|
|
146
|
+
private topFrame;
|
|
132
147
|
constructor(ctrl: A11yController, options?: A11yWidgetOptions);
|
|
133
148
|
mount(): void;
|
|
134
149
|
destroy(): void;
|
|
150
|
+
/** Stay the last child so equal-z-index overlays appended later cannot cover us. */
|
|
151
|
+
private keepOnTop;
|
|
135
152
|
private copy;
|
|
136
153
|
private fabBottom;
|
|
137
154
|
private activeChips;
|
|
138
155
|
private render;
|
|
156
|
+
private syncMaskDom;
|
|
139
157
|
private renderPanel;
|
|
140
158
|
private renderSteppers;
|
|
141
159
|
private renderMask;
|
|
142
|
-
private
|
|
160
|
+
private handleClick;
|
|
143
161
|
private onReset;
|
|
144
162
|
private handleKey;
|
|
145
163
|
}
|
package/dist/index.js
CHANGED
|
@@ -33,6 +33,7 @@ var A11yController = class {
|
|
|
33
33
|
this.mqContrast = null;
|
|
34
34
|
this.localeObserver = null;
|
|
35
35
|
this.localeOverride = null;
|
|
36
|
+
this.syncDocumentLocale = false;
|
|
36
37
|
this.destroyed = false;
|
|
37
38
|
this.isOpen = false;
|
|
38
39
|
this.modes = /* @__PURE__ */ new Set();
|
|
@@ -53,6 +54,7 @@ var A11yController = class {
|
|
|
53
54
|
};
|
|
54
55
|
this.doc = options.document ?? document;
|
|
55
56
|
this.storageKey = options.storageKey ?? DEFAULT_STORAGE_KEY;
|
|
57
|
+
this.syncDocumentLocale = options.syncDocumentLocale === true;
|
|
56
58
|
if (options.locale && options.locale !== "auto") {
|
|
57
59
|
this.localeOverride = options.locale;
|
|
58
60
|
this.locale = options.locale;
|
|
@@ -179,10 +181,13 @@ var A11yController = class {
|
|
|
179
181
|
this.maskBand = Math.max(20, Math.min(160, this.maskBand + delta));
|
|
180
182
|
this.notify();
|
|
181
183
|
}
|
|
182
|
-
setMaskY(y) {
|
|
184
|
+
setMaskY(y, options = {}) {
|
|
183
185
|
const vh = this.doc.defaultView?.innerHeight ?? 800;
|
|
184
186
|
this.maskY = Math.max(0, Math.min(vh, y));
|
|
185
|
-
this.
|
|
187
|
+
this.doc.documentElement.style.setProperty("--a11y-mask-y", `${this.maskY}px`);
|
|
188
|
+
if (options.persist === false) return;
|
|
189
|
+
this.persist();
|
|
190
|
+
this.emit();
|
|
186
191
|
}
|
|
187
192
|
reset() {
|
|
188
193
|
this.modes = /* @__PURE__ */ new Set();
|
|
@@ -206,12 +211,14 @@ var A11yController = class {
|
|
|
206
211
|
}
|
|
207
212
|
toggleLocale() {
|
|
208
213
|
const next = this.locale === "ar" ? "en" : "ar";
|
|
209
|
-
const root = this.doc.documentElement;
|
|
210
|
-
root.lang = next;
|
|
211
|
-
root.dir = next === "ar" ? "rtl" : "ltr";
|
|
212
|
-
root.dataset["locale"] = next;
|
|
213
214
|
this.localeOverride = next;
|
|
214
215
|
this.locale = next;
|
|
216
|
+
if (this.syncDocumentLocale) {
|
|
217
|
+
const root = this.doc.documentElement;
|
|
218
|
+
root.lang = next;
|
|
219
|
+
root.dir = next === "ar" ? "rtl" : "ltr";
|
|
220
|
+
root.dataset["locale"] = next;
|
|
221
|
+
}
|
|
215
222
|
this.emit();
|
|
216
223
|
return next;
|
|
217
224
|
}
|
|
@@ -466,7 +473,8 @@ function createSkipLink(options = {}) {
|
|
|
466
473
|
position: absolute;
|
|
467
474
|
inset-inline-start: 1rem;
|
|
468
475
|
top: 1rem;
|
|
469
|
-
z-index:
|
|
476
|
+
z-index: 2147483647;
|
|
477
|
+
pointer-events: auto;
|
|
470
478
|
padding: 0.5rem 1rem;
|
|
471
479
|
border-radius: 0.375rem;
|
|
472
480
|
background: #fff;
|
|
@@ -651,7 +659,10 @@ function getCopy(locale) {
|
|
|
651
659
|
|
|
652
660
|
// src/icons.ts
|
|
653
661
|
var ICONS = {
|
|
654
|
-
|
|
662
|
+
/**
|
|
663
|
+
* GDS access icon (exact markup from the design system).
|
|
664
|
+
*/
|
|
665
|
+
access: '<path fill="currentColor" d="M16.853 8.265a.75.75 0 0 1 .294 1.47l-4.397.88v2.24l1.946 4.867a.75.75 0 1 1-1.392.556L12 15.02l-1.304 3.258a.75.75 0 1 1-1.392-.556l1.946-4.867v-2.24l-4.397-.88a.75.75 0 1 1 .294-1.47l4.853.97zM12 5.25a1.25 1.25 0 1 1 0 2.5 1.25 1.25 0 0 1 0-2.5"/><path fill="currentColor" fill-rule="evenodd" d="M12 1.25c5.937 0 10.75 4.813 10.75 10.75S17.937 22.75 12 22.75 1.25 17.937 1.25 12 6.063 1.25 12 1.25m0 1.5a9.25 9.25 0 1 0 0 18.5 9.25 9.25 0 0 0 0-18.5" clip-rule="evenodd"/>',
|
|
655
666
|
close: '<path d="M6.7 6.7a1 1 0 0 1 1.4 0L12 10.6l3.9-3.9a1 1 0 1 1 1.4 1.4L13.4 12l3.9 3.9a1 1 0 0 1-1.4 1.4L12 13.4l-3.9 3.9a1 1 0 0 1-1.4-1.4L10.6 12 6.7 8.1a1 1 0 0 1 0-1.4Z"/>',
|
|
656
667
|
flash: '<path d="M13 2 4 14h7l-1 8 10-14h-7l1-6Z"/>',
|
|
657
668
|
eye: '<path d="M12 5c-5 0-9 4.5-10 7 1 2.5 5 7 10 7s9-4.5 10-7c-1-2.5-5-7-10-7Zm0 11a4 4 0 1 1 0-8 4 4 0 0 1 0 8Z"/>',
|
|
@@ -674,8 +685,31 @@ var ICONS = {
|
|
|
674
685
|
sun: '<path d="M12 4V2m0 20v-2m8-8h2M2 12h2m13.7 5.7 1.4 1.4M4.9 4.9l1.4 1.4m0 11.4-1.4 1.4M19.1 4.9l-1.4 1.4M12 8a4 4 0 1 1 0 8 4 4 0 0 1 0-8Z"/>',
|
|
675
686
|
colorPicker: '<path d="M12 3a7 7 0 0 1 0 14h-1l-4 4v-5a7 7 0 0 1 5-13Z"/>'
|
|
676
687
|
};
|
|
688
|
+
var STROKE_ICONS = /* @__PURE__ */ new Set([
|
|
689
|
+
"close",
|
|
690
|
+
"heading",
|
|
691
|
+
"linkAlt",
|
|
692
|
+
"pause",
|
|
693
|
+
"textFont",
|
|
694
|
+
"textAlign",
|
|
695
|
+
"textUnderline",
|
|
696
|
+
"letterSpacing",
|
|
697
|
+
"maximize",
|
|
698
|
+
"minus",
|
|
699
|
+
"plus",
|
|
700
|
+
"glasses",
|
|
701
|
+
"sun",
|
|
702
|
+
"touch",
|
|
703
|
+
"mask"
|
|
704
|
+
]);
|
|
677
705
|
function iconSvg(name, size = 18) {
|
|
678
706
|
const path = ICONS[name] ?? ICONS.access;
|
|
707
|
+
if (name === "access") {
|
|
708
|
+
return `<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="a11y-icon" width="${size}" height="${size}" aria-hidden="true" focusable="false">${path}</svg>`;
|
|
709
|
+
}
|
|
710
|
+
if (STROKE_ICONS.has(name)) {
|
|
711
|
+
return `<svg class="a11y-icon" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.25" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true" focusable="false">${path}</svg>`;
|
|
712
|
+
}
|
|
679
713
|
return `<svg class="a11y-icon" width="${size}" height="${size}" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true" focusable="false">${path}</svg>`;
|
|
680
714
|
}
|
|
681
715
|
|
|
@@ -712,14 +746,28 @@ var A11yWidget = class {
|
|
|
712
746
|
this.resetArmed = false;
|
|
713
747
|
this.resetTimer = null;
|
|
714
748
|
this.draggingMask = false;
|
|
749
|
+
this.wasOpen = false;
|
|
750
|
+
this.panelScrollTop = 0;
|
|
715
751
|
this.onKeyDown = (e) => this.handleKey(e);
|
|
716
752
|
this.onPointerMove = (e) => {
|
|
717
753
|
if (!this.draggingMask) return;
|
|
718
|
-
this.ctrl.setMaskY(e.clientY);
|
|
754
|
+
this.ctrl.setMaskY(e.clientY, { persist: false });
|
|
755
|
+
this.syncMaskDom();
|
|
719
756
|
};
|
|
720
757
|
this.onPointerUp = () => {
|
|
758
|
+
if (!this.draggingMask) return;
|
|
721
759
|
this.draggingMask = false;
|
|
760
|
+
this.ctrl.setMaskY(this.ctrl.maskY, { persist: true });
|
|
761
|
+
};
|
|
762
|
+
this.onRootClick = (e) => this.handleClick(e);
|
|
763
|
+
this.onRootPointerDown = (e) => {
|
|
764
|
+
const t = e.target?.closest?.('[data-a11y-action="mask-drag"]');
|
|
765
|
+
if (!t || !this.rootEl?.contains(t)) return;
|
|
766
|
+
e.preventDefault();
|
|
767
|
+
this.draggingMask = true;
|
|
722
768
|
};
|
|
769
|
+
this.topObserver = null;
|
|
770
|
+
this.topFrame = 0;
|
|
723
771
|
this.ctrl = ctrl;
|
|
724
772
|
this.position = options.position ?? "end";
|
|
725
773
|
this.stack = Math.max(0, options.stack ?? 0);
|
|
@@ -731,15 +779,24 @@ var A11yWidget = class {
|
|
|
731
779
|
this.rootEl.className = "a11y-root";
|
|
732
780
|
this.rootEl.setAttribute("data-a11y-ui", "");
|
|
733
781
|
this.mountRoot.appendChild(this.rootEl);
|
|
782
|
+
this.rootEl.addEventListener("click", this.onRootClick);
|
|
783
|
+
this.rootEl.addEventListener("pointerdown", this.onRootPointerDown);
|
|
734
784
|
this.unsub = this.ctrl.subscribe(() => this.render());
|
|
735
785
|
document.addEventListener("keydown", this.onKeyDown);
|
|
736
786
|
document.addEventListener("pointermove", this.onPointerMove);
|
|
737
787
|
document.addEventListener("pointerup", this.onPointerUp);
|
|
788
|
+
this.keepOnTop();
|
|
738
789
|
this.render();
|
|
739
790
|
}
|
|
740
791
|
destroy() {
|
|
741
792
|
this.unsub?.();
|
|
742
793
|
this.unsub = null;
|
|
794
|
+
this.topObserver?.disconnect();
|
|
795
|
+
this.topObserver = null;
|
|
796
|
+
if (this.topFrame) cancelAnimationFrame(this.topFrame);
|
|
797
|
+
this.topFrame = 0;
|
|
798
|
+
this.rootEl?.removeEventListener("click", this.onRootClick);
|
|
799
|
+
this.rootEl?.removeEventListener("pointerdown", this.onRootPointerDown);
|
|
743
800
|
document.removeEventListener("keydown", this.onKeyDown);
|
|
744
801
|
document.removeEventListener("pointermove", this.onPointerMove);
|
|
745
802
|
document.removeEventListener("pointerup", this.onPointerUp);
|
|
@@ -747,6 +804,25 @@ var A11yWidget = class {
|
|
|
747
804
|
this.rootEl?.remove();
|
|
748
805
|
this.rootEl = null;
|
|
749
806
|
}
|
|
807
|
+
/** Stay the last child so equal-z-index overlays appended later cannot cover us. */
|
|
808
|
+
keepOnTop() {
|
|
809
|
+
const parent = this.rootEl?.parentElement;
|
|
810
|
+
if (!parent || !this.rootEl) return;
|
|
811
|
+
const bump = () => {
|
|
812
|
+
if (!this.rootEl || this.rootEl.parentElement !== parent) return;
|
|
813
|
+
if (parent.lastElementChild === this.rootEl) return;
|
|
814
|
+
parent.appendChild(this.rootEl);
|
|
815
|
+
};
|
|
816
|
+
this.topObserver = new MutationObserver(() => {
|
|
817
|
+
if (this.topFrame) return;
|
|
818
|
+
this.topFrame = requestAnimationFrame(() => {
|
|
819
|
+
this.topFrame = 0;
|
|
820
|
+
bump();
|
|
821
|
+
});
|
|
822
|
+
});
|
|
823
|
+
this.topObserver.observe(parent, { childList: true });
|
|
824
|
+
bump();
|
|
825
|
+
}
|
|
750
826
|
copy() {
|
|
751
827
|
return getCopy(this.ctrl.locale);
|
|
752
828
|
}
|
|
@@ -812,7 +888,16 @@ var A11yWidget = class {
|
|
|
812
888
|
const chips = this.activeChips();
|
|
813
889
|
const badge = chips.length;
|
|
814
890
|
const open = this.ctrl.isOpen;
|
|
891
|
+
const opening = open && !this.wasOpen;
|
|
892
|
+
const stayingOpen = open && this.wasOpen;
|
|
815
893
|
const readingMask = this.ctrl.hasEffectiveMode("reading-mask");
|
|
894
|
+
const dir = this.ctrl.locale === "ar" ? "rtl" : "ltr";
|
|
895
|
+
if (stayingOpen) {
|
|
896
|
+
const body = this.rootEl.querySelector(".a11y-panel__body");
|
|
897
|
+
if (body) this.panelScrollTop = body.scrollTop;
|
|
898
|
+
}
|
|
899
|
+
this.rootEl.lang = this.ctrl.locale;
|
|
900
|
+
this.rootEl.dir = dir;
|
|
816
901
|
this.rootEl.innerHTML = `
|
|
817
902
|
<svg class="a11y-deuteranopia-svg" aria-hidden="true" focusable="false">
|
|
818
903
|
<filter id="a11y-deuteranopia">
|
|
@@ -838,23 +923,42 @@ var A11yWidget = class {
|
|
|
838
923
|
${badge > 0 ? `<span class="a11y-fab__badge" aria-hidden="true">${badge > 9 ? "9+" : badge}</span>` : ""}
|
|
839
924
|
</button>
|
|
840
925
|
|
|
841
|
-
${open ? this.renderPanel(c, chips) : ""}
|
|
926
|
+
${open ? this.renderPanel(c, chips, stayingOpen) : ""}
|
|
842
927
|
${readingMask ? this.renderMask(c) : ""}
|
|
843
928
|
`;
|
|
844
|
-
|
|
845
|
-
|
|
929
|
+
if (stayingOpen) {
|
|
930
|
+
const body = this.rootEl.querySelector(".a11y-panel__body");
|
|
931
|
+
if (body) body.scrollTop = this.panelScrollTop;
|
|
932
|
+
}
|
|
933
|
+
if (opening) {
|
|
846
934
|
const closeBtn = this.rootEl.querySelector('[data-a11y-action="close"]');
|
|
847
935
|
queueMicrotask(() => closeBtn?.focus());
|
|
848
936
|
}
|
|
937
|
+
this.wasOpen = open;
|
|
938
|
+
}
|
|
939
|
+
syncMaskDom() {
|
|
940
|
+
if (!this.rootEl) return;
|
|
941
|
+
const shades = this.rootEl.querySelectorAll(".a11y-mask-shade");
|
|
942
|
+
const toolbar = this.rootEl.querySelector(".a11y-mask-toolbar");
|
|
943
|
+
if (shades[0]) {
|
|
944
|
+
shades[0].style.height = "calc(var(--a11y-mask-y) - var(--a11y-mask-band))";
|
|
945
|
+
}
|
|
946
|
+
if (shades[1]) {
|
|
947
|
+
shades[1].style.top = "calc(var(--a11y-mask-y) + var(--a11y-mask-band))";
|
|
948
|
+
}
|
|
949
|
+
if (toolbar) {
|
|
950
|
+
toolbar.style.top = "calc(var(--a11y-mask-y) + var(--a11y-mask-band) + 8px)";
|
|
951
|
+
}
|
|
849
952
|
}
|
|
850
|
-
renderPanel(c, chips) {
|
|
953
|
+
renderPanel(c, chips, staticPanel) {
|
|
851
954
|
const profileCount = this.ctrl.activeModeCount("modes");
|
|
955
|
+
const panelClass = staticPanel ? `a11y-panel a11y-panel--${this.position} a11y-panel--static` : `a11y-panel a11y-panel--${this.position}`;
|
|
852
956
|
return `
|
|
853
957
|
<div class="a11y-layer" role="presentation">
|
|
854
958
|
<div class="a11y-backdrop" data-a11y-action="close" aria-hidden="true"></div>
|
|
855
959
|
<aside
|
|
856
960
|
id="${PANEL_ID}"
|
|
857
|
-
class="
|
|
961
|
+
class="${panelClass}"
|
|
858
962
|
role="dialog"
|
|
859
963
|
aria-modal="true"
|
|
860
964
|
aria-label="${escapeAttr(c.panelLabel)}"
|
|
@@ -1048,67 +1152,65 @@ var A11yWidget = class {
|
|
|
1048
1152
|
</button>
|
|
1049
1153
|
</div>`;
|
|
1050
1154
|
}
|
|
1051
|
-
|
|
1052
|
-
|
|
1053
|
-
this.rootEl
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
this.rootEl.
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
});
|
|
1111
|
-
});
|
|
1155
|
+
handleClick(e) {
|
|
1156
|
+
const target = e.target;
|
|
1157
|
+
if (!target || !this.rootEl) return;
|
|
1158
|
+
const actionEl = target.closest("[data-a11y-action]");
|
|
1159
|
+
if (actionEl && this.rootEl.contains(actionEl)) {
|
|
1160
|
+
const action = actionEl.getAttribute("data-a11y-action");
|
|
1161
|
+
if (action === "toggle") {
|
|
1162
|
+
this.ctrl.toggle(actionEl);
|
|
1163
|
+
} else if (action === "close") {
|
|
1164
|
+
this.ctrl.close();
|
|
1165
|
+
} else if (action === "reset") {
|
|
1166
|
+
this.onReset();
|
|
1167
|
+
} else if (action === "mask-smaller") {
|
|
1168
|
+
this.ctrl.adjustMaskBand(-20);
|
|
1169
|
+
} else if (action === "mask-larger") {
|
|
1170
|
+
this.ctrl.adjustMaskBand(20);
|
|
1171
|
+
} else if (action === "mask-close") {
|
|
1172
|
+
this.ctrl.setMode("reading-mask", false);
|
|
1173
|
+
}
|
|
1174
|
+
return;
|
|
1175
|
+
}
|
|
1176
|
+
const bundleEl = target.closest("[data-a11y-bundle]");
|
|
1177
|
+
if (bundleEl && this.rootEl.contains(bundleEl)) {
|
|
1178
|
+
const id = bundleEl.getAttribute("data-a11y-bundle");
|
|
1179
|
+
this.ctrl.setMode(id, !this.ctrl.hasMode(id));
|
|
1180
|
+
return;
|
|
1181
|
+
}
|
|
1182
|
+
const toggleEl = target.closest("[data-a11y-toggle]");
|
|
1183
|
+
if (toggleEl && this.rootEl.contains(toggleEl)) {
|
|
1184
|
+
const mode = toggleEl.getAttribute("data-a11y-toggle");
|
|
1185
|
+
this.ctrl.setMode(mode, !this.ctrl.hasEffectiveMode(mode));
|
|
1186
|
+
return;
|
|
1187
|
+
}
|
|
1188
|
+
const filterEl = target.closest("[data-a11y-filter]");
|
|
1189
|
+
if (filterEl && this.rootEl.contains(filterEl)) {
|
|
1190
|
+
const filter = filterEl.getAttribute("data-a11y-filter");
|
|
1191
|
+
this.ctrl.setVisualFilter(filter);
|
|
1192
|
+
return;
|
|
1193
|
+
}
|
|
1194
|
+
const stepEl = target.closest("[data-a11y-step]");
|
|
1195
|
+
if (stepEl && this.rootEl.contains(stepEl)) {
|
|
1196
|
+
e.preventDefault();
|
|
1197
|
+
const id = stepEl.getAttribute("data-a11y-step");
|
|
1198
|
+
const delta = Number(stepEl.getAttribute("data-delta") || "1");
|
|
1199
|
+
if (id === "font") this.ctrl.stepFontStep(delta);
|
|
1200
|
+
else if (id === "align") this.ctrl.stepTextAlign(delta);
|
|
1201
|
+
else if (id === "line") this.ctrl.stepLineHeight(delta);
|
|
1202
|
+
else if (id === "letter") this.ctrl.stepLetterSpacing(delta);
|
|
1203
|
+
else if (id === "word") this.ctrl.stepWordSpacing(delta);
|
|
1204
|
+
return;
|
|
1205
|
+
}
|
|
1206
|
+
const focusEl = target.closest("[data-a11y-focus]");
|
|
1207
|
+
if (focusEl && this.rootEl.contains(focusEl)) {
|
|
1208
|
+
const id = focusEl.getAttribute("data-a11y-focus");
|
|
1209
|
+
const control = this.rootEl.querySelector(`[data-a11y-control="${id}"]`);
|
|
1210
|
+
control?.scrollIntoView({ block: "nearest", behavior: "smooth" });
|
|
1211
|
+
const focusable = control?.querySelector("button, [tabindex]");
|
|
1212
|
+
focusable?.focus();
|
|
1213
|
+
}
|
|
1112
1214
|
}
|
|
1113
1215
|
onReset() {
|
|
1114
1216
|
if (!this.resetArmed) {
|
|
@@ -1180,7 +1282,8 @@ function mount(options = {}) {
|
|
|
1180
1282
|
ensureStyles();
|
|
1181
1283
|
const controller = new A11yController({
|
|
1182
1284
|
storageKey: options.storageKey,
|
|
1183
|
-
locale: options.locale
|
|
1285
|
+
locale: options.locale,
|
|
1286
|
+
syncDocumentLocale: options.syncDocumentLocale
|
|
1184
1287
|
});
|
|
1185
1288
|
let widget = null;
|
|
1186
1289
|
if (!options.headless) {
|
package/dist/styles.css
CHANGED
|
@@ -12,11 +12,93 @@
|
|
|
12
12
|
border: 0 !important;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
/* Font scale
|
|
15
|
+
/* Font scale — rem-based layouts follow html font-size */
|
|
16
16
|
html {
|
|
17
17
|
font-size: calc(100% * var(--user-font-scale, var(--dga-font-scale, 1)));
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
/* Make spacing / align apply broadly so host pages notice the change */
|
|
21
|
+
[data-a11y~='has-line-height'] body,
|
|
22
|
+
[data-a11y~='has-line-height'] p,
|
|
23
|
+
[data-a11y~='has-line-height'] li,
|
|
24
|
+
[data-a11y~='has-line-height'] h1,
|
|
25
|
+
[data-a11y~='has-line-height'] h2,
|
|
26
|
+
[data-a11y~='has-line-height'] h3,
|
|
27
|
+
[data-a11y~='has-line-height'] h4,
|
|
28
|
+
[data-a11y~='has-line-height'] h5,
|
|
29
|
+
[data-a11y~='has-line-height'] h6,
|
|
30
|
+
[data-a11y~='has-line-height'] td,
|
|
31
|
+
[data-a11y~='has-line-height'] th,
|
|
32
|
+
[data-a11y~='has-line-height'] label,
|
|
33
|
+
[data-a11y~='has-line-height'] .lede,
|
|
34
|
+
[data-a11y~='has-line-height'] .section-lede {
|
|
35
|
+
line-height: var(--user-line-height, 1.6) !important;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
[data-a11y~='has-letter-spacing'] body,
|
|
39
|
+
[data-a11y~='has-letter-spacing'] p,
|
|
40
|
+
[data-a11y~='has-letter-spacing'] li,
|
|
41
|
+
[data-a11y~='has-letter-spacing'] h1,
|
|
42
|
+
[data-a11y~='has-letter-spacing'] h2,
|
|
43
|
+
[data-a11y~='has-letter-spacing'] h3,
|
|
44
|
+
[data-a11y~='has-letter-spacing'] h4,
|
|
45
|
+
[data-a11y~='has-letter-spacing'] h5,
|
|
46
|
+
[data-a11y~='has-letter-spacing'] h6,
|
|
47
|
+
[data-a11y~='has-letter-spacing'] label {
|
|
48
|
+
letter-spacing: var(--user-letter-spacing, 0) !important;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
[data-a11y~='has-word-spacing'] body,
|
|
52
|
+
[data-a11y~='has-word-spacing'] p,
|
|
53
|
+
[data-a11y~='has-word-spacing'] li,
|
|
54
|
+
[data-a11y~='has-word-spacing'] h1,
|
|
55
|
+
[data-a11y~='has-word-spacing'] h2,
|
|
56
|
+
[data-a11y~='has-word-spacing'] h3,
|
|
57
|
+
[data-a11y~='has-word-spacing'] h4,
|
|
58
|
+
[data-a11y~='has-word-spacing'] h5,
|
|
59
|
+
[data-a11y~='has-word-spacing'] h6 {
|
|
60
|
+
word-spacing: var(--user-word-spacing, 0) !important;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
[data-a11y~='text-align-start'] body,
|
|
64
|
+
[data-a11y~='text-align-start'] main,
|
|
65
|
+
[data-a11y~='text-align-start'] article,
|
|
66
|
+
[data-a11y~='text-align-start'] section,
|
|
67
|
+
[data-a11y~='text-align-start'] p,
|
|
68
|
+
[data-a11y~='text-align-start'] li,
|
|
69
|
+
[data-a11y~='text-align-start'] h1,
|
|
70
|
+
[data-a11y~='text-align-start'] h2,
|
|
71
|
+
[data-a11y~='text-align-start'] h3,
|
|
72
|
+
[data-a11y~='text-align-start'] h4,
|
|
73
|
+
[data-a11y~='text-align-start'] h5,
|
|
74
|
+
[data-a11y~='text-align-start'] h6 {
|
|
75
|
+
text-align: start !important;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
[data-a11y~='text-align-end'] body,
|
|
79
|
+
[data-a11y~='text-align-end'] main,
|
|
80
|
+
[data-a11y~='text-align-end'] article,
|
|
81
|
+
[data-a11y~='text-align-end'] section,
|
|
82
|
+
[data-a11y~='text-align-end'] p,
|
|
83
|
+
[data-a11y~='text-align-end'] li,
|
|
84
|
+
[data-a11y~='text-align-end'] h1,
|
|
85
|
+
[data-a11y~='text-align-end'] h2,
|
|
86
|
+
[data-a11y~='text-align-end'] h3,
|
|
87
|
+
[data-a11y~='text-align-end'] h4,
|
|
88
|
+
[data-a11y~='text-align-end'] h5,
|
|
89
|
+
[data-a11y~='text-align-end'] h6 {
|
|
90
|
+
text-align: end !important;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
[data-a11y~='text-align-justify'] body,
|
|
94
|
+
[data-a11y~='text-align-justify'] main,
|
|
95
|
+
[data-a11y~='text-align-justify'] article,
|
|
96
|
+
[data-a11y~='text-align-justify'] section,
|
|
97
|
+
[data-a11y~='text-align-justify'] p,
|
|
98
|
+
[data-a11y~='text-align-justify'] li {
|
|
99
|
+
text-align: justify !important;
|
|
100
|
+
}
|
|
101
|
+
|
|
20
102
|
/* ——— Document effects ——— */
|
|
21
103
|
|
|
22
104
|
[data-a11y~='reduce-motion'] *,
|
|
@@ -152,42 +234,6 @@ html {
|
|
|
152
234
|
outline-offset: 4px !important;
|
|
153
235
|
}
|
|
154
236
|
|
|
155
|
-
[data-a11y~='has-line-height'] body,
|
|
156
|
-
[data-a11y~='has-line-height'] p,
|
|
157
|
-
[data-a11y~='has-line-height'] li {
|
|
158
|
-
line-height: var(--user-line-height, 1.6) !important;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
[data-a11y~='has-letter-spacing'] body,
|
|
162
|
-
[data-a11y~='has-letter-spacing'] p,
|
|
163
|
-
[data-a11y~='has-letter-spacing'] li {
|
|
164
|
-
letter-spacing: var(--user-letter-spacing, 0) !important;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
[data-a11y~='has-word-spacing'] body,
|
|
168
|
-
[data-a11y~='has-word-spacing'] p,
|
|
169
|
-
[data-a11y~='has-word-spacing'] li {
|
|
170
|
-
word-spacing: var(--user-word-spacing, 0) !important;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
[data-a11y~='text-align-start'] body,
|
|
174
|
-
[data-a11y~='text-align-start'] p,
|
|
175
|
-
[data-a11y~='text-align-start'] li {
|
|
176
|
-
text-align: start !important;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
[data-a11y~='text-align-end'] body,
|
|
180
|
-
[data-a11y~='text-align-end'] p,
|
|
181
|
-
[data-a11y~='text-align-end'] li {
|
|
182
|
-
text-align: end !important;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
[data-a11y~='text-align-justify'] body,
|
|
186
|
-
[data-a11y~='text-align-justify'] p,
|
|
187
|
-
[data-a11y~='text-align-justify'] li {
|
|
188
|
-
text-align: justify !important;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
237
|
@media not (forced-colors: active) {
|
|
192
238
|
html[data-a11y-filter='monochrome'] {
|
|
193
239
|
filter: grayscale(1);
|
|
@@ -233,12 +279,34 @@ html {
|
|
|
233
279
|
--a11y-fab-size: 3.5rem;
|
|
234
280
|
--a11y-fab-edge: 2rem;
|
|
235
281
|
--a11y-fab-stack-step: 4.25rem;
|
|
282
|
+
/* Beat host overlays (modals, CDK, 2147483000 FABs). Override via --a11y-z. */
|
|
283
|
+
--a11y-z: 2147483647;
|
|
284
|
+
position: fixed;
|
|
285
|
+
inset: 0;
|
|
286
|
+
z-index: var(--a11y-z);
|
|
287
|
+
isolation: isolate;
|
|
288
|
+
pointer-events: none;
|
|
289
|
+
/* Isolate widget chrome from page font-scale / spacing modes */
|
|
236
290
|
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
|
|
237
291
|
font-size: 14px;
|
|
238
292
|
line-height: 1.4;
|
|
293
|
+
letter-spacing: normal !important;
|
|
294
|
+
word-spacing: normal !important;
|
|
295
|
+
text-align: start;
|
|
239
296
|
color: var(--a11y-text);
|
|
240
297
|
}
|
|
241
298
|
|
|
299
|
+
.a11y-root * {
|
|
300
|
+
letter-spacing: normal;
|
|
301
|
+
word-spacing: normal;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.a11y-fab,
|
|
305
|
+
.a11y-layer,
|
|
306
|
+
.a11y-mask-toolbar {
|
|
307
|
+
pointer-events: auto;
|
|
308
|
+
}
|
|
309
|
+
|
|
242
310
|
.a11y-fab {
|
|
243
311
|
position: fixed;
|
|
244
312
|
z-index: 390;
|
|
@@ -357,6 +425,10 @@ html {
|
|
|
357
425
|
animation-name: a11y-panel-from-start;
|
|
358
426
|
}
|
|
359
427
|
|
|
428
|
+
.a11y-panel--static {
|
|
429
|
+
animation: none !important;
|
|
430
|
+
}
|
|
431
|
+
|
|
360
432
|
[dir='rtl'] .a11y-panel--end {
|
|
361
433
|
animation-name: a11y-panel-from-start;
|
|
362
434
|
}
|
|
@@ -365,6 +437,10 @@ html {
|
|
|
365
437
|
animation-name: a11y-panel-from-end;
|
|
366
438
|
}
|
|
367
439
|
|
|
440
|
+
[dir='rtl'] .a11y-panel--static {
|
|
441
|
+
animation: none !important;
|
|
442
|
+
}
|
|
443
|
+
|
|
368
444
|
.a11y-panel__header,
|
|
369
445
|
.a11y-panel__footer {
|
|
370
446
|
flex-shrink: 0;
|
|
@@ -510,6 +586,20 @@ html {
|
|
|
510
586
|
background: var(--a11y-neutral);
|
|
511
587
|
}
|
|
512
588
|
|
|
589
|
+
.a11y-stepper .a11y-btn {
|
|
590
|
+
width: 1.75rem;
|
|
591
|
+
height: 1.75rem;
|
|
592
|
+
min-height: 1.75rem;
|
|
593
|
+
padding: 0;
|
|
594
|
+
flex-shrink: 0;
|
|
595
|
+
color: var(--a11y-text);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
.a11y-stepper .a11y-icon {
|
|
599
|
+
display: block;
|
|
600
|
+
flex-shrink: 0;
|
|
601
|
+
}
|
|
602
|
+
|
|
513
603
|
.a11y-stepper__value {
|
|
514
604
|
min-width: 4.5rem;
|
|
515
605
|
text-align: center;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a.nemreen/a11y",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "Framework-agnostic accessibility tools widget — profiles, readable experience, visual filters. Works with Angular, React, Vue, and plain HTML.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Ahmed Nemreen",
|
|
@@ -24,8 +24,15 @@
|
|
|
24
24
|
"exports": {
|
|
25
25
|
".": {
|
|
26
26
|
"types": "./dist/index.d.ts",
|
|
27
|
-
"import":
|
|
28
|
-
|
|
27
|
+
"import": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"default": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"require": {
|
|
32
|
+
"types": "./dist/index.d.cts",
|
|
33
|
+
"default": "./dist/index.cjs"
|
|
34
|
+
},
|
|
35
|
+
"default": "./dist/index.js"
|
|
29
36
|
},
|
|
30
37
|
"./styles.css": "./dist/styles.css"
|
|
31
38
|
},
|