@oksigenia/access-panel 0.4.3 → 0.5.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/CHANGELOG.md +24 -0
- package/README.md +26 -2
- package/dist/index.d.ts +65 -21
- package/dist/index.js +320 -74
- package/dist/index.js.map +1 -1
- package/dist/web-component.d.ts +14 -1
- package/dist/web-component.js +405 -92
- package/dist/web-component.js.map +1 -1
- package/package.json +1 -1
package/dist/web-component.js
CHANGED
|
@@ -1,3 +1,76 @@
|
|
|
1
|
+
// src/controls.ts
|
|
2
|
+
var ALL_CONTROLS = [
|
|
3
|
+
"text-size",
|
|
4
|
+
"line-height",
|
|
5
|
+
"text-align",
|
|
6
|
+
"readable-font",
|
|
7
|
+
"dyslexia-font",
|
|
8
|
+
"letter-spacing",
|
|
9
|
+
"contrast",
|
|
10
|
+
"grayscale",
|
|
11
|
+
"hide-images",
|
|
12
|
+
"highlight-links",
|
|
13
|
+
"colorblind",
|
|
14
|
+
"reading-guide",
|
|
15
|
+
"reading-mask",
|
|
16
|
+
"big-cursor",
|
|
17
|
+
"big-targets",
|
|
18
|
+
"pause-anim",
|
|
19
|
+
"focus"
|
|
20
|
+
];
|
|
21
|
+
var CONTROL_STATE_KEY = {
|
|
22
|
+
"text-size": "zoom",
|
|
23
|
+
"line-height": "lh",
|
|
24
|
+
"text-align": "align",
|
|
25
|
+
"readable-font": "font",
|
|
26
|
+
"dyslexia-font": "dyslexia",
|
|
27
|
+
"letter-spacing": "ls",
|
|
28
|
+
"contrast": "contrast",
|
|
29
|
+
"grayscale": "grayOverlay",
|
|
30
|
+
"hide-images": "hideImages",
|
|
31
|
+
"highlight-links": "highlightLinks",
|
|
32
|
+
"colorblind": "colorblind",
|
|
33
|
+
"reading-guide": "readingGuide",
|
|
34
|
+
"reading-mask": "readingMask",
|
|
35
|
+
"big-cursor": "bigCursor",
|
|
36
|
+
"big-targets": "bigTargets",
|
|
37
|
+
"pause-anim": "pauseAnim",
|
|
38
|
+
"focus": "focusOutline"
|
|
39
|
+
};
|
|
40
|
+
var STATE_KEY_CONTROL = Object.fromEntries(
|
|
41
|
+
Object.entries(CONTROL_STATE_KEY).map(([control, key]) => [key, control])
|
|
42
|
+
);
|
|
43
|
+
var PRESETS = {
|
|
44
|
+
lowvision: { zoom: 2, contrast: true, highlightLinks: true, bigCursor: true, focusOutline: true },
|
|
45
|
+
dyslexia: { dyslexia: true, lh: 2, ls: 2, readingGuide: true },
|
|
46
|
+
motor: { bigCursor: true, bigTargets: true, focusOutline: true },
|
|
47
|
+
calm: { hideImages: true, pauseAnim: true }
|
|
48
|
+
};
|
|
49
|
+
var PRESET_IDS = ["lowvision", "dyslexia", "motor", "calm"];
|
|
50
|
+
function presetControlIds(id) {
|
|
51
|
+
return Object.keys(PRESETS[id]).map((k) => STATE_KEY_CONTROL[k]);
|
|
52
|
+
}
|
|
53
|
+
function resolveEnabledControls(controlsAttr, excludeAttr) {
|
|
54
|
+
const valid = new Set(ALL_CONTROLS);
|
|
55
|
+
const parse = (raw) => (raw ?? "").split(",").map((s) => s.trim().toLowerCase()).filter((s) => valid.has(s));
|
|
56
|
+
const whitelist = controlsAttr != null ? parse(controlsAttr) : null;
|
|
57
|
+
let enabled = whitelist && whitelist.length > 0 ? new Set(whitelist) : new Set(ALL_CONTROLS);
|
|
58
|
+
if (excludeAttr != null) {
|
|
59
|
+
for (const id of parse(excludeAttr)) enabled.delete(id);
|
|
60
|
+
}
|
|
61
|
+
return enabled;
|
|
62
|
+
}
|
|
63
|
+
function filterPresetForEnabled(id, enabled) {
|
|
64
|
+
const out = {};
|
|
65
|
+
for (const [k, v] of Object.entries(PRESETS[id])) {
|
|
66
|
+
if (enabled.has(STATE_KEY_CONTROL[k])) out[k] = v;
|
|
67
|
+
}
|
|
68
|
+
return out;
|
|
69
|
+
}
|
|
70
|
+
function presetIsAvailable(id, enabled) {
|
|
71
|
+
return presetControlIds(id).filter((c) => enabled.has(c)).length >= 2;
|
|
72
|
+
}
|
|
73
|
+
|
|
1
74
|
// src/icons.ts
|
|
2
75
|
var ICON_TXT = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M2.5,4v3h5v12h3V7h5V4H2.5z M21.5,9h-9v3h3v7h3v-7h3V9z"/></svg>';
|
|
3
76
|
var ICON_LH = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M6,7h2.5L5,3.5L1.5,7H4v10H1.5L5,20.5L8.5,17H6V7z M10,5v2h12V5H10z M10,19h12v-2H10V19z M10,13h12v-2H10V13z"/></svg>';
|
|
@@ -16,75 +89,100 @@ var ICON_COLORBLIND = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12
|
|
|
16
89
|
var ICON_FOCUS = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 7V3h4v2H5v2H3zm14-4h4v4h-2V5h-2V3zm4 14v4h-4v-2h2v-2h2zM7 21H3v-4h2v2h2v2z"/></svg>';
|
|
17
90
|
var ICON_MASK = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M2 4h20v6H2zm0 10h20v6H2zm2-9v4h16V5zm0 10v4h16v-4z" opacity=".55"/><rect x="2" y="10" width="20" height="4"/></svg>';
|
|
18
91
|
var ICON_TARGETS = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M3 3h4v2H5v2H3zm14 0h4v4h-2V5h-2zm0 18v-2h2v-2h2v4zM3 21v-4h2v2h2v2zM12 8a4 4 0 100 8 4 4 0 000-8zm0 2a2 2 0 110 4 2 2 0 010-4z"/></svg>';
|
|
92
|
+
var UNIVERSAL_GLYPH = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 2c1.1 0 2 .9 2 2s-.9 2-2 2-2-.9-2-2 .9-2 2-2zm9 7h-6v13h-2v-6h-2v6H9V9H3V7h18v2z"/></svg>';
|
|
19
93
|
var TRIGGER_ICONS = {
|
|
20
94
|
vitruvian: '<svg viewBox="0 0 122.88 122.88" fill="currentColor"><path d="M61.44,0A61.46,61.46,0,1,1,18,18,61.21,61.21,0,0,1,61.44,0Zm-.39,74.18L52.1,98.91a4.94,4.94,0,0,1-2.58,2.83A5,5,0,0,1,42.7,95.5l6.24-17.28a26.3,26.3,0,0,0,1.17-4,40.64,40.64,0,0,0,.54-4.18c.24-2.53.41-5.27.54-7.9s.22-5.18.29-7.29c.09-2.63-.62-2.8-2.73-3.3l-.44-.1-18-3.39A5,5,0,0,1,27.08,46a5,5,0,0,1,5.05-7.74l19.34,3.63c.77.07,1.52.16,2.31.25a57.64,57.64,0,0,0,7.18.53A81.13,81.13,0,0,0,69.9,42c.9-.1,1.75-.21,2.6-.29l18.25-3.42A5,5,0,0,1,94.5,39a5,5,0,0,1,1.3,7,5,5,0,0,1-3.21,2.09L75.15,51.37c-.58.13-1.1.22-1.56.29-1.82.31-2.72.47-2.61,3.06.08,1.89.31,4.15.61,6.51.35,2.77.81,5.71,1.29,8.4.31,1.77.6,3.19,1,4.55s.79,2.75,1.39,4.42l6.11,16.9a5,5,0,0,1-6.82,6.24,4.94,4.94,0,0,1-2.58-2.83L63,74.23,62,72.4l-1,1.78Zm.39-53.52a8.83,8.83,0,1,1-6.24,2.59,8.79,8.79,0,0,1,6.24-2.59Zm36.35,4.43a51.42,51.42,0,1,0,15,36.35,51.27,51.27,0,0,0-15-36.35Z"/></svg>',
|
|
21
95
|
wheelchair: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M19 13v-2c-1.54.02-3.09-.75-4.07-1.83l-1.29-1.43c-.17-.19-.38-.34-.61-.45-.01 0-.01-.01-.02-.01H13c-.35-.2-.75-.3-1.19-.26C10.76 7.11 10 8.04 10 9.09V15c0 1.1.9 2 2 2h5v5h2v-5.5c0-1.1-.9-2-2-2h-3v-3.45c1.29 1.07 3.25 1.94 5 1.95zm-6.17 5c-.41 1.16-1.52 2-2.83 2-1.66 0-3-1.34-3-3 0-1.31.84-2.41 2-2.83V12.1c-2.28.46-4 2.48-4 4.9 0 2.76 2.24 5 5 5 2.42 0 4.44-1.72 4.9-4h-2.07zM12 6c1.1 0 2-.9 2-2s-.9-2-2-2-2 .9-2 2 .9 2 2 2z"/></svg>',
|
|
22
96
|
eye: '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/></svg>',
|
|
23
|
-
universal:
|
|
97
|
+
universal: UNIVERSAL_GLYPH,
|
|
98
|
+
porthole: UNIVERSAL_GLYPH
|
|
24
99
|
};
|
|
25
100
|
var ICON_CLOSE = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>';
|
|
26
101
|
var COLORBLIND_FILTERS_SVG = '<svg xmlns="http://www.w3.org/2000/svg" style="position:absolute;width:0;height:0;overflow:hidden;" aria-hidden="true"><defs><filter id="oks-filter-protanopia"><feColorMatrix type="matrix" values="0.567 0.433 0 0 0 0.558 0.442 0 0 0 0 0.242 0.758 0 0 0 0 0 1 0"/></filter><filter id="oks-filter-deuteranopia"><feColorMatrix type="matrix" values="0.625 0.375 0 0 0 0.7 0.3 0 0 0 0 0.3 0.7 0 0 0 0 0 1 0"/></filter><filter id="oks-filter-tritanopia"><feColorMatrix type="matrix" values="0.95 0.05 0 0 0 0 0.433 0.567 0 0 0 0.475 0.525 0 0 0 0 0 1 0"/></filter></defs></svg>';
|
|
27
102
|
|
|
28
103
|
// src/render.ts
|
|
104
|
+
var PRESET_ICON = {
|
|
105
|
+
lowvision: ICON_CONTRAST,
|
|
106
|
+
dyslexia: ICON_DYSLEXIA,
|
|
107
|
+
motor: ICON_CURSOR,
|
|
108
|
+
calm: ICON_HIDE
|
|
109
|
+
};
|
|
29
110
|
function buildPanelHtml(opts) {
|
|
30
111
|
const { t, triggerIcon } = opts;
|
|
112
|
+
const enabled = opts.enabled ?? new Set(ALL_CONTROLS);
|
|
113
|
+
const showTrigger = opts.showTrigger ?? true;
|
|
114
|
+
const showPresets = opts.showPresets ?? true;
|
|
31
115
|
const trig = TRIGGER_ICONS[triggerIcon];
|
|
32
|
-
const
|
|
33
|
-
|
|
116
|
+
const multi = (id, prefix, levels, label, icon, full = false) => {
|
|
117
|
+
if (!enabled.has(id)) return "";
|
|
118
|
+
const action = id === "colorblind" ? "colorblind" : "multi";
|
|
34
119
|
const fullClass = full ? " full-width" : "";
|
|
35
120
|
const dots = Array.from({ length: levels }, () => "<span></span>").join("");
|
|
36
|
-
return `<button class="oks-access-opt multi-step${fullClass}" data-action="${action}" data-prefix="${prefix}" data-levels="${levels}" aria-pressed="false" type="button"><span class="oks-icon">${icon}</span><span class="oks-label">${label}</span><div class="oks-levels">${dots}</div></button>`;
|
|
121
|
+
return `<button class="oks-access-opt multi-step${fullClass}" data-control="${id}" data-action="${action}" data-prefix="${prefix}" data-levels="${levels}" aria-pressed="false" type="button"><span class="oks-icon">${icon}</span><span class="oks-label">${label}</span><div class="oks-levels">${dots}</div></button>`;
|
|
122
|
+
};
|
|
123
|
+
const toggle = (id, klass, label, icon) => {
|
|
124
|
+
if (!enabled.has(id)) return "";
|
|
125
|
+
return `<button class="oks-access-opt" data-control="${id}" data-action="toggle" data-class="${klass}" aria-pressed="false" type="button"><span class="oks-icon">${icon}</span><span class="oks-label">${label}</span></button>`;
|
|
126
|
+
};
|
|
127
|
+
const overlay = (id, target, label, icon) => {
|
|
128
|
+
if (!enabled.has(id)) return "";
|
|
129
|
+
return `<button class="oks-access-opt" data-control="${id}" data-action="overlay" data-target="${target}" aria-pressed="false" type="button"><span class="oks-icon">${icon}</span><span class="oks-label">${label}</span></button>`;
|
|
130
|
+
};
|
|
131
|
+
const simple = (id, action, label, icon) => {
|
|
132
|
+
if (!enabled.has(id)) return "";
|
|
133
|
+
return `<button class="oks-access-opt" data-control="${id}" data-action="${action}" aria-pressed="false" type="button"><span class="oks-icon">${icon}</span><span class="oks-label">${label}</span></button>`;
|
|
134
|
+
};
|
|
135
|
+
const preset = (id, label) => `<button class="oks-preset" data-action="preset" data-preset="${id}" type="button"><span class="oks-icon">${PRESET_ICON[id]}</span><span class="oks-label">${label}</span></button>`;
|
|
136
|
+
const section = (title, rows) => {
|
|
137
|
+
const kept = rows.filter(Boolean);
|
|
138
|
+
if (kept.length === 0) return "";
|
|
139
|
+
return `<h4 class="oks-access-title">${escapeHtml(title)}</h4><div class="oks-access-grid">${kept.join("")}</div>`;
|
|
140
|
+
};
|
|
141
|
+
const presetLabels = {
|
|
142
|
+
lowvision: t.pLow,
|
|
143
|
+
dyslexia: t.pDys,
|
|
144
|
+
motor: t.pMot,
|
|
145
|
+
calm: t.pCalm
|
|
37
146
|
};
|
|
38
|
-
const
|
|
39
|
-
const
|
|
40
|
-
const
|
|
41
|
-
const mask = (label, icon) => `<button class="oks-access-opt" data-action="mask" aria-pressed="false" type="button"><span class="oks-icon">${icon}</span><span class="oks-label">${label}</span></button>`;
|
|
42
|
-
const preset = (id, label, icon) => `<button class="oks-preset" data-action="preset" data-preset="${id}" type="button"><span class="oks-icon">${icon}</span><span class="oks-label">${label}</span></button>`;
|
|
43
|
-
return `
|
|
147
|
+
const presetButtons = showPresets ? PRESET_IDS.filter((id) => presetIsAvailable(id, enabled)).map((id) => preset(id, presetLabels[id])) : [];
|
|
148
|
+
const presetsBlock = presetButtons.length ? `<h4 class="oks-access-title">${escapeHtml(t.presets)}</h4><div class="oks-access-presets">${presetButtons.join("")}</div>` : "";
|
|
149
|
+
const triggerBlock = showTrigger ? `
|
|
44
150
|
<div class="oks-access-wrapper" id="oks-wrapper" data-position="${opts.position}">
|
|
45
|
-
<button class="oks-access-btn" id="oks-trigger" aria-label="${escapeAttr(t.title)}" aria-expanded="false" aria-controls="oks-panel" type="button">
|
|
151
|
+
<button class="oks-access-btn" id="oks-trigger" part="trigger" data-trigger-icon="${triggerIcon}" aria-label="${escapeAttr(t.title)}" aria-expanded="false" aria-controls="oks-panel" type="button">
|
|
46
152
|
${trig}
|
|
47
153
|
</button>
|
|
48
154
|
<span class="oks-active-badge"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="4"><polyline points="20 6 9 17 4 12"/></svg></span>
|
|
49
|
-
</div
|
|
155
|
+
</div>` : "";
|
|
156
|
+
return `${triggerBlock}
|
|
50
157
|
<div class="oks-access-panel" id="oks-panel" role="dialog" aria-modal="true" aria-labelledby="oks-panel-title" inert>
|
|
51
158
|
<div class="oks-access-header">
|
|
52
159
|
<h3 id="oks-panel-title">${escapeHtml(t.title)}</h3>
|
|
53
160
|
<button class="oks-access-close" id="oks-close" aria-label="${escapeAttr(t.close)}" type="button">${ICON_CLOSE}</button>
|
|
54
161
|
</div>
|
|
55
162
|
<div class="oks-access-content">
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
${grid([
|
|
65
|
-
multi("multi", "oks-zoom", 4, t.size, ICON_TXT),
|
|
66
|
-
multi("multi", "oks-lh", 3, t.lh, ICON_LH),
|
|
67
|
-
multi("multi", "oks-align", 3, t.align, ICON_ALIGN),
|
|
68
|
-
toggle("oks-a11y-font", t.font, ICON_FONT),
|
|
69
|
-
toggle("oks-dyslexia", t.dyslexia, ICON_DYSLEXIA),
|
|
70
|
-
multi("multi", "oks-ls", 3, t.ls, ICON_LS)
|
|
163
|
+
${presetsBlock}
|
|
164
|
+
${section(t.txt, [
|
|
165
|
+
multi("text-size", "oks-zoom", 4, t.size, ICON_TXT),
|
|
166
|
+
multi("line-height", "oks-lh", 3, t.lh, ICON_LH),
|
|
167
|
+
multi("text-align", "oks-align", 3, t.align, ICON_ALIGN),
|
|
168
|
+
toggle("readable-font", "oks-a11y-font", t.font, ICON_FONT),
|
|
169
|
+
toggle("dyslexia-font", "oks-dyslexia", t.dyslexia, ICON_DYSLEXIA),
|
|
170
|
+
multi("letter-spacing", "oks-ls", 3, t.ls, ICON_LS)
|
|
71
171
|
])}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
toggle("oks-a11y-
|
|
77
|
-
toggle("oks-a11y-links", t.links, ICON_LINK),
|
|
172
|
+
${section(t.vis, [
|
|
173
|
+
toggle("contrast", "oks-a11y-contrast", t.contrast, ICON_CONTRAST),
|
|
174
|
+
overlay("grayscale", "oks-overlay-gray", t.gray, ICON_GRAY),
|
|
175
|
+
toggle("hide-images", "oks-a11y-hide", t.hide, ICON_HIDE),
|
|
176
|
+
toggle("highlight-links", "oks-a11y-links", t.links, ICON_LINK),
|
|
78
177
|
multi("colorblind", "oks-colorblind", 3, t.cb, ICON_COLORBLIND, true)
|
|
79
178
|
])}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
toggle("oks-
|
|
85
|
-
toggle("oks-a11y-
|
|
86
|
-
toggle("oks-a11y-
|
|
87
|
-
toggle("oks-a11y-focus", t.focus, ICON_FOCUS)
|
|
179
|
+
${section(t.ori, [
|
|
180
|
+
simple("reading-guide", "guide", t.guide, ICON_GUIDE),
|
|
181
|
+
simple("reading-mask", "mask", t.mask, ICON_MASK),
|
|
182
|
+
toggle("big-cursor", "oks-big-cursor", t.cursor, ICON_CURSOR),
|
|
183
|
+
toggle("big-targets", "oks-a11y-bigtargets", t.targets, ICON_TARGETS),
|
|
184
|
+
toggle("pause-anim", "oks-a11y-pause", t.pause, ICON_PAUSE),
|
|
185
|
+
toggle("focus", "oks-a11y-focus", t.focus, ICON_FOCUS)
|
|
88
186
|
])}
|
|
89
187
|
</div>
|
|
90
188
|
<div class="oks-access-footer">
|
|
@@ -109,14 +207,20 @@ function positionRules(position) {
|
|
|
109
207
|
switch (position) {
|
|
110
208
|
case "top-left":
|
|
111
209
|
return { wrap: "top: 20px; left: 20px;", panel: "top: 80px; left: 20px;" };
|
|
210
|
+
case "top-center":
|
|
211
|
+
return { wrap: "top: 20px; left: 50%; transform: translateX(-50%);", panel: "top: 80px; left: 50%; transform: translateX(-50%);" };
|
|
112
212
|
case "top-right":
|
|
113
213
|
return { wrap: "top: 20px; right: 20px;", panel: "top: 80px; right: 20px;" };
|
|
114
214
|
case "mid-left":
|
|
115
215
|
return { wrap: "top: 50%; left: 20px; transform: translateY(-50%);", panel: "top: 50%; left: 90px; transform: translateY(-50%);" };
|
|
216
|
+
case "mid-center":
|
|
217
|
+
return { wrap: "top: 50%; left: 50%; transform: translate(-50%, -50%);", panel: "top: 50%; left: 50%; transform: translate(-50%, -50%);" };
|
|
116
218
|
case "mid-right":
|
|
117
219
|
return { wrap: "top: 50%; right: 20px; transform: translateY(-50%);", panel: "top: 50%; right: 90px; transform: translateY(-50%);" };
|
|
118
220
|
case "bottom-left":
|
|
119
221
|
return { wrap: "bottom: 20px; left: 20px;", panel: "bottom: 100px; left: 20px;" };
|
|
222
|
+
case "bottom-center":
|
|
223
|
+
return { wrap: "bottom: 20px; left: 50%; transform: translateX(-50%);", panel: "bottom: 100px; left: 50%; transform: translateX(-50%);" };
|
|
120
224
|
case "bottom-right":
|
|
121
225
|
return { wrap: "bottom: 20px; right: 20px;", panel: "bottom: 100px; right: 20px;" };
|
|
122
226
|
}
|
|
@@ -184,6 +288,24 @@ function isStateEmpty(state) {
|
|
|
184
288
|
return state.zoom === 0 && state.lh === 0 && state.align === 0 && state.ls === 0 && state.colorblind === 0 && !state.font && !state.dyslexia && !state.contrast && !state.hideImages && !state.highlightLinks && !state.bigCursor && !state.pauseAnim && !state.focusOutline && !state.grayOverlay && !state.readingGuide && !state.readingMask && !state.bigTargets;
|
|
185
289
|
}
|
|
186
290
|
|
|
291
|
+
// src/translations.ts
|
|
292
|
+
var DICT = {
|
|
293
|
+
es: { title: "Accesibilidad", close: "Cerrar", txt: "Texto", size: "Tama\xF1o", lh: "Interlineado", align: "Alineaci\xF3n", font: "Legible", dyslexia: "Fuente Dislexia", vis: "Visual", contrast: "Contraste", gray: "Grises", hide: "Ocultar Img", links: "Links", ori: "Orientaci\xF3n", guide: "Gu\xEDa", mask: "M\xE1scara", targets: "\xC1reas Grandes", cursor: "Cursor Grande", pause: "Parar Animac.", reset: "Restablecer Todo", dev: "Desarrollado por", ls: "Espaciado", cb: "Daltonismo", focus: "Foco", presets: "Perfiles", pLow: "Baja Visi\xF3n", pDys: "Dislexia", pMot: "Motor", pCalm: "Sin Distrac.", level: (l, n, m) => `${l}, nivel ${n} de ${m}` },
|
|
294
|
+
en: { title: "Accessibility", close: "Close", txt: "Text", size: "Size", lh: "Line Height", align: "Align", font: "Readable Font", dyslexia: "Dyslexia Font", vis: "Visual", contrast: "Contrast", gray: "Grayscale", hide: "Hide Images", links: "Highlight Links", ori: "Orientation", guide: "Reading Guide", mask: "Reading Mask", targets: "Big Targets", cursor: "Big Cursor", pause: "Pause Anim.", reset: "Reset All", dev: "Developed by", ls: "Letter Spacing", cb: "Color Blind", focus: "Focus", presets: "Profiles", pLow: "Low Vision", pDys: "Dyslexia", pMot: "Motor", pCalm: "No Distract.", level: (l, n, m) => `${l}, level ${n} of ${m}` },
|
|
295
|
+
gn: { title: "Oikeha (Accesibilidad)", close: "Mboty", txt: "Mo\xF1e'\u1EBDr\xE3", size: "Tuichakue", lh: "Jei", align: "Mbojoja", font: "Letra Por\xE3", dyslexia: "Dislexia", vis: "Hechapy", contrast: "Sa'y", gray: "H\u0169 ha T\u0129", hide: "Moka\xF1y Ta'anga", links: "Joajuha", ori: "S\xE3mbyhy", guide: "S\xE3mbyhyha", mask: "Mbohov\xE1i", targets: "Tenda Guasu", cursor: "Cursor Guasu", pause: "Mboopyta", reset: "Mbojevy", dev: "Apojare", ls: "Rapykue", cb: "Sa'yvy", focus: "\xD1emoha", presets: "Tekor\xE3", pLow: "Hechapy Vai", pDys: "Dislexia", pMot: "Po-rehegua", pCalm: "Py\u02BCa Guapy", level: (l, n, m) => `${l}, ${n}/${m}` },
|
|
296
|
+
fr: { title: "Accessibilit\xE9", close: "Fermer", txt: "Texte", size: "Taille", lh: "Interligne", align: "Alignement", font: "Police Lisible", dyslexia: "Police Dyslexie", vis: "Visuel", contrast: "Contraste", gray: "Niveaux Gris", hide: "Masquer Img", links: "Liens", ori: "Orientation", guide: "Guide Lecture", mask: "Masque Lecture", targets: "Grandes Cibles", cursor: "Grand Curseur", pause: "Pause Anim.", reset: "R\xE9initialiser", dev: "D\xE9velopp\xE9 par", ls: "Espacement", cb: "Daltonisme", focus: "Focus", presets: "Profils", pLow: "Basse Vision", pDys: "Dyslexie", pMot: "Moteur", pCalm: "Sans Distrac.", level: (l, n, m) => `${l}, niveau ${n} sur ${m}` },
|
|
297
|
+
it: { title: "Accessibilit\xE0", close: "Chiudi", txt: "Testo", size: "Dimensione", lh: "Interlinea", align: "Allineamento", font: "Leggibile", dyslexia: "Font Dislessia", vis: "Visivo", contrast: "Contrasto", gray: "Scala Grig.", hide: "Nascondi Img", links: "Link", ori: "Orientamento", guide: "Guida", mask: "Maschera", targets: "Aree Grandi", cursor: "Cursore Grande", pause: "Pausa Anim.", reset: "Reimposta", dev: "Sviluppato da", ls: "Spaziatura", cb: "Daltonismo", focus: "Focus", presets: "Profili", pLow: "Ipovisione", pDys: "Dislessia", pMot: "Motorio", pCalm: "No Distraz.", level: (l, n, m) => `${l}, livello ${n} di ${m}` },
|
|
298
|
+
de: { title: "Barrierefreiheit", close: "Schlie\xDFen", txt: "Text", size: "Gr\xF6\xDFe", lh: "Zeilenh\xF6he", align: "Ausrichtung", font: "Lesbar", dyslexia: "Dyslexie", vis: "Visuell", contrast: "Kontrast", gray: "Graustufen", hide: "Bilder Aus", links: "Links", ori: "Orientierung", guide: "Lesehilfe", mask: "Lesemaske", targets: "Gro\xDFe Ziele", cursor: "Gro\xDFer Cursor", pause: "Anim. Stopp", reset: "Zur\xFCcksetzen", dev: "Entwickelt von", ls: "Buchst.abst.", cb: "Farbblindheit", focus: "Fokus", presets: "Profile", pLow: "Sehschw\xE4che", pDys: "Dyslexie", pMot: "Motorik", pCalm: "Ohne Ablenk.", level: (l, n, m) => `${l}, Stufe ${n} von ${m}` },
|
|
299
|
+
nl: { title: "Toegankelijkheid", close: "Sluiten", txt: "Tekst", size: "Grootte", lh: "Regelhoogte", align: "Uitlijning", font: "Leesbaar", dyslexia: "Dyslexie", vis: "Visueel", contrast: "Contrast", gray: "Grijstinten", hide: "Verberg Afb.", links: "Links", ori: "Ori\xEBntatie", guide: "Leesgids", mask: "Leesmasker", targets: "Grote Doelen", cursor: "Grote Cursor", pause: "Anim. Pauze", reset: "Resetten", dev: "Ontwikkeld door", ls: "Letterspatie", cb: "Kleurenblind", focus: "Focus", presets: "Profielen", pLow: "Slechtziend", pDys: "Dyslexie", pMot: "Motoriek", pCalm: "Geen Afleid.", level: (l, n, m) => `${l}, niveau ${n} van ${m}` },
|
|
300
|
+
sv: { title: "Tillg\xE4nglighet", close: "St\xE4ng", txt: "Text", size: "Storlek", lh: "Radh\xF6jd", align: "Justering", font: "L\xE4sbar", dyslexia: "Dyslexi", vis: "Visuell", contrast: "Kontrast", gray: "Gr\xE5skala", hide: "D\xF6lj Bilder", links: "L\xE4nkar", ori: "Orientering", guide: "L\xE4slinjal", mask: "L\xE4smask", targets: "Stora Ytor", cursor: "Stor Mark\xF6r", pause: "Pausa Anim.", reset: "\xC5terst\xE4ll", dev: "Utvecklad av", ls: "Bokstavsavst.", cb: "F\xE4rgblindhet", focus: "Fokus", presets: "Profiler", pLow: "Synneds\xE4tt.", pDys: "Dyslexi", pMot: "Motorik", pCalm: "Inga Distrak.", level: (l, n, m) => `${l}, niv\xE5 ${n} av ${m}` }
|
|
301
|
+
};
|
|
302
|
+
function getTranslation(locale) {
|
|
303
|
+
const lc = locale.toLowerCase();
|
|
304
|
+
if (lc === "gn" || lc.startsWith("gn-") || lc.startsWith("gn_")) return DICT.gn;
|
|
305
|
+
const base = lc.split(/[-_]/)[0];
|
|
306
|
+
return DICT[base] ?? DICT.en;
|
|
307
|
+
}
|
|
308
|
+
|
|
187
309
|
// src/behavior.ts
|
|
188
310
|
var MULTI_KEYS = {
|
|
189
311
|
"oks-zoom": "zoom",
|
|
@@ -210,37 +332,45 @@ var TOGGLE_KEYS = {
|
|
|
210
332
|
"oks-a11y-focus": "focusOutline",
|
|
211
333
|
"oks-a11y-bigtargets": "bigTargets"
|
|
212
334
|
};
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
};
|
|
335
|
+
function noopController() {
|
|
336
|
+
return Object.assign(() => {
|
|
337
|
+
}, { open() {
|
|
338
|
+
}, close() {
|
|
339
|
+
}, toggle() {
|
|
340
|
+
} });
|
|
341
|
+
}
|
|
342
|
+
function deepActiveElement() {
|
|
343
|
+
let a = document.activeElement;
|
|
344
|
+
while (a?.shadowRoot?.activeElement) a = a.shadowRoot.activeElement;
|
|
345
|
+
return a;
|
|
346
|
+
}
|
|
219
347
|
function bindPanelBehavior(root, opts = {}) {
|
|
220
348
|
const storageKey = opts.storageKey ?? "oksiacSettings";
|
|
349
|
+
const t = getTranslation(opts.locale ?? "en");
|
|
350
|
+
const enabled = opts.enabled ?? new Set(ALL_CONTROLS);
|
|
351
|
+
const nudgeMax = Math.max(0, opts.nudgeMax ?? 0);
|
|
221
352
|
const trigger = root.getElementById("oks-trigger");
|
|
222
353
|
const panel = root.getElementById("oks-panel");
|
|
223
354
|
const closeBtn = root.getElementById("oks-close");
|
|
224
355
|
const resetBtn = root.getElementById("oks-reset");
|
|
225
356
|
const wrapper = root.getElementById("oks-wrapper");
|
|
226
357
|
const opts$ = Array.from(root.querySelectorAll(".oks-access-opt, .oks-preset"));
|
|
227
|
-
if (!
|
|
228
|
-
return ()
|
|
229
|
-
};
|
|
358
|
+
if (!panel || !closeBtn || !resetBtn) {
|
|
359
|
+
return noopController();
|
|
230
360
|
}
|
|
231
361
|
let state = loadState(storageKey);
|
|
232
362
|
function applyState() {
|
|
233
363
|
const body = document.body;
|
|
234
|
-
const
|
|
364
|
+
const rootEl = document.documentElement;
|
|
235
365
|
for (const cls of Array.from(body.classList)) {
|
|
236
366
|
if (cls.startsWith("oks-")) body.classList.remove(cls);
|
|
237
367
|
}
|
|
238
|
-
[1, 2, 3].forEach((l) =>
|
|
368
|
+
[1, 2, 3].forEach((l) => rootEl.classList.remove(`oks-colorblind-${l}`));
|
|
239
369
|
if (state.zoom > 0) body.classList.add(`oks-zoom-${state.zoom}`);
|
|
240
370
|
if (state.lh > 0) body.classList.add(`oks-lh-${state.lh}`);
|
|
241
371
|
if (state.align > 0) body.classList.add(`oks-align-${state.align}`);
|
|
242
372
|
if (state.ls > 0) body.classList.add(`oks-ls-${state.ls}`);
|
|
243
|
-
if (state.colorblind > 0)
|
|
373
|
+
if (state.colorblind > 0) rootEl.classList.add(`oks-colorblind-${state.colorblind}`);
|
|
244
374
|
if (state.font) body.classList.add("oks-a11y-font");
|
|
245
375
|
if (state.dyslexia) body.classList.add("oks-dyslexia");
|
|
246
376
|
if (state.contrast) body.classList.add("oks-a11y-contrast");
|
|
@@ -268,6 +398,10 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
268
398
|
btn.setAttribute("data-level", String(lvl));
|
|
269
399
|
btn.classList.toggle("is-active", lvl > 0);
|
|
270
400
|
btn.setAttribute("aria-pressed", lvl > 0 ? "true" : "false");
|
|
401
|
+
const max = parseInt(btn.getAttribute("data-levels") ?? "0", 10);
|
|
402
|
+
const lbl = btn.querySelector(".oks-label")?.textContent?.trim() ?? "";
|
|
403
|
+
if (lvl > 0 && max > 0) btn.setAttribute("aria-label", t.level(lbl, lvl, max));
|
|
404
|
+
else btn.removeAttribute("aria-label");
|
|
271
405
|
} else if (action === "toggle") {
|
|
272
406
|
const klass = btn.getAttribute("data-class") ?? "";
|
|
273
407
|
const key = TOGGLE_KEYS[klass];
|
|
@@ -313,9 +447,8 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
313
447
|
} else if (action === "mask") {
|
|
314
448
|
state.readingMask = !state.readingMask;
|
|
315
449
|
} else if (action === "preset") {
|
|
316
|
-
const id = btn.getAttribute("data-preset")
|
|
317
|
-
|
|
318
|
-
if (preset) Object.assign(state, preset);
|
|
450
|
+
const id = btn.getAttribute("data-preset");
|
|
451
|
+
if (id && PRESETS[id]) Object.assign(state, filterPresetForEnabled(id, enabled));
|
|
319
452
|
btn.classList.add("is-flashing");
|
|
320
453
|
setTimeout(() => btn.classList.remove("is-flashing"), 250);
|
|
321
454
|
}
|
|
@@ -327,28 +460,50 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
327
460
|
applyState();
|
|
328
461
|
saveState(storageKey, state);
|
|
329
462
|
};
|
|
463
|
+
let opener = null;
|
|
464
|
+
let ignoreDocClose = false;
|
|
330
465
|
const openPanel = () => {
|
|
466
|
+
if (panel.classList.contains("is-open")) return;
|
|
467
|
+
opener = deepActiveElement();
|
|
331
468
|
panel.classList.add("is-open");
|
|
332
469
|
panel.removeAttribute("inert");
|
|
333
|
-
trigger
|
|
470
|
+
trigger?.setAttribute("aria-expanded", "true");
|
|
471
|
+
ignoreDocClose = true;
|
|
472
|
+
setTimeout(() => {
|
|
473
|
+
ignoreDocClose = false;
|
|
474
|
+
}, 0);
|
|
334
475
|
const first = panel.querySelector("button:not([disabled])");
|
|
335
476
|
first?.focus();
|
|
336
477
|
};
|
|
337
478
|
const closePanel = () => {
|
|
479
|
+
if (!panel.classList.contains("is-open")) return;
|
|
338
480
|
panel.classList.remove("is-open");
|
|
339
481
|
panel.setAttribute("inert", "");
|
|
340
|
-
trigger
|
|
341
|
-
trigger.focus();
|
|
482
|
+
trigger?.setAttribute("aria-expanded", "false");
|
|
483
|
+
if (trigger) trigger.focus();
|
|
484
|
+
else if (opener?.isConnected) opener.focus();
|
|
485
|
+
opener = null;
|
|
342
486
|
};
|
|
343
|
-
const
|
|
344
|
-
e.stopPropagation();
|
|
487
|
+
const togglePanel = () => {
|
|
345
488
|
if (panel.classList.contains("is-open")) closePanel();
|
|
346
489
|
else openPanel();
|
|
347
490
|
};
|
|
491
|
+
let suppressClick = false;
|
|
492
|
+
const onTriggerClick = (e) => {
|
|
493
|
+
e.stopPropagation();
|
|
494
|
+
if (suppressClick) {
|
|
495
|
+
suppressClick = false;
|
|
496
|
+
return;
|
|
497
|
+
}
|
|
498
|
+
togglePanel();
|
|
499
|
+
};
|
|
348
500
|
const onDocClick = (e) => {
|
|
349
501
|
if (!panel.classList.contains("is-open")) return;
|
|
502
|
+
if (ignoreDocClose) return;
|
|
350
503
|
const path = e.composedPath();
|
|
351
|
-
if (path.includes(panel)
|
|
504
|
+
if (path.includes(panel)) return;
|
|
505
|
+
if (trigger && path.includes(trigger)) return;
|
|
506
|
+
if (wrapper && path.includes(wrapper)) return;
|
|
352
507
|
closePanel();
|
|
353
508
|
};
|
|
354
509
|
const onKeyDown = (e) => {
|
|
@@ -375,6 +530,88 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
375
530
|
}
|
|
376
531
|
}
|
|
377
532
|
};
|
|
533
|
+
const NUDGE_KEY = `${storageKey}::pos`;
|
|
534
|
+
const clampMax = (v) => Math.max(-nudgeMax, Math.min(nudgeMax, v));
|
|
535
|
+
let nudge = { x: 0, y: 0 };
|
|
536
|
+
const loadNudge = () => {
|
|
537
|
+
if (nudgeMax <= 0) return;
|
|
538
|
+
try {
|
|
539
|
+
const raw = localStorage.getItem(NUDGE_KEY);
|
|
540
|
+
if (!raw) return;
|
|
541
|
+
const p = JSON.parse(raw);
|
|
542
|
+
if (typeof p?.x === "number" && typeof p?.y === "number") {
|
|
543
|
+
nudge = { x: clampMax(p.x), y: clampMax(p.y) };
|
|
544
|
+
}
|
|
545
|
+
} catch {
|
|
546
|
+
}
|
|
547
|
+
};
|
|
548
|
+
const saveNudge = () => {
|
|
549
|
+
try {
|
|
550
|
+
if (nudge.x === 0 && nudge.y === 0) localStorage.removeItem(NUDGE_KEY);
|
|
551
|
+
else localStorage.setItem(NUDGE_KEY, JSON.stringify(nudge));
|
|
552
|
+
} catch {
|
|
553
|
+
}
|
|
554
|
+
};
|
|
555
|
+
const applyNudge = () => {
|
|
556
|
+
if (wrapper) wrapper.style.translate = `${nudge.x}px ${nudge.y}px`;
|
|
557
|
+
};
|
|
558
|
+
const clampOffset = (x, y) => {
|
|
559
|
+
x = clampMax(x);
|
|
560
|
+
y = clampMax(y);
|
|
561
|
+
if (wrapper) {
|
|
562
|
+
const r = wrapper.getBoundingClientRect();
|
|
563
|
+
const pad = 4;
|
|
564
|
+
const baseLeft = r.left - nudge.x;
|
|
565
|
+
const baseTop = r.top - nudge.y;
|
|
566
|
+
const vw = window.innerWidth || 0;
|
|
567
|
+
const vh = window.innerHeight || 0;
|
|
568
|
+
if (r.width > 0 && vw > 0) x = Math.max(pad - baseLeft, Math.min(vw - pad - r.width - baseLeft, x));
|
|
569
|
+
if (r.height > 0 && vh > 0) y = Math.max(pad - baseTop, Math.min(vh - pad - r.height - baseTop, y));
|
|
570
|
+
}
|
|
571
|
+
return { x, y };
|
|
572
|
+
};
|
|
573
|
+
let dragStart = null;
|
|
574
|
+
let didDrag = false;
|
|
575
|
+
const DRAG_THRESHOLD = 4;
|
|
576
|
+
const onPointerDown = (e) => {
|
|
577
|
+
if (nudgeMax <= 0 || e.button !== 0) return;
|
|
578
|
+
dragStart = { px: e.clientX, py: e.clientY, ox: nudge.x, oy: nudge.y };
|
|
579
|
+
didDrag = false;
|
|
580
|
+
trigger?.setPointerCapture?.(e.pointerId);
|
|
581
|
+
};
|
|
582
|
+
const onPointerMove = (e) => {
|
|
583
|
+
if (!dragStart) return;
|
|
584
|
+
const dx = e.clientX - dragStart.px;
|
|
585
|
+
const dy = e.clientY - dragStart.py;
|
|
586
|
+
if (!didDrag && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
|
|
587
|
+
didDrag = true;
|
|
588
|
+
nudge = clampOffset(dragStart.ox + dx, dragStart.oy + dy);
|
|
589
|
+
applyNudge();
|
|
590
|
+
e.preventDefault();
|
|
591
|
+
};
|
|
592
|
+
const onPointerUp = () => {
|
|
593
|
+
if (!dragStart) return;
|
|
594
|
+
dragStart = null;
|
|
595
|
+
if (didDrag) {
|
|
596
|
+
saveNudge();
|
|
597
|
+
suppressClick = true;
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
const onTriggerKey = (e) => {
|
|
601
|
+
if (nudgeMax <= 0) return;
|
|
602
|
+
const step = e.shiftKey ? 1 : 10;
|
|
603
|
+
let dx = 0;
|
|
604
|
+
let dy = 0;
|
|
605
|
+
if (e.key === "ArrowLeft") dx = -step;
|
|
606
|
+
else if (e.key === "ArrowRight") dx = step;
|
|
607
|
+
else if (e.key === "ArrowUp") dy = -step;
|
|
608
|
+
else if (e.key === "ArrowDown") dy = step;
|
|
609
|
+
else return;
|
|
610
|
+
e.preventDefault();
|
|
611
|
+
nudge = clampOffset(nudge.x + dx, nudge.y + dy);
|
|
612
|
+
applyNudge();
|
|
613
|
+
saveNudge();
|
|
614
|
+
};
|
|
378
615
|
const onMove = (e) => {
|
|
379
616
|
if (!state.readingGuide && !state.readingMask) return;
|
|
380
617
|
const y = e.touches?.[0]?.clientY ?? e.clientY;
|
|
@@ -388,7 +625,7 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
388
625
|
if (mask) mask.style.setProperty("--oks-mask-y", `${y}px`);
|
|
389
626
|
}
|
|
390
627
|
};
|
|
391
|
-
trigger
|
|
628
|
+
trigger?.addEventListener("click", onTriggerClick);
|
|
392
629
|
closeBtn.addEventListener("click", closePanel);
|
|
393
630
|
resetBtn.addEventListener("click", onReset);
|
|
394
631
|
for (const btn of opts$) btn.addEventListener("click", onOptClick);
|
|
@@ -396,9 +633,19 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
396
633
|
document.addEventListener("keydown", onKeyDown);
|
|
397
634
|
document.addEventListener("mousemove", onMove);
|
|
398
635
|
document.addEventListener("touchmove", onMove, { passive: true });
|
|
636
|
+
if (nudgeMax > 0 && trigger) {
|
|
637
|
+
loadNudge();
|
|
638
|
+
applyNudge();
|
|
639
|
+
trigger.style.cursor = "grab";
|
|
640
|
+
trigger.style.touchAction = "none";
|
|
641
|
+
trigger.addEventListener("pointerdown", onPointerDown);
|
|
642
|
+
document.addEventListener("pointermove", onPointerMove);
|
|
643
|
+
document.addEventListener("pointerup", onPointerUp);
|
|
644
|
+
trigger.addEventListener("keydown", onTriggerKey);
|
|
645
|
+
}
|
|
399
646
|
applyState();
|
|
400
|
-
|
|
401
|
-
trigger
|
|
647
|
+
const dispose = () => {
|
|
648
|
+
trigger?.removeEventListener("click", onTriggerClick);
|
|
402
649
|
closeBtn.removeEventListener("click", closePanel);
|
|
403
650
|
resetBtn.removeEventListener("click", onReset);
|
|
404
651
|
for (const btn of opts$) btn.removeEventListener("click", onOptClick);
|
|
@@ -406,7 +653,12 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
406
653
|
document.removeEventListener("keydown", onKeyDown);
|
|
407
654
|
document.removeEventListener("mousemove", onMove);
|
|
408
655
|
document.removeEventListener("touchmove", onMove);
|
|
656
|
+
trigger?.removeEventListener("pointerdown", onPointerDown);
|
|
657
|
+
document.removeEventListener("pointermove", onPointerMove);
|
|
658
|
+
document.removeEventListener("pointerup", onPointerUp);
|
|
659
|
+
trigger?.removeEventListener("keydown", onTriggerKey);
|
|
409
660
|
};
|
|
661
|
+
return Object.assign(dispose, { open: openPanel, close: closePanel, toggle: togglePanel });
|
|
410
662
|
}
|
|
411
663
|
function ensureOverlay() {
|
|
412
664
|
let el = document.getElementById("oks-overlay-gray");
|
|
@@ -461,6 +713,12 @@ var PANEL_CSS = `
|
|
|
461
713
|
width: 60%;
|
|
462
714
|
height: 60%;
|
|
463
715
|
}
|
|
716
|
+
/* Porthole preset: a brushed-metal ring framing the standard glyph. The glyph
|
|
717
|
+
itself is untouched (discoverability), only the button gets the frame.
|
|
718
|
+
Hosts wanting a bespoke frame can target ::part(trigger) from light DOM. */
|
|
719
|
+
.oks-access-btn[data-trigger-icon="porthole"] {
|
|
720
|
+
box-shadow: 0 0 0 4px #b0bec5, 0 0 0 7px #546e7a, 0 6px 18px rgba(0,0,0,0.3);
|
|
721
|
+
}
|
|
464
722
|
.oks-active-badge {
|
|
465
723
|
position: absolute;
|
|
466
724
|
top: -2px;
|
|
@@ -629,6 +887,12 @@ var PANEL_CSS = `
|
|
|
629
887
|
.oks-access-branding { margin-top: 12px; font-size: 12px; color: #000; font-weight: 700; }
|
|
630
888
|
.oks-access-branding a { color: #000; text-decoration: none; border-bottom: 1px dotted #000; }
|
|
631
889
|
|
|
890
|
+
@media (prefers-reduced-motion: reduce) {
|
|
891
|
+
.oks-access-btn, .oks-access-panel, .oks-access-close,
|
|
892
|
+
.oks-preset, .oks-access-opt { transition: none; }
|
|
893
|
+
.oks-access-btn:hover { transform: scale(1); }
|
|
894
|
+
.oks-preset.is-flashing { transform: none; }
|
|
895
|
+
}
|
|
632
896
|
@media (max-width: 768px) {
|
|
633
897
|
.oks-access-panel {
|
|
634
898
|
width: 100%; height: 100%; max-height: 100%;
|
|
@@ -806,26 +1070,20 @@ body.oks-a11y-bigtargets summary:not(oksigenia-access-panel):not(oksigenia-acces
|
|
|
806
1070
|
}
|
|
807
1071
|
`;
|
|
808
1072
|
|
|
809
|
-
// src/translations.ts
|
|
810
|
-
var DICT = {
|
|
811
|
-
es: { title: "Accesibilidad", close: "Cerrar", txt: "Texto", size: "Tama\xF1o", lh: "Interlineado", align: "Alineaci\xF3n", font: "Legible", dyslexia: "Fuente Dislexia", vis: "Visual", contrast: "Contraste", gray: "Grises", hide: "Ocultar Img", links: "Links", ori: "Orientaci\xF3n", guide: "Gu\xEDa", mask: "M\xE1scara", targets: "\xC1reas Grandes", cursor: "Cursor Grande", pause: "Parar Animac.", reset: "Restablecer Todo", dev: "Desarrollado por", ls: "Espaciado", cb: "Daltonismo", focus: "Foco", presets: "Perfiles", pLow: "Baja Visi\xF3n", pDys: "Dislexia", pMot: "Motor", pCalm: "Sin Distrac." },
|
|
812
|
-
en: { title: "Accessibility", close: "Close", txt: "Text", size: "Size", lh: "Line Height", align: "Align", font: "Readable Font", dyslexia: "Dyslexia Font", vis: "Visual", contrast: "Contrast", gray: "Grayscale", hide: "Hide Images", links: "Highlight Links", ori: "Orientation", guide: "Reading Guide", mask: "Reading Mask", targets: "Big Targets", cursor: "Big Cursor", pause: "Pause Anim.", reset: "Reset All", dev: "Developed by", ls: "Letter Spacing", cb: "Color Blind", focus: "Focus", presets: "Profiles", pLow: "Low Vision", pDys: "Dyslexia", pMot: "Motor", pCalm: "No Distract." },
|
|
813
|
-
gn: { title: "Oikeha (Accesibilidad)", close: "Mboty", txt: "Mo\xF1e'\u1EBDr\xE3", size: "Tuichakue", lh: "Jei", align: "Mbojoja", font: "Letra Por\xE3", dyslexia: "Dislexia", vis: "Hechapy", contrast: "Sa'y", gray: "H\u0169 ha T\u0129", hide: "Moka\xF1y Ta'anga", links: "Joajuha", ori: "S\xE3mbyhy", guide: "S\xE3mbyhyha", mask: "Mbohov\xE1i", targets: "Tenda Guasu", cursor: "Cursor Guasu", pause: "Mboopyta", reset: "Mbojevy", dev: "Apojare", ls: "Rapykue", cb: "Sa'yvy", focus: "\xD1emoha", presets: "Tekor\xE3", pLow: "Hechapy Vai", pDys: "Dislexia", pMot: "Po-rehegua", pCalm: "Py\u02BCa Guapy" },
|
|
814
|
-
fr: { title: "Accessibilit\xE9", close: "Fermer", txt: "Texte", size: "Taille", lh: "Interligne", align: "Alignement", font: "Police Lisible", dyslexia: "Police Dyslexie", vis: "Visuel", contrast: "Contraste", gray: "Niveaux Gris", hide: "Masquer Img", links: "Liens", ori: "Orientation", guide: "Guide Lecture", mask: "Masque Lecture", targets: "Grandes Cibles", cursor: "Grand Curseur", pause: "Pause Anim.", reset: "R\xE9initialiser", dev: "D\xE9velopp\xE9 par", ls: "Espacement", cb: "Daltonisme", focus: "Focus", presets: "Profils", pLow: "Basse Vision", pDys: "Dyslexie", pMot: "Moteur", pCalm: "Sans Distrac." },
|
|
815
|
-
it: { title: "Accessibilit\xE0", close: "Chiudi", txt: "Testo", size: "Dimensione", lh: "Interlinea", align: "Allineamento", font: "Leggibile", dyslexia: "Font Dislessia", vis: "Visivo", contrast: "Contrasto", gray: "Scala Grig.", hide: "Nascondi Img", links: "Link", ori: "Orientamento", guide: "Guida", mask: "Maschera", targets: "Aree Grandi", cursor: "Cursore Grande", pause: "Pausa Anim.", reset: "Reimposta", dev: "Sviluppato da", ls: "Spaziatura", cb: "Daltonismo", focus: "Focus", presets: "Profili", pLow: "Ipovisione", pDys: "Dislessia", pMot: "Motorio", pCalm: "No Distraz." },
|
|
816
|
-
de: { title: "Barrierefreiheit", close: "Schlie\xDFen", txt: "Text", size: "Gr\xF6\xDFe", lh: "Zeilenh\xF6he", align: "Ausrichtung", font: "Lesbar", dyslexia: "Dyslexie", vis: "Visuell", contrast: "Kontrast", gray: "Graustufen", hide: "Bilder Aus", links: "Links", ori: "Orientierung", guide: "Lesehilfe", mask: "Lesemaske", targets: "Gro\xDFe Ziele", cursor: "Gro\xDFer Cursor", pause: "Anim. Stopp", reset: "Zur\xFCcksetzen", dev: "Entwickelt von", ls: "Buchst.abst.", cb: "Farbblindheit", focus: "Fokus", presets: "Profile", pLow: "Sehschw\xE4che", pDys: "Dyslexie", pMot: "Motorik", pCalm: "Ohne Ablenk." },
|
|
817
|
-
nl: { title: "Toegankelijkheid", close: "Sluiten", txt: "Tekst", size: "Grootte", lh: "Regelhoogte", align: "Uitlijning", font: "Leesbaar", dyslexia: "Dyslexie", vis: "Visueel", contrast: "Contrast", gray: "Grijstinten", hide: "Verberg Afb.", links: "Links", ori: "Ori\xEBntatie", guide: "Leesgids", mask: "Leesmasker", targets: "Grote Doelen", cursor: "Grote Cursor", pause: "Anim. Pauze", reset: "Resetten", dev: "Ontwikkeld door", ls: "Letterspatie", cb: "Kleurenblind", focus: "Focus", presets: "Profielen", pLow: "Slechtziend", pDys: "Dyslexie", pMot: "Motoriek", pCalm: "Geen Afleid." },
|
|
818
|
-
sv: { title: "Tillg\xE4nglighet", close: "St\xE4ng", txt: "Text", size: "Storlek", lh: "Radh\xF6jd", align: "Justering", font: "L\xE4sbar", dyslexia: "Dyslexi", vis: "Visuell", contrast: "Kontrast", gray: "Gr\xE5skala", hide: "D\xF6lj Bilder", links: "L\xE4nkar", ori: "Orientering", guide: "L\xE4slinjal", mask: "L\xE4smask", targets: "Stora Ytor", cursor: "Stor Mark\xF6r", pause: "Pausa Anim.", reset: "\xC5terst\xE4ll", dev: "Utvecklad av", ls: "Bokstavsavst.", cb: "F\xE4rgblindhet", focus: "Fokus", presets: "Profiler", pLow: "Synneds\xE4tt.", pDys: "Dyslexi", pMot: "Motorik", pCalm: "Inga Distrak." }
|
|
819
|
-
};
|
|
820
|
-
function getTranslation(locale) {
|
|
821
|
-
const lc = locale.toLowerCase();
|
|
822
|
-
if (lc === "gn" || lc.startsWith("gn-") || lc.startsWith("gn_")) return DICT.gn;
|
|
823
|
-
const base = lc.split(/[-_]/)[0];
|
|
824
|
-
return DICT[base] ?? DICT.en;
|
|
825
|
-
}
|
|
826
|
-
|
|
827
1073
|
// src/web-component.ts
|
|
828
|
-
var OBSERVED = [
|
|
1074
|
+
var OBSERVED = [
|
|
1075
|
+
"locale",
|
|
1076
|
+
"position",
|
|
1077
|
+
"position-mobile",
|
|
1078
|
+
"trigger-icon",
|
|
1079
|
+
"storage-key",
|
|
1080
|
+
"controls",
|
|
1081
|
+
"exclude",
|
|
1082
|
+
"trigger",
|
|
1083
|
+
"effects-exclude",
|
|
1084
|
+
"nudge",
|
|
1085
|
+
"presets"
|
|
1086
|
+
];
|
|
829
1087
|
var STYLE_ID = "oksigenia-access-effects";
|
|
830
1088
|
var FILTERS_ID = "oksigenia-access-filters";
|
|
831
1089
|
var GUIDE_ID = "oks-reading-guide";
|
|
@@ -860,18 +1118,23 @@ function ensureGlobalStyles() {
|
|
|
860
1118
|
}
|
|
861
1119
|
var VALID_POSITIONS = [
|
|
862
1120
|
"top-left",
|
|
1121
|
+
"top-center",
|
|
863
1122
|
"top-right",
|
|
864
1123
|
"mid-left",
|
|
1124
|
+
"mid-center",
|
|
865
1125
|
"mid-right",
|
|
866
1126
|
"bottom-left",
|
|
1127
|
+
"bottom-center",
|
|
867
1128
|
"bottom-right"
|
|
868
1129
|
];
|
|
869
|
-
var VALID_ICONS = ["vitruvian", "wheelchair", "eye", "universal"];
|
|
1130
|
+
var VALID_ICONS = ["vitruvian", "wheelchair", "eye", "universal", "porthole"];
|
|
1131
|
+
var fxSeq = 0;
|
|
870
1132
|
var OksigeniaAccessPanelElement = class extends HTMLElement {
|
|
871
1133
|
static get observedAttributes() {
|
|
872
1134
|
return OBSERVED;
|
|
873
1135
|
}
|
|
874
|
-
|
|
1136
|
+
_controller = null;
|
|
1137
|
+
_fxId = `oks-access-fx-${++fxSeq}`;
|
|
875
1138
|
constructor() {
|
|
876
1139
|
super();
|
|
877
1140
|
this.attachShadow({ mode: "open" });
|
|
@@ -881,12 +1144,24 @@ var OksigeniaAccessPanelElement = class extends HTMLElement {
|
|
|
881
1144
|
this.render();
|
|
882
1145
|
}
|
|
883
1146
|
disconnectedCallback() {
|
|
884
|
-
this.
|
|
885
|
-
this.
|
|
1147
|
+
this._controller?.();
|
|
1148
|
+
this._controller = null;
|
|
1149
|
+
if (typeof document !== "undefined") document.getElementById(this._fxId)?.remove();
|
|
886
1150
|
}
|
|
887
1151
|
attributeChangedCallback() {
|
|
888
1152
|
if (this.isConnected) this.render();
|
|
889
1153
|
}
|
|
1154
|
+
/** Imperative API (#2): open / close / toggle the panel from the host.
|
|
1155
|
+
* Useful with `trigger="none"`, where the host drives its own button. */
|
|
1156
|
+
open() {
|
|
1157
|
+
this._controller?.open();
|
|
1158
|
+
}
|
|
1159
|
+
close() {
|
|
1160
|
+
this._controller?.close();
|
|
1161
|
+
}
|
|
1162
|
+
toggle() {
|
|
1163
|
+
this._controller?.toggle();
|
|
1164
|
+
}
|
|
890
1165
|
getPosition() {
|
|
891
1166
|
const attr = this.getAttribute("position") ?? "mid-left";
|
|
892
1167
|
return VALID_POSITIONS.includes(attr) ? attr : "mid-left";
|
|
@@ -902,21 +1177,59 @@ var OksigeniaAccessPanelElement = class extends HTMLElement {
|
|
|
902
1177
|
getLocale() {
|
|
903
1178
|
return this.getAttribute("locale") ?? (typeof navigator !== "undefined" ? navigator.language : "en");
|
|
904
1179
|
}
|
|
1180
|
+
/** `nudge` (bare) ⇒ default 80px cap; `nudge="50"` ⇒ 50; absent/invalid ⇒ off. */
|
|
1181
|
+
getNudgeMax() {
|
|
1182
|
+
if (!this.hasAttribute("nudge")) return 0;
|
|
1183
|
+
const raw = this.getAttribute("nudge");
|
|
1184
|
+
if (raw == null || raw.trim() === "") return 80;
|
|
1185
|
+
const n = parseInt(raw, 10);
|
|
1186
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
1187
|
+
}
|
|
1188
|
+
/** Inject the per-instance effects-exclude rule (#4): under high-contrast,
|
|
1189
|
+
* drop the destructive filter from selectors the host marks as essential
|
|
1190
|
+
* media (video, canvas, scientific imagery). Grayscale is handled by not
|
|
1191
|
+
* offering the control (curation), not here. */
|
|
1192
|
+
updateEffectsExclude() {
|
|
1193
|
+
if (typeof document === "undefined") return;
|
|
1194
|
+
const sel = (this.getAttribute("effects-exclude") ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
1195
|
+
const existing = document.getElementById(this._fxId);
|
|
1196
|
+
if (sel.length === 0) {
|
|
1197
|
+
existing?.remove();
|
|
1198
|
+
return;
|
|
1199
|
+
}
|
|
1200
|
+
const style = existing ?? document.createElement("style");
|
|
1201
|
+
if (!existing) {
|
|
1202
|
+
style.id = this._fxId;
|
|
1203
|
+
document.head.appendChild(style);
|
|
1204
|
+
}
|
|
1205
|
+
const list = sel.join(", ");
|
|
1206
|
+
style.textContent = `body.oks-a11y-contrast :is(${list}), body.oks-a11y-contrast :is(${list}) * { filter: none !important; }`;
|
|
1207
|
+
}
|
|
905
1208
|
render() {
|
|
906
1209
|
const shadow = this.shadowRoot;
|
|
907
1210
|
if (!shadow) return;
|
|
908
|
-
this.
|
|
1211
|
+
this._controller?.();
|
|
909
1212
|
const position = this.getPosition();
|
|
910
1213
|
const positionMobile = this.getPositionMobile();
|
|
1214
|
+
const enabled = resolveEnabledControls(this.getAttribute("controls"), this.getAttribute("exclude"));
|
|
1215
|
+
const showTrigger = this.getAttribute("trigger") !== "none";
|
|
1216
|
+
const showPresets = (this.getAttribute("presets") ?? "").trim().toLowerCase() !== "none";
|
|
911
1217
|
const html = buildPanelHtml({
|
|
912
1218
|
t: getTranslation(this.getLocale()),
|
|
913
1219
|
triggerIcon: this.getTriggerIcon(),
|
|
914
|
-
position
|
|
1220
|
+
position,
|
|
1221
|
+
enabled,
|
|
1222
|
+
showTrigger,
|
|
1223
|
+
showPresets
|
|
915
1224
|
});
|
|
916
1225
|
shadow.innerHTML = `<style>${PANEL_CSS}${positionCss(position, positionMobile)}</style>${html}`;
|
|
917
|
-
this.
|
|
918
|
-
storageKey: this.getAttribute("storage-key") ?? void 0
|
|
1226
|
+
this._controller = bindPanelBehavior(shadow, {
|
|
1227
|
+
storageKey: this.getAttribute("storage-key") ?? void 0,
|
|
1228
|
+
locale: this.getLocale(),
|
|
1229
|
+
enabled,
|
|
1230
|
+
nudgeMax: this.getNudgeMax()
|
|
919
1231
|
});
|
|
1232
|
+
this.updateEffectsExclude();
|
|
920
1233
|
}
|
|
921
1234
|
};
|
|
922
1235
|
if (typeof customElements !== "undefined" && !customElements.get("oksigenia-access-panel")) {
|