@oksigenia/access-panel 0.5.0 → 0.6.1
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/CHANGELOG.md +19 -0
- package/README.md +73 -3
- package/dist/index.d.ts +32 -2
- package/dist/index.js +146 -26
- package/dist/index.js.map +1 -1
- package/dist/web-component.d.ts +5 -0
- package/dist/web-component.js +179 -32
- package/dist/web-component.js.map +1 -1
- package/package.json +1 -1
package/dist/web-component.js
CHANGED
|
@@ -60,6 +60,18 @@ function resolveEnabledControls(controlsAttr, excludeAttr) {
|
|
|
60
60
|
}
|
|
61
61
|
return enabled;
|
|
62
62
|
}
|
|
63
|
+
var NON_SCOPABLE_CONTROLS = [
|
|
64
|
+
"grayscale",
|
|
65
|
+
"reading-guide",
|
|
66
|
+
"reading-mask",
|
|
67
|
+
"big-cursor",
|
|
68
|
+
"colorblind"
|
|
69
|
+
];
|
|
70
|
+
function scopedControls(enabled) {
|
|
71
|
+
const out = new Set(enabled);
|
|
72
|
+
for (const id of NON_SCOPABLE_CONTROLS) out.delete(id);
|
|
73
|
+
return out;
|
|
74
|
+
}
|
|
63
75
|
function filterPresetForEnabled(id, enabled) {
|
|
64
76
|
const out = {};
|
|
65
77
|
for (const [k, v] of Object.entries(PRESETS[id])) {
|
|
@@ -360,30 +372,36 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
360
372
|
}
|
|
361
373
|
let state = loadState(storageKey);
|
|
362
374
|
function applyState() {
|
|
363
|
-
const
|
|
375
|
+
const scoped = opts.scopeEl !== void 0;
|
|
376
|
+
const fxRoot = scoped ? opts.scopeEl ?? null : document.body;
|
|
364
377
|
const rootEl = document.documentElement;
|
|
365
|
-
|
|
366
|
-
|
|
378
|
+
if (fxRoot) {
|
|
379
|
+
for (const cls of Array.from(fxRoot.classList)) {
|
|
380
|
+
if (cls.startsWith("oks-")) fxRoot.classList.remove(cls);
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
if (!scoped) [1, 2, 3].forEach((l) => rootEl.classList.remove(`oks-colorblind-${l}`));
|
|
384
|
+
if (fxRoot) {
|
|
385
|
+
if (state.zoom > 0) fxRoot.classList.add(`oks-zoom-${state.zoom}`);
|
|
386
|
+
if (state.lh > 0) fxRoot.classList.add(`oks-lh-${state.lh}`);
|
|
387
|
+
if (state.align > 0) fxRoot.classList.add(`oks-align-${state.align}`);
|
|
388
|
+
if (state.ls > 0) fxRoot.classList.add(`oks-ls-${state.ls}`);
|
|
389
|
+
if (state.font) fxRoot.classList.add("oks-a11y-font");
|
|
390
|
+
if (state.dyslexia) fxRoot.classList.add("oks-dyslexia");
|
|
391
|
+
if (state.contrast) fxRoot.classList.add("oks-a11y-contrast");
|
|
392
|
+
if (state.hideImages) fxRoot.classList.add("oks-a11y-hide");
|
|
393
|
+
if (state.highlightLinks) fxRoot.classList.add("oks-a11y-links");
|
|
394
|
+
if (state.pauseAnim) fxRoot.classList.add("oks-a11y-pause");
|
|
395
|
+
if (state.focusOutline) fxRoot.classList.add("oks-a11y-focus");
|
|
396
|
+
if (state.bigTargets) fxRoot.classList.add("oks-a11y-bigtargets");
|
|
397
|
+
}
|
|
398
|
+
if (!scoped) {
|
|
399
|
+
if (state.colorblind > 0) rootEl.classList.add(`oks-colorblind-${state.colorblind}`);
|
|
400
|
+
if (state.bigCursor) document.body.classList.add("oks-big-cursor");
|
|
401
|
+
if (state.readingGuide) document.body.classList.add("oks-a11y-guide");
|
|
402
|
+
if (state.readingMask) document.body.classList.add("oks-a11y-mask");
|
|
403
|
+
ensureOverlay().classList.toggle("is-active", state.grayOverlay);
|
|
367
404
|
}
|
|
368
|
-
[1, 2, 3].forEach((l) => rootEl.classList.remove(`oks-colorblind-${l}`));
|
|
369
|
-
if (state.zoom > 0) body.classList.add(`oks-zoom-${state.zoom}`);
|
|
370
|
-
if (state.lh > 0) body.classList.add(`oks-lh-${state.lh}`);
|
|
371
|
-
if (state.align > 0) body.classList.add(`oks-align-${state.align}`);
|
|
372
|
-
if (state.ls > 0) body.classList.add(`oks-ls-${state.ls}`);
|
|
373
|
-
if (state.colorblind > 0) rootEl.classList.add(`oks-colorblind-${state.colorblind}`);
|
|
374
|
-
if (state.font) body.classList.add("oks-a11y-font");
|
|
375
|
-
if (state.dyslexia) body.classList.add("oks-dyslexia");
|
|
376
|
-
if (state.contrast) body.classList.add("oks-a11y-contrast");
|
|
377
|
-
if (state.hideImages) body.classList.add("oks-a11y-hide");
|
|
378
|
-
if (state.highlightLinks) body.classList.add("oks-a11y-links");
|
|
379
|
-
if (state.bigCursor) body.classList.add("oks-big-cursor");
|
|
380
|
-
if (state.pauseAnim) body.classList.add("oks-a11y-pause");
|
|
381
|
-
if (state.focusOutline) body.classList.add("oks-a11y-focus");
|
|
382
|
-
if (state.readingGuide) body.classList.add("oks-a11y-guide");
|
|
383
|
-
if (state.readingMask) body.classList.add("oks-a11y-mask");
|
|
384
|
-
if (state.bigTargets) body.classList.add("oks-a11y-bigtargets");
|
|
385
|
-
const overlay = ensureOverlay();
|
|
386
|
-
overlay.classList.toggle("is-active", state.grayOverlay);
|
|
387
405
|
syncButtonsFromState();
|
|
388
406
|
wrapper?.classList.toggle("has-active", !isStateEmpty(state));
|
|
389
407
|
}
|
|
@@ -462,11 +480,24 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
462
480
|
};
|
|
463
481
|
let opener = null;
|
|
464
482
|
let ignoreDocClose = false;
|
|
483
|
+
const positionScopedDialog = () => {
|
|
484
|
+
const el = opts.scopeEl;
|
|
485
|
+
if (!el) return;
|
|
486
|
+
const r = el.getBoundingClientRect();
|
|
487
|
+
if (r.width === 0 && r.height === 0) return;
|
|
488
|
+
const pad = 8;
|
|
489
|
+
panel.style.top = `${Math.max(pad, r.top + pad)}px`;
|
|
490
|
+
panel.style.bottom = "auto";
|
|
491
|
+
panel.style.left = `${r.left + r.width / 2}px`;
|
|
492
|
+
panel.style.right = "auto";
|
|
493
|
+
panel.style.transform = "translateX(-50%)";
|
|
494
|
+
};
|
|
465
495
|
const openPanel = () => {
|
|
466
496
|
if (panel.classList.contains("is-open")) return;
|
|
467
497
|
opener = deepActiveElement();
|
|
468
498
|
panel.classList.add("is-open");
|
|
469
499
|
panel.removeAttribute("inert");
|
|
500
|
+
if (opts.scopeEl) positionScopedDialog();
|
|
470
501
|
trigger?.setAttribute("aria-expanded", "true");
|
|
471
502
|
ignoreDocClose = true;
|
|
472
503
|
setTimeout(() => {
|
|
@@ -633,6 +664,13 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
633
664
|
document.addEventListener("keydown", onKeyDown);
|
|
634
665
|
document.addEventListener("mousemove", onMove);
|
|
635
666
|
document.addEventListener("touchmove", onMove, { passive: true });
|
|
667
|
+
const onReposition = () => {
|
|
668
|
+
if (opts.scopeEl && panel.classList.contains("is-open")) positionScopedDialog();
|
|
669
|
+
};
|
|
670
|
+
if (opts.scopeEl !== void 0) {
|
|
671
|
+
window.addEventListener("scroll", onReposition, true);
|
|
672
|
+
window.addEventListener("resize", onReposition);
|
|
673
|
+
}
|
|
636
674
|
if (nudgeMax > 0 && trigger) {
|
|
637
675
|
loadNudge();
|
|
638
676
|
applyNudge();
|
|
@@ -653,6 +691,10 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
653
691
|
document.removeEventListener("keydown", onKeyDown);
|
|
654
692
|
document.removeEventListener("mousemove", onMove);
|
|
655
693
|
document.removeEventListener("touchmove", onMove);
|
|
694
|
+
if (opts.scopeEl !== void 0) {
|
|
695
|
+
window.removeEventListener("scroll", onReposition, true);
|
|
696
|
+
window.removeEventListener("resize", onReposition);
|
|
697
|
+
}
|
|
656
698
|
trigger?.removeEventListener("pointerdown", onPointerDown);
|
|
657
699
|
document.removeEventListener("pointermove", onPointerMove);
|
|
658
700
|
document.removeEventListener("pointerup", onPointerUp);
|
|
@@ -959,18 +1001,22 @@ body.oks-ls-1 * { letter-spacing: 0.05em !important; }
|
|
|
959
1001
|
body.oks-ls-2 * { letter-spacing: 0.10em !important; }
|
|
960
1002
|
body.oks-ls-3 * { letter-spacing: 0.16em !important; }
|
|
961
1003
|
|
|
1004
|
+
/* Focus highlight colour is themeable via --oks-focus-color (default #005fcc):
|
|
1005
|
+
set it on :root for a dark theme so the highlight stays visible. The
|
|
1006
|
+
persistent "all interactive" dashed derives a 45% tint from it; the glow can
|
|
1007
|
+
be overridden on its own with --oks-focus-glow. */
|
|
962
1008
|
body.oks-a11y-focus a:not(oksigenia-access-panel):not(oksigenia-access-panel *),
|
|
963
1009
|
body.oks-a11y-focus button:not(oksigenia-access-panel):not(oksigenia-access-panel *),
|
|
964
1010
|
body.oks-a11y-focus input,
|
|
965
1011
|
body.oks-a11y-focus select,
|
|
966
1012
|
body.oks-a11y-focus textarea {
|
|
967
|
-
outline: 2px dashed
|
|
1013
|
+
outline: 2px dashed color-mix(in srgb, var(--oks-focus-color, #005fcc) 45%, transparent) !important;
|
|
968
1014
|
outline-offset: 2px !important;
|
|
969
1015
|
}
|
|
970
1016
|
body.oks-a11y-focus *:focus-visible {
|
|
971
|
-
outline: 3px solid #005fcc !important;
|
|
1017
|
+
outline: 3px solid var(--oks-focus-color, #005fcc) !important;
|
|
972
1018
|
outline-offset: 3px !important;
|
|
973
|
-
box-shadow: 0 0 0 6px
|
|
1019
|
+
box-shadow: 0 0 0 6px var(--oks-focus-glow, color-mix(in srgb, var(--oks-focus-color, #005fcc) 25%, transparent)) !important;
|
|
974
1020
|
}
|
|
975
1021
|
|
|
976
1022
|
body.oks-a11y-contrast.oks-a11y-focus *:focus,
|
|
@@ -1069,6 +1115,80 @@ body.oks-a11y-bigtargets summary:not(oksigenia-access-panel):not(oksigenia-acces
|
|
|
1069
1115
|
box-sizing: border-box !important;
|
|
1070
1116
|
}
|
|
1071
1117
|
`;
|
|
1118
|
+
function scopedEffectCss(s) {
|
|
1119
|
+
return `
|
|
1120
|
+
${s}.oks-zoom-1 { font-size: 110% !important; }
|
|
1121
|
+
${s}.oks-zoom-2 { font-size: 120% !important; }
|
|
1122
|
+
${s}.oks-zoom-3 { font-size: 135% !important; }
|
|
1123
|
+
${s}.oks-zoom-4 { font-size: 150% !important; }
|
|
1124
|
+
|
|
1125
|
+
${s}.oks-lh-1 * { line-height: 1.6 !important; }
|
|
1126
|
+
${s}.oks-lh-2 * { line-height: 1.9 !important; }
|
|
1127
|
+
${s}.oks-lh-3 * { line-height: 2.2 !important; }
|
|
1128
|
+
|
|
1129
|
+
${s}.oks-a11y-font, ${s}.oks-a11y-font * { font-family: Arial, sans-serif !important; }
|
|
1130
|
+
|
|
1131
|
+
${s}.oks-dyslexia * {
|
|
1132
|
+
font-family: 'Comic Sans MS', 'Verdana', sans-serif !important;
|
|
1133
|
+
letter-spacing: 0.05em !important;
|
|
1134
|
+
word-spacing: 0.1em !important;
|
|
1135
|
+
line-height: 1.6 !important;
|
|
1136
|
+
}
|
|
1137
|
+
|
|
1138
|
+
${s}.oks-a11y-hide img { opacity: 0 !important; visibility: hidden !important; }
|
|
1139
|
+
${s}.oks-a11y-links a { text-decoration: underline !important; background: #ff0 !important; color: #000 !important; }
|
|
1140
|
+
|
|
1141
|
+
${s}.oks-align-1 * { text-align: left !important; }
|
|
1142
|
+
${s}.oks-align-2 * { text-align: center !important; }
|
|
1143
|
+
${s}.oks-align-3 * { text-align: right !important; }
|
|
1144
|
+
|
|
1145
|
+
${s}.oks-a11y-pause * { animation: none !important; transition: none !important; }
|
|
1146
|
+
|
|
1147
|
+
${s}.oks-ls-1 * { letter-spacing: 0.05em !important; }
|
|
1148
|
+
${s}.oks-ls-2 * { letter-spacing: 0.10em !important; }
|
|
1149
|
+
${s}.oks-ls-3 * { letter-spacing: 0.16em !important; }
|
|
1150
|
+
|
|
1151
|
+
${s}.oks-a11y-focus a,
|
|
1152
|
+
${s}.oks-a11y-focus button,
|
|
1153
|
+
${s}.oks-a11y-focus input,
|
|
1154
|
+
${s}.oks-a11y-focus select,
|
|
1155
|
+
${s}.oks-a11y-focus textarea {
|
|
1156
|
+
outline: 2px dashed color-mix(in srgb, var(--oks-focus-color, #005fcc) 45%, transparent) !important;
|
|
1157
|
+
outline-offset: 2px !important;
|
|
1158
|
+
}
|
|
1159
|
+
${s}.oks-a11y-focus *:focus-visible {
|
|
1160
|
+
outline: 3px solid var(--oks-focus-color, #005fcc) !important;
|
|
1161
|
+
outline-offset: 3px !important;
|
|
1162
|
+
box-shadow: 0 0 0 6px var(--oks-focus-glow, color-mix(in srgb, var(--oks-focus-color, #005fcc) 25%, transparent)) !important;
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
${s}.oks-a11y-contrast, ${s}.oks-a11y-contrast * {
|
|
1166
|
+
background-color: #000 !important;
|
|
1167
|
+
color: #ff0 !important;
|
|
1168
|
+
border-color: #ff0 !important;
|
|
1169
|
+
text-shadow: none !important;
|
|
1170
|
+
box-shadow: none !important;
|
|
1171
|
+
}
|
|
1172
|
+
${s}.oks-a11y-contrast img { filter: grayscale(100%) contrast(120%) !important; }
|
|
1173
|
+
${s}.oks-a11y-contrast a { color: #0ff !important; text-decoration: underline !important; }
|
|
1174
|
+
${s}.oks-a11y-contrast.oks-a11y-focus *:focus-visible {
|
|
1175
|
+
outline-color: #0ff !important;
|
|
1176
|
+
box-shadow: 0 0 0 6px rgba(0, 255, 255, 0.3) !important;
|
|
1177
|
+
}
|
|
1178
|
+
|
|
1179
|
+
${s}.oks-a11y-bigtargets a,
|
|
1180
|
+
${s}.oks-a11y-bigtargets button,
|
|
1181
|
+
${s}.oks-a11y-bigtargets [role="button"],
|
|
1182
|
+
${s}.oks-a11y-bigtargets input[type="checkbox"],
|
|
1183
|
+
${s}.oks-a11y-bigtargets input[type="radio"],
|
|
1184
|
+
${s}.oks-a11y-bigtargets summary {
|
|
1185
|
+
min-height: 44px !important;
|
|
1186
|
+
min-width: 44px !important;
|
|
1187
|
+
padding: 8px 12px !important;
|
|
1188
|
+
box-sizing: border-box !important;
|
|
1189
|
+
}
|
|
1190
|
+
`;
|
|
1191
|
+
}
|
|
1072
1192
|
|
|
1073
1193
|
// src/web-component.ts
|
|
1074
1194
|
var OBSERVED = [
|
|
@@ -1082,7 +1202,8 @@ var OBSERVED = [
|
|
|
1082
1202
|
"trigger",
|
|
1083
1203
|
"effects-exclude",
|
|
1084
1204
|
"nudge",
|
|
1085
|
-
"presets"
|
|
1205
|
+
"presets",
|
|
1206
|
+
"scope"
|
|
1086
1207
|
];
|
|
1087
1208
|
var STYLE_ID = "oksigenia-access-effects";
|
|
1088
1209
|
var FILTERS_ID = "oksigenia-access-filters";
|
|
@@ -1135,6 +1256,7 @@ var OksigeniaAccessPanelElement = class extends HTMLElement {
|
|
|
1135
1256
|
}
|
|
1136
1257
|
_controller = null;
|
|
1137
1258
|
_fxId = `oks-access-fx-${++fxSeq}`;
|
|
1259
|
+
_scopeId = `oks-access-scope-${this._fxId.split("-").pop()}`;
|
|
1138
1260
|
constructor() {
|
|
1139
1261
|
super();
|
|
1140
1262
|
this.attachShadow({ mode: "open" });
|
|
@@ -1146,7 +1268,10 @@ var OksigeniaAccessPanelElement = class extends HTMLElement {
|
|
|
1146
1268
|
disconnectedCallback() {
|
|
1147
1269
|
this._controller?.();
|
|
1148
1270
|
this._controller = null;
|
|
1149
|
-
if (typeof document !== "undefined")
|
|
1271
|
+
if (typeof document !== "undefined") {
|
|
1272
|
+
document.getElementById(this._fxId)?.remove();
|
|
1273
|
+
document.getElementById(this._scopeId)?.remove();
|
|
1274
|
+
}
|
|
1150
1275
|
}
|
|
1151
1276
|
attributeChangedCallback() {
|
|
1152
1277
|
if (this.isConnected) this.render();
|
|
@@ -1189,7 +1314,7 @@ var OksigeniaAccessPanelElement = class extends HTMLElement {
|
|
|
1189
1314
|
* drop the destructive filter from selectors the host marks as essential
|
|
1190
1315
|
* media (video, canvas, scientific imagery). Grayscale is handled by not
|
|
1191
1316
|
* offering the control (curation), not here. */
|
|
1192
|
-
updateEffectsExclude() {
|
|
1317
|
+
updateEffectsExclude(base) {
|
|
1193
1318
|
if (typeof document === "undefined") return;
|
|
1194
1319
|
const sel = (this.getAttribute("effects-exclude") ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
1195
1320
|
const existing = document.getElementById(this._fxId);
|
|
@@ -1203,7 +1328,24 @@ var OksigeniaAccessPanelElement = class extends HTMLElement {
|
|
|
1203
1328
|
document.head.appendChild(style);
|
|
1204
1329
|
}
|
|
1205
1330
|
const list = sel.join(", ");
|
|
1206
|
-
style.textContent =
|
|
1331
|
+
style.textContent = `${base}.oks-a11y-contrast :is(${list}), ${base}.oks-a11y-contrast :is(${list}) * { filter: none !important; }`;
|
|
1332
|
+
}
|
|
1333
|
+
/** Inject the per-instance scoped effect CSS (`scope=`): the scopable effects
|
|
1334
|
+
* anchored to the host's container instead of `body`, so several panels
|
|
1335
|
+
* regionalise their own zone without clobbering each other. */
|
|
1336
|
+
updateScopeStyle(scope) {
|
|
1337
|
+
if (typeof document === "undefined") return;
|
|
1338
|
+
const existing = document.getElementById(this._scopeId);
|
|
1339
|
+
if (!scope) {
|
|
1340
|
+
existing?.remove();
|
|
1341
|
+
return;
|
|
1342
|
+
}
|
|
1343
|
+
const style = existing ?? document.createElement("style");
|
|
1344
|
+
if (!existing) {
|
|
1345
|
+
style.id = this._scopeId;
|
|
1346
|
+
document.head.appendChild(style);
|
|
1347
|
+
}
|
|
1348
|
+
style.textContent = scopedEffectCss(scope);
|
|
1207
1349
|
}
|
|
1208
1350
|
render() {
|
|
1209
1351
|
const shadow = this.shadowRoot;
|
|
@@ -1211,7 +1353,9 @@ var OksigeniaAccessPanelElement = class extends HTMLElement {
|
|
|
1211
1353
|
this._controller?.();
|
|
1212
1354
|
const position = this.getPosition();
|
|
1213
1355
|
const positionMobile = this.getPositionMobile();
|
|
1214
|
-
const
|
|
1356
|
+
const scope = this.getAttribute("scope")?.trim() || null;
|
|
1357
|
+
let enabled = resolveEnabledControls(this.getAttribute("controls"), this.getAttribute("exclude"));
|
|
1358
|
+
if (scope) enabled = scopedControls(enabled);
|
|
1215
1359
|
const showTrigger = this.getAttribute("trigger") !== "none";
|
|
1216
1360
|
const showPresets = (this.getAttribute("presets") ?? "").trim().toLowerCase() !== "none";
|
|
1217
1361
|
const html = buildPanelHtml({
|
|
@@ -1223,13 +1367,16 @@ var OksigeniaAccessPanelElement = class extends HTMLElement {
|
|
|
1223
1367
|
showPresets
|
|
1224
1368
|
});
|
|
1225
1369
|
shadow.innerHTML = `<style>${PANEL_CSS}${positionCss(position, positionMobile)}</style>${html}`;
|
|
1370
|
+
const scopeEl = scope ? document.querySelector(scope) : void 0;
|
|
1226
1371
|
this._controller = bindPanelBehavior(shadow, {
|
|
1227
1372
|
storageKey: this.getAttribute("storage-key") ?? void 0,
|
|
1228
1373
|
locale: this.getLocale(),
|
|
1229
1374
|
enabled,
|
|
1230
|
-
nudgeMax: this.getNudgeMax()
|
|
1375
|
+
nudgeMax: this.getNudgeMax(),
|
|
1376
|
+
scopeEl
|
|
1231
1377
|
});
|
|
1232
|
-
this.
|
|
1378
|
+
this.updateScopeStyle(scope);
|
|
1379
|
+
this.updateEffectsExclude(scope ?? "body");
|
|
1233
1380
|
}
|
|
1234
1381
|
};
|
|
1235
1382
|
if (typeof customElements !== "undefined" && !customElements.get("oksigenia-access-panel")) {
|