@oksigenia/access-panel 0.4.4 → 0.6.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 +31 -0
- package/README.md +88 -5
- package/dist/index.d.ts +90 -21
- package/dist/index.js +437 -86
- package/dist/index.js.map +1 -1
- package/dist/web-component.d.ts +19 -1
- package/dist/web-component.js +538 -94
- package/dist/web-component.js.map +1 -1
- package/package.json +1 -1
package/dist/web-component.js
CHANGED
|
@@ -1,3 +1,88 @@
|
|
|
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
|
+
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
|
+
}
|
|
75
|
+
function filterPresetForEnabled(id, enabled) {
|
|
76
|
+
const out = {};
|
|
77
|
+
for (const [k, v] of Object.entries(PRESETS[id])) {
|
|
78
|
+
if (enabled.has(STATE_KEY_CONTROL[k])) out[k] = v;
|
|
79
|
+
}
|
|
80
|
+
return out;
|
|
81
|
+
}
|
|
82
|
+
function presetIsAvailable(id, enabled) {
|
|
83
|
+
return presetControlIds(id).filter((c) => enabled.has(c)).length >= 2;
|
|
84
|
+
}
|
|
85
|
+
|
|
1
86
|
// src/icons.ts
|
|
2
87
|
var ICON_TXT = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M2.5,4v3h5v12h3V7h5V4H2.5z M21.5,9h-9v3h3v7h3v-7h3V9z"/></svg>';
|
|
3
88
|
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 +101,100 @@ var ICON_COLORBLIND = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12
|
|
|
16
101
|
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
102
|
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
103
|
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>';
|
|
104
|
+
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
105
|
var TRIGGER_ICONS = {
|
|
20
106
|
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
107
|
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
108
|
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:
|
|
109
|
+
universal: UNIVERSAL_GLYPH,
|
|
110
|
+
porthole: UNIVERSAL_GLYPH
|
|
24
111
|
};
|
|
25
112
|
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
113
|
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
114
|
|
|
28
115
|
// src/render.ts
|
|
116
|
+
var PRESET_ICON = {
|
|
117
|
+
lowvision: ICON_CONTRAST,
|
|
118
|
+
dyslexia: ICON_DYSLEXIA,
|
|
119
|
+
motor: ICON_CURSOR,
|
|
120
|
+
calm: ICON_HIDE
|
|
121
|
+
};
|
|
29
122
|
function buildPanelHtml(opts) {
|
|
30
123
|
const { t, triggerIcon } = opts;
|
|
124
|
+
const enabled = opts.enabled ?? new Set(ALL_CONTROLS);
|
|
125
|
+
const showTrigger = opts.showTrigger ?? true;
|
|
126
|
+
const showPresets = opts.showPresets ?? true;
|
|
31
127
|
const trig = TRIGGER_ICONS[triggerIcon];
|
|
32
|
-
const
|
|
33
|
-
|
|
128
|
+
const multi = (id, prefix, levels, label, icon, full = false) => {
|
|
129
|
+
if (!enabled.has(id)) return "";
|
|
130
|
+
const action = id === "colorblind" ? "colorblind" : "multi";
|
|
34
131
|
const fullClass = full ? " full-width" : "";
|
|
35
132
|
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>`;
|
|
133
|
+
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>`;
|
|
37
134
|
};
|
|
38
|
-
const toggle = (klass, label, icon) =>
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
const
|
|
43
|
-
|
|
135
|
+
const toggle = (id, klass, label, icon) => {
|
|
136
|
+
if (!enabled.has(id)) return "";
|
|
137
|
+
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>`;
|
|
138
|
+
};
|
|
139
|
+
const overlay = (id, target, label, icon) => {
|
|
140
|
+
if (!enabled.has(id)) return "";
|
|
141
|
+
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>`;
|
|
142
|
+
};
|
|
143
|
+
const simple = (id, action, label, icon) => {
|
|
144
|
+
if (!enabled.has(id)) return "";
|
|
145
|
+
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>`;
|
|
146
|
+
};
|
|
147
|
+
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>`;
|
|
148
|
+
const section = (title, rows) => {
|
|
149
|
+
const kept = rows.filter(Boolean);
|
|
150
|
+
if (kept.length === 0) return "";
|
|
151
|
+
return `<h4 class="oks-access-title">${escapeHtml(title)}</h4><div class="oks-access-grid">${kept.join("")}</div>`;
|
|
152
|
+
};
|
|
153
|
+
const presetLabels = {
|
|
154
|
+
lowvision: t.pLow,
|
|
155
|
+
dyslexia: t.pDys,
|
|
156
|
+
motor: t.pMot,
|
|
157
|
+
calm: t.pCalm
|
|
158
|
+
};
|
|
159
|
+
const presetButtons = showPresets ? PRESET_IDS.filter((id) => presetIsAvailable(id, enabled)).map((id) => preset(id, presetLabels[id])) : [];
|
|
160
|
+
const presetsBlock = presetButtons.length ? `<h4 class="oks-access-title">${escapeHtml(t.presets)}</h4><div class="oks-access-presets">${presetButtons.join("")}</div>` : "";
|
|
161
|
+
const triggerBlock = showTrigger ? `
|
|
44
162
|
<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">
|
|
163
|
+
<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
164
|
${trig}
|
|
47
165
|
</button>
|
|
48
166
|
<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
|
|
167
|
+
</div>` : "";
|
|
168
|
+
return `${triggerBlock}
|
|
50
169
|
<div class="oks-access-panel" id="oks-panel" role="dialog" aria-modal="true" aria-labelledby="oks-panel-title" inert>
|
|
51
170
|
<div class="oks-access-header">
|
|
52
171
|
<h3 id="oks-panel-title">${escapeHtml(t.title)}</h3>
|
|
53
172
|
<button class="oks-access-close" id="oks-close" aria-label="${escapeAttr(t.close)}" type="button">${ICON_CLOSE}</button>
|
|
54
173
|
</div>
|
|
55
174
|
<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)
|
|
175
|
+
${presetsBlock}
|
|
176
|
+
${section(t.txt, [
|
|
177
|
+
multi("text-size", "oks-zoom", 4, t.size, ICON_TXT),
|
|
178
|
+
multi("line-height", "oks-lh", 3, t.lh, ICON_LH),
|
|
179
|
+
multi("text-align", "oks-align", 3, t.align, ICON_ALIGN),
|
|
180
|
+
toggle("readable-font", "oks-a11y-font", t.font, ICON_FONT),
|
|
181
|
+
toggle("dyslexia-font", "oks-dyslexia", t.dyslexia, ICON_DYSLEXIA),
|
|
182
|
+
multi("letter-spacing", "oks-ls", 3, t.ls, ICON_LS)
|
|
71
183
|
])}
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
toggle("oks-a11y-
|
|
77
|
-
toggle("oks-a11y-links", t.links, ICON_LINK),
|
|
184
|
+
${section(t.vis, [
|
|
185
|
+
toggle("contrast", "oks-a11y-contrast", t.contrast, ICON_CONTRAST),
|
|
186
|
+
overlay("grayscale", "oks-overlay-gray", t.gray, ICON_GRAY),
|
|
187
|
+
toggle("hide-images", "oks-a11y-hide", t.hide, ICON_HIDE),
|
|
188
|
+
toggle("highlight-links", "oks-a11y-links", t.links, ICON_LINK),
|
|
78
189
|
multi("colorblind", "oks-colorblind", 3, t.cb, ICON_COLORBLIND, true)
|
|
79
190
|
])}
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
toggle("oks-
|
|
85
|
-
toggle("oks-a11y-
|
|
86
|
-
toggle("oks-a11y-
|
|
87
|
-
toggle("oks-a11y-focus", t.focus, ICON_FOCUS)
|
|
191
|
+
${section(t.ori, [
|
|
192
|
+
simple("reading-guide", "guide", t.guide, ICON_GUIDE),
|
|
193
|
+
simple("reading-mask", "mask", t.mask, ICON_MASK),
|
|
194
|
+
toggle("big-cursor", "oks-big-cursor", t.cursor, ICON_CURSOR),
|
|
195
|
+
toggle("big-targets", "oks-a11y-bigtargets", t.targets, ICON_TARGETS),
|
|
196
|
+
toggle("pause-anim", "oks-a11y-pause", t.pause, ICON_PAUSE),
|
|
197
|
+
toggle("focus", "oks-a11y-focus", t.focus, ICON_FOCUS)
|
|
88
198
|
])}
|
|
89
199
|
</div>
|
|
90
200
|
<div class="oks-access-footer">
|
|
@@ -109,14 +219,20 @@ function positionRules(position) {
|
|
|
109
219
|
switch (position) {
|
|
110
220
|
case "top-left":
|
|
111
221
|
return { wrap: "top: 20px; left: 20px;", panel: "top: 80px; left: 20px;" };
|
|
222
|
+
case "top-center":
|
|
223
|
+
return { wrap: "top: 20px; left: 50%; transform: translateX(-50%);", panel: "top: 80px; left: 50%; transform: translateX(-50%);" };
|
|
112
224
|
case "top-right":
|
|
113
225
|
return { wrap: "top: 20px; right: 20px;", panel: "top: 80px; right: 20px;" };
|
|
114
226
|
case "mid-left":
|
|
115
227
|
return { wrap: "top: 50%; left: 20px; transform: translateY(-50%);", panel: "top: 50%; left: 90px; transform: translateY(-50%);" };
|
|
228
|
+
case "mid-center":
|
|
229
|
+
return { wrap: "top: 50%; left: 50%; transform: translate(-50%, -50%);", panel: "top: 50%; left: 50%; transform: translate(-50%, -50%);" };
|
|
116
230
|
case "mid-right":
|
|
117
231
|
return { wrap: "top: 50%; right: 20px; transform: translateY(-50%);", panel: "top: 50%; right: 90px; transform: translateY(-50%);" };
|
|
118
232
|
case "bottom-left":
|
|
119
233
|
return { wrap: "bottom: 20px; left: 20px;", panel: "bottom: 100px; left: 20px;" };
|
|
234
|
+
case "bottom-center":
|
|
235
|
+
return { wrap: "bottom: 20px; left: 50%; transform: translateX(-50%);", panel: "bottom: 100px; left: 50%; transform: translateX(-50%);" };
|
|
120
236
|
case "bottom-right":
|
|
121
237
|
return { wrap: "bottom: 20px; right: 20px;", panel: "bottom: 100px; right: 20px;" };
|
|
122
238
|
}
|
|
@@ -228,51 +344,64 @@ var TOGGLE_KEYS = {
|
|
|
228
344
|
"oks-a11y-focus": "focusOutline",
|
|
229
345
|
"oks-a11y-bigtargets": "bigTargets"
|
|
230
346
|
};
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
};
|
|
347
|
+
function noopController() {
|
|
348
|
+
return Object.assign(() => {
|
|
349
|
+
}, { open() {
|
|
350
|
+
}, close() {
|
|
351
|
+
}, toggle() {
|
|
352
|
+
} });
|
|
353
|
+
}
|
|
354
|
+
function deepActiveElement() {
|
|
355
|
+
let a = document.activeElement;
|
|
356
|
+
while (a?.shadowRoot?.activeElement) a = a.shadowRoot.activeElement;
|
|
357
|
+
return a;
|
|
358
|
+
}
|
|
237
359
|
function bindPanelBehavior(root, opts = {}) {
|
|
238
360
|
const storageKey = opts.storageKey ?? "oksiacSettings";
|
|
239
361
|
const t = getTranslation(opts.locale ?? "en");
|
|
362
|
+
const enabled = opts.enabled ?? new Set(ALL_CONTROLS);
|
|
363
|
+
const nudgeMax = Math.max(0, opts.nudgeMax ?? 0);
|
|
240
364
|
const trigger = root.getElementById("oks-trigger");
|
|
241
365
|
const panel = root.getElementById("oks-panel");
|
|
242
366
|
const closeBtn = root.getElementById("oks-close");
|
|
243
367
|
const resetBtn = root.getElementById("oks-reset");
|
|
244
368
|
const wrapper = root.getElementById("oks-wrapper");
|
|
245
369
|
const opts$ = Array.from(root.querySelectorAll(".oks-access-opt, .oks-preset"));
|
|
246
|
-
if (!
|
|
247
|
-
return ()
|
|
248
|
-
};
|
|
370
|
+
if (!panel || !closeBtn || !resetBtn) {
|
|
371
|
+
return noopController();
|
|
249
372
|
}
|
|
250
373
|
let state = loadState(storageKey);
|
|
251
374
|
function applyState() {
|
|
252
|
-
const
|
|
253
|
-
const
|
|
254
|
-
|
|
255
|
-
|
|
375
|
+
const scoped = opts.scopeEl !== void 0;
|
|
376
|
+
const fxRoot = scoped ? opts.scopeEl ?? null : document.body;
|
|
377
|
+
const rootEl = document.documentElement;
|
|
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);
|
|
256
404
|
}
|
|
257
|
-
[1, 2, 3].forEach((l) => root2.classList.remove(`oks-colorblind-${l}`));
|
|
258
|
-
if (state.zoom > 0) body.classList.add(`oks-zoom-${state.zoom}`);
|
|
259
|
-
if (state.lh > 0) body.classList.add(`oks-lh-${state.lh}`);
|
|
260
|
-
if (state.align > 0) body.classList.add(`oks-align-${state.align}`);
|
|
261
|
-
if (state.ls > 0) body.classList.add(`oks-ls-${state.ls}`);
|
|
262
|
-
if (state.colorblind > 0) root2.classList.add(`oks-colorblind-${state.colorblind}`);
|
|
263
|
-
if (state.font) body.classList.add("oks-a11y-font");
|
|
264
|
-
if (state.dyslexia) body.classList.add("oks-dyslexia");
|
|
265
|
-
if (state.contrast) body.classList.add("oks-a11y-contrast");
|
|
266
|
-
if (state.hideImages) body.classList.add("oks-a11y-hide");
|
|
267
|
-
if (state.highlightLinks) body.classList.add("oks-a11y-links");
|
|
268
|
-
if (state.bigCursor) body.classList.add("oks-big-cursor");
|
|
269
|
-
if (state.pauseAnim) body.classList.add("oks-a11y-pause");
|
|
270
|
-
if (state.focusOutline) body.classList.add("oks-a11y-focus");
|
|
271
|
-
if (state.readingGuide) body.classList.add("oks-a11y-guide");
|
|
272
|
-
if (state.readingMask) body.classList.add("oks-a11y-mask");
|
|
273
|
-
if (state.bigTargets) body.classList.add("oks-a11y-bigtargets");
|
|
274
|
-
const overlay = ensureOverlay();
|
|
275
|
-
overlay.classList.toggle("is-active", state.grayOverlay);
|
|
276
405
|
syncButtonsFromState();
|
|
277
406
|
wrapper?.classList.toggle("has-active", !isStateEmpty(state));
|
|
278
407
|
}
|
|
@@ -336,9 +465,8 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
336
465
|
} else if (action === "mask") {
|
|
337
466
|
state.readingMask = !state.readingMask;
|
|
338
467
|
} else if (action === "preset") {
|
|
339
|
-
const id = btn.getAttribute("data-preset")
|
|
340
|
-
|
|
341
|
-
if (preset) Object.assign(state, preset);
|
|
468
|
+
const id = btn.getAttribute("data-preset");
|
|
469
|
+
if (id && PRESETS[id]) Object.assign(state, filterPresetForEnabled(id, enabled));
|
|
342
470
|
btn.classList.add("is-flashing");
|
|
343
471
|
setTimeout(() => btn.classList.remove("is-flashing"), 250);
|
|
344
472
|
}
|
|
@@ -350,28 +478,63 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
350
478
|
applyState();
|
|
351
479
|
saveState(storageKey, state);
|
|
352
480
|
};
|
|
481
|
+
let opener = null;
|
|
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
|
+
};
|
|
353
495
|
const openPanel = () => {
|
|
496
|
+
if (panel.classList.contains("is-open")) return;
|
|
497
|
+
opener = deepActiveElement();
|
|
354
498
|
panel.classList.add("is-open");
|
|
355
499
|
panel.removeAttribute("inert");
|
|
356
|
-
|
|
500
|
+
if (opts.scopeEl) positionScopedDialog();
|
|
501
|
+
trigger?.setAttribute("aria-expanded", "true");
|
|
502
|
+
ignoreDocClose = true;
|
|
503
|
+
setTimeout(() => {
|
|
504
|
+
ignoreDocClose = false;
|
|
505
|
+
}, 0);
|
|
357
506
|
const first = panel.querySelector("button:not([disabled])");
|
|
358
507
|
first?.focus();
|
|
359
508
|
};
|
|
360
509
|
const closePanel = () => {
|
|
510
|
+
if (!panel.classList.contains("is-open")) return;
|
|
361
511
|
panel.classList.remove("is-open");
|
|
362
512
|
panel.setAttribute("inert", "");
|
|
363
|
-
trigger
|
|
364
|
-
trigger.focus();
|
|
513
|
+
trigger?.setAttribute("aria-expanded", "false");
|
|
514
|
+
if (trigger) trigger.focus();
|
|
515
|
+
else if (opener?.isConnected) opener.focus();
|
|
516
|
+
opener = null;
|
|
365
517
|
};
|
|
366
|
-
const
|
|
367
|
-
e.stopPropagation();
|
|
518
|
+
const togglePanel = () => {
|
|
368
519
|
if (panel.classList.contains("is-open")) closePanel();
|
|
369
520
|
else openPanel();
|
|
370
521
|
};
|
|
522
|
+
let suppressClick = false;
|
|
523
|
+
const onTriggerClick = (e) => {
|
|
524
|
+
e.stopPropagation();
|
|
525
|
+
if (suppressClick) {
|
|
526
|
+
suppressClick = false;
|
|
527
|
+
return;
|
|
528
|
+
}
|
|
529
|
+
togglePanel();
|
|
530
|
+
};
|
|
371
531
|
const onDocClick = (e) => {
|
|
372
532
|
if (!panel.classList.contains("is-open")) return;
|
|
533
|
+
if (ignoreDocClose) return;
|
|
373
534
|
const path = e.composedPath();
|
|
374
|
-
if (path.includes(panel)
|
|
535
|
+
if (path.includes(panel)) return;
|
|
536
|
+
if (trigger && path.includes(trigger)) return;
|
|
537
|
+
if (wrapper && path.includes(wrapper)) return;
|
|
375
538
|
closePanel();
|
|
376
539
|
};
|
|
377
540
|
const onKeyDown = (e) => {
|
|
@@ -398,6 +561,88 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
398
561
|
}
|
|
399
562
|
}
|
|
400
563
|
};
|
|
564
|
+
const NUDGE_KEY = `${storageKey}::pos`;
|
|
565
|
+
const clampMax = (v) => Math.max(-nudgeMax, Math.min(nudgeMax, v));
|
|
566
|
+
let nudge = { x: 0, y: 0 };
|
|
567
|
+
const loadNudge = () => {
|
|
568
|
+
if (nudgeMax <= 0) return;
|
|
569
|
+
try {
|
|
570
|
+
const raw = localStorage.getItem(NUDGE_KEY);
|
|
571
|
+
if (!raw) return;
|
|
572
|
+
const p = JSON.parse(raw);
|
|
573
|
+
if (typeof p?.x === "number" && typeof p?.y === "number") {
|
|
574
|
+
nudge = { x: clampMax(p.x), y: clampMax(p.y) };
|
|
575
|
+
}
|
|
576
|
+
} catch {
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
const saveNudge = () => {
|
|
580
|
+
try {
|
|
581
|
+
if (nudge.x === 0 && nudge.y === 0) localStorage.removeItem(NUDGE_KEY);
|
|
582
|
+
else localStorage.setItem(NUDGE_KEY, JSON.stringify(nudge));
|
|
583
|
+
} catch {
|
|
584
|
+
}
|
|
585
|
+
};
|
|
586
|
+
const applyNudge = () => {
|
|
587
|
+
if (wrapper) wrapper.style.translate = `${nudge.x}px ${nudge.y}px`;
|
|
588
|
+
};
|
|
589
|
+
const clampOffset = (x, y) => {
|
|
590
|
+
x = clampMax(x);
|
|
591
|
+
y = clampMax(y);
|
|
592
|
+
if (wrapper) {
|
|
593
|
+
const r = wrapper.getBoundingClientRect();
|
|
594
|
+
const pad = 4;
|
|
595
|
+
const baseLeft = r.left - nudge.x;
|
|
596
|
+
const baseTop = r.top - nudge.y;
|
|
597
|
+
const vw = window.innerWidth || 0;
|
|
598
|
+
const vh = window.innerHeight || 0;
|
|
599
|
+
if (r.width > 0 && vw > 0) x = Math.max(pad - baseLeft, Math.min(vw - pad - r.width - baseLeft, x));
|
|
600
|
+
if (r.height > 0 && vh > 0) y = Math.max(pad - baseTop, Math.min(vh - pad - r.height - baseTop, y));
|
|
601
|
+
}
|
|
602
|
+
return { x, y };
|
|
603
|
+
};
|
|
604
|
+
let dragStart = null;
|
|
605
|
+
let didDrag = false;
|
|
606
|
+
const DRAG_THRESHOLD = 4;
|
|
607
|
+
const onPointerDown = (e) => {
|
|
608
|
+
if (nudgeMax <= 0 || e.button !== 0) return;
|
|
609
|
+
dragStart = { px: e.clientX, py: e.clientY, ox: nudge.x, oy: nudge.y };
|
|
610
|
+
didDrag = false;
|
|
611
|
+
trigger?.setPointerCapture?.(e.pointerId);
|
|
612
|
+
};
|
|
613
|
+
const onPointerMove = (e) => {
|
|
614
|
+
if (!dragStart) return;
|
|
615
|
+
const dx = e.clientX - dragStart.px;
|
|
616
|
+
const dy = e.clientY - dragStart.py;
|
|
617
|
+
if (!didDrag && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
|
|
618
|
+
didDrag = true;
|
|
619
|
+
nudge = clampOffset(dragStart.ox + dx, dragStart.oy + dy);
|
|
620
|
+
applyNudge();
|
|
621
|
+
e.preventDefault();
|
|
622
|
+
};
|
|
623
|
+
const onPointerUp = () => {
|
|
624
|
+
if (!dragStart) return;
|
|
625
|
+
dragStart = null;
|
|
626
|
+
if (didDrag) {
|
|
627
|
+
saveNudge();
|
|
628
|
+
suppressClick = true;
|
|
629
|
+
}
|
|
630
|
+
};
|
|
631
|
+
const onTriggerKey = (e) => {
|
|
632
|
+
if (nudgeMax <= 0) return;
|
|
633
|
+
const step = e.shiftKey ? 1 : 10;
|
|
634
|
+
let dx = 0;
|
|
635
|
+
let dy = 0;
|
|
636
|
+
if (e.key === "ArrowLeft") dx = -step;
|
|
637
|
+
else if (e.key === "ArrowRight") dx = step;
|
|
638
|
+
else if (e.key === "ArrowUp") dy = -step;
|
|
639
|
+
else if (e.key === "ArrowDown") dy = step;
|
|
640
|
+
else return;
|
|
641
|
+
e.preventDefault();
|
|
642
|
+
nudge = clampOffset(nudge.x + dx, nudge.y + dy);
|
|
643
|
+
applyNudge();
|
|
644
|
+
saveNudge();
|
|
645
|
+
};
|
|
401
646
|
const onMove = (e) => {
|
|
402
647
|
if (!state.readingGuide && !state.readingMask) return;
|
|
403
648
|
const y = e.touches?.[0]?.clientY ?? e.clientY;
|
|
@@ -411,7 +656,7 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
411
656
|
if (mask) mask.style.setProperty("--oks-mask-y", `${y}px`);
|
|
412
657
|
}
|
|
413
658
|
};
|
|
414
|
-
trigger
|
|
659
|
+
trigger?.addEventListener("click", onTriggerClick);
|
|
415
660
|
closeBtn.addEventListener("click", closePanel);
|
|
416
661
|
resetBtn.addEventListener("click", onReset);
|
|
417
662
|
for (const btn of opts$) btn.addEventListener("click", onOptClick);
|
|
@@ -419,9 +664,26 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
419
664
|
document.addEventListener("keydown", onKeyDown);
|
|
420
665
|
document.addEventListener("mousemove", onMove);
|
|
421
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
|
+
}
|
|
674
|
+
if (nudgeMax > 0 && trigger) {
|
|
675
|
+
loadNudge();
|
|
676
|
+
applyNudge();
|
|
677
|
+
trigger.style.cursor = "grab";
|
|
678
|
+
trigger.style.touchAction = "none";
|
|
679
|
+
trigger.addEventListener("pointerdown", onPointerDown);
|
|
680
|
+
document.addEventListener("pointermove", onPointerMove);
|
|
681
|
+
document.addEventListener("pointerup", onPointerUp);
|
|
682
|
+
trigger.addEventListener("keydown", onTriggerKey);
|
|
683
|
+
}
|
|
422
684
|
applyState();
|
|
423
|
-
|
|
424
|
-
trigger
|
|
685
|
+
const dispose = () => {
|
|
686
|
+
trigger?.removeEventListener("click", onTriggerClick);
|
|
425
687
|
closeBtn.removeEventListener("click", closePanel);
|
|
426
688
|
resetBtn.removeEventListener("click", onReset);
|
|
427
689
|
for (const btn of opts$) btn.removeEventListener("click", onOptClick);
|
|
@@ -429,7 +691,16 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
429
691
|
document.removeEventListener("keydown", onKeyDown);
|
|
430
692
|
document.removeEventListener("mousemove", onMove);
|
|
431
693
|
document.removeEventListener("touchmove", onMove);
|
|
694
|
+
if (opts.scopeEl !== void 0) {
|
|
695
|
+
window.removeEventListener("scroll", onReposition, true);
|
|
696
|
+
window.removeEventListener("resize", onReposition);
|
|
697
|
+
}
|
|
698
|
+
trigger?.removeEventListener("pointerdown", onPointerDown);
|
|
699
|
+
document.removeEventListener("pointermove", onPointerMove);
|
|
700
|
+
document.removeEventListener("pointerup", onPointerUp);
|
|
701
|
+
trigger?.removeEventListener("keydown", onTriggerKey);
|
|
432
702
|
};
|
|
703
|
+
return Object.assign(dispose, { open: openPanel, close: closePanel, toggle: togglePanel });
|
|
433
704
|
}
|
|
434
705
|
function ensureOverlay() {
|
|
435
706
|
let el = document.getElementById("oks-overlay-gray");
|
|
@@ -484,6 +755,12 @@ var PANEL_CSS = `
|
|
|
484
755
|
width: 60%;
|
|
485
756
|
height: 60%;
|
|
486
757
|
}
|
|
758
|
+
/* Porthole preset: a brushed-metal ring framing the standard glyph. The glyph
|
|
759
|
+
itself is untouched (discoverability), only the button gets the frame.
|
|
760
|
+
Hosts wanting a bespoke frame can target ::part(trigger) from light DOM. */
|
|
761
|
+
.oks-access-btn[data-trigger-icon="porthole"] {
|
|
762
|
+
box-shadow: 0 0 0 4px #b0bec5, 0 0 0 7px #546e7a, 0 6px 18px rgba(0,0,0,0.3);
|
|
763
|
+
}
|
|
487
764
|
.oks-active-badge {
|
|
488
765
|
position: absolute;
|
|
489
766
|
top: -2px;
|
|
@@ -834,9 +1111,96 @@ body.oks-a11y-bigtargets summary:not(oksigenia-access-panel):not(oksigenia-acces
|
|
|
834
1111
|
box-sizing: border-box !important;
|
|
835
1112
|
}
|
|
836
1113
|
`;
|
|
1114
|
+
function scopedEffectCss(s) {
|
|
1115
|
+
return `
|
|
1116
|
+
${s}.oks-zoom-1 { font-size: 110% !important; }
|
|
1117
|
+
${s}.oks-zoom-2 { font-size: 120% !important; }
|
|
1118
|
+
${s}.oks-zoom-3 { font-size: 135% !important; }
|
|
1119
|
+
${s}.oks-zoom-4 { font-size: 150% !important; }
|
|
1120
|
+
|
|
1121
|
+
${s}.oks-lh-1 * { line-height: 1.6 !important; }
|
|
1122
|
+
${s}.oks-lh-2 * { line-height: 1.9 !important; }
|
|
1123
|
+
${s}.oks-lh-3 * { line-height: 2.2 !important; }
|
|
1124
|
+
|
|
1125
|
+
${s}.oks-a11y-font, ${s}.oks-a11y-font * { font-family: Arial, sans-serif !important; }
|
|
1126
|
+
|
|
1127
|
+
${s}.oks-dyslexia * {
|
|
1128
|
+
font-family: 'Comic Sans MS', 'Verdana', sans-serif !important;
|
|
1129
|
+
letter-spacing: 0.05em !important;
|
|
1130
|
+
word-spacing: 0.1em !important;
|
|
1131
|
+
line-height: 1.6 !important;
|
|
1132
|
+
}
|
|
1133
|
+
|
|
1134
|
+
${s}.oks-a11y-hide img { opacity: 0 !important; visibility: hidden !important; }
|
|
1135
|
+
${s}.oks-a11y-links a { text-decoration: underline !important; background: #ff0 !important; color: #000 !important; }
|
|
1136
|
+
|
|
1137
|
+
${s}.oks-align-1 * { text-align: left !important; }
|
|
1138
|
+
${s}.oks-align-2 * { text-align: center !important; }
|
|
1139
|
+
${s}.oks-align-3 * { text-align: right !important; }
|
|
1140
|
+
|
|
1141
|
+
${s}.oks-a11y-pause * { animation: none !important; transition: none !important; }
|
|
1142
|
+
|
|
1143
|
+
${s}.oks-ls-1 * { letter-spacing: 0.05em !important; }
|
|
1144
|
+
${s}.oks-ls-2 * { letter-spacing: 0.10em !important; }
|
|
1145
|
+
${s}.oks-ls-3 * { letter-spacing: 0.16em !important; }
|
|
1146
|
+
|
|
1147
|
+
${s}.oks-a11y-focus a,
|
|
1148
|
+
${s}.oks-a11y-focus button,
|
|
1149
|
+
${s}.oks-a11y-focus input,
|
|
1150
|
+
${s}.oks-a11y-focus select,
|
|
1151
|
+
${s}.oks-a11y-focus textarea {
|
|
1152
|
+
outline: 2px dashed rgba(0, 95, 204, 0.45) !important;
|
|
1153
|
+
outline-offset: 2px !important;
|
|
1154
|
+
}
|
|
1155
|
+
${s}.oks-a11y-focus *:focus-visible {
|
|
1156
|
+
outline: 3px solid #005fcc !important;
|
|
1157
|
+
outline-offset: 3px !important;
|
|
1158
|
+
box-shadow: 0 0 0 6px rgba(0, 95, 204, 0.25) !important;
|
|
1159
|
+
}
|
|
1160
|
+
|
|
1161
|
+
${s}.oks-a11y-contrast, ${s}.oks-a11y-contrast * {
|
|
1162
|
+
background-color: #000 !important;
|
|
1163
|
+
color: #ff0 !important;
|
|
1164
|
+
border-color: #ff0 !important;
|
|
1165
|
+
text-shadow: none !important;
|
|
1166
|
+
box-shadow: none !important;
|
|
1167
|
+
}
|
|
1168
|
+
${s}.oks-a11y-contrast img { filter: grayscale(100%) contrast(120%) !important; }
|
|
1169
|
+
${s}.oks-a11y-contrast a { color: #0ff !important; text-decoration: underline !important; }
|
|
1170
|
+
${s}.oks-a11y-contrast.oks-a11y-focus *:focus-visible {
|
|
1171
|
+
outline-color: #0ff !important;
|
|
1172
|
+
box-shadow: 0 0 0 6px rgba(0, 255, 255, 0.3) !important;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
${s}.oks-a11y-bigtargets a,
|
|
1176
|
+
${s}.oks-a11y-bigtargets button,
|
|
1177
|
+
${s}.oks-a11y-bigtargets [role="button"],
|
|
1178
|
+
${s}.oks-a11y-bigtargets input[type="checkbox"],
|
|
1179
|
+
${s}.oks-a11y-bigtargets input[type="radio"],
|
|
1180
|
+
${s}.oks-a11y-bigtargets summary {
|
|
1181
|
+
min-height: 44px !important;
|
|
1182
|
+
min-width: 44px !important;
|
|
1183
|
+
padding: 8px 12px !important;
|
|
1184
|
+
box-sizing: border-box !important;
|
|
1185
|
+
}
|
|
1186
|
+
`;
|
|
1187
|
+
}
|
|
837
1188
|
|
|
838
1189
|
// src/web-component.ts
|
|
839
|
-
var OBSERVED = [
|
|
1190
|
+
var OBSERVED = [
|
|
1191
|
+
"locale",
|
|
1192
|
+
"position",
|
|
1193
|
+
"position-mobile",
|
|
1194
|
+
"trigger-icon",
|
|
1195
|
+
"storage-key",
|
|
1196
|
+
"controls",
|
|
1197
|
+
"exclude",
|
|
1198
|
+
"trigger",
|
|
1199
|
+
"effects-exclude",
|
|
1200
|
+
"nudge",
|
|
1201
|
+
"presets",
|
|
1202
|
+
"scope"
|
|
1203
|
+
];
|
|
840
1204
|
var STYLE_ID = "oksigenia-access-effects";
|
|
841
1205
|
var FILTERS_ID = "oksigenia-access-filters";
|
|
842
1206
|
var GUIDE_ID = "oks-reading-guide";
|
|
@@ -871,18 +1235,24 @@ function ensureGlobalStyles() {
|
|
|
871
1235
|
}
|
|
872
1236
|
var VALID_POSITIONS = [
|
|
873
1237
|
"top-left",
|
|
1238
|
+
"top-center",
|
|
874
1239
|
"top-right",
|
|
875
1240
|
"mid-left",
|
|
1241
|
+
"mid-center",
|
|
876
1242
|
"mid-right",
|
|
877
1243
|
"bottom-left",
|
|
1244
|
+
"bottom-center",
|
|
878
1245
|
"bottom-right"
|
|
879
1246
|
];
|
|
880
|
-
var VALID_ICONS = ["vitruvian", "wheelchair", "eye", "universal"];
|
|
1247
|
+
var VALID_ICONS = ["vitruvian", "wheelchair", "eye", "universal", "porthole"];
|
|
1248
|
+
var fxSeq = 0;
|
|
881
1249
|
var OksigeniaAccessPanelElement = class extends HTMLElement {
|
|
882
1250
|
static get observedAttributes() {
|
|
883
1251
|
return OBSERVED;
|
|
884
1252
|
}
|
|
885
|
-
|
|
1253
|
+
_controller = null;
|
|
1254
|
+
_fxId = `oks-access-fx-${++fxSeq}`;
|
|
1255
|
+
_scopeId = `oks-access-scope-${this._fxId.split("-").pop()}`;
|
|
886
1256
|
constructor() {
|
|
887
1257
|
super();
|
|
888
1258
|
this.attachShadow({ mode: "open" });
|
|
@@ -892,12 +1262,27 @@ var OksigeniaAccessPanelElement = class extends HTMLElement {
|
|
|
892
1262
|
this.render();
|
|
893
1263
|
}
|
|
894
1264
|
disconnectedCallback() {
|
|
895
|
-
this.
|
|
896
|
-
this.
|
|
1265
|
+
this._controller?.();
|
|
1266
|
+
this._controller = null;
|
|
1267
|
+
if (typeof document !== "undefined") {
|
|
1268
|
+
document.getElementById(this._fxId)?.remove();
|
|
1269
|
+
document.getElementById(this._scopeId)?.remove();
|
|
1270
|
+
}
|
|
897
1271
|
}
|
|
898
1272
|
attributeChangedCallback() {
|
|
899
1273
|
if (this.isConnected) this.render();
|
|
900
1274
|
}
|
|
1275
|
+
/** Imperative API (#2): open / close / toggle the panel from the host.
|
|
1276
|
+
* Useful with `trigger="none"`, where the host drives its own button. */
|
|
1277
|
+
open() {
|
|
1278
|
+
this._controller?.open();
|
|
1279
|
+
}
|
|
1280
|
+
close() {
|
|
1281
|
+
this._controller?.close();
|
|
1282
|
+
}
|
|
1283
|
+
toggle() {
|
|
1284
|
+
this._controller?.toggle();
|
|
1285
|
+
}
|
|
901
1286
|
getPosition() {
|
|
902
1287
|
const attr = this.getAttribute("position") ?? "mid-left";
|
|
903
1288
|
return VALID_POSITIONS.includes(attr) ? attr : "mid-left";
|
|
@@ -913,22 +1298,81 @@ var OksigeniaAccessPanelElement = class extends HTMLElement {
|
|
|
913
1298
|
getLocale() {
|
|
914
1299
|
return this.getAttribute("locale") ?? (typeof navigator !== "undefined" ? navigator.language : "en");
|
|
915
1300
|
}
|
|
1301
|
+
/** `nudge` (bare) ⇒ default 80px cap; `nudge="50"` ⇒ 50; absent/invalid ⇒ off. */
|
|
1302
|
+
getNudgeMax() {
|
|
1303
|
+
if (!this.hasAttribute("nudge")) return 0;
|
|
1304
|
+
const raw = this.getAttribute("nudge");
|
|
1305
|
+
if (raw == null || raw.trim() === "") return 80;
|
|
1306
|
+
const n = parseInt(raw, 10);
|
|
1307
|
+
return Number.isFinite(n) && n > 0 ? n : 0;
|
|
1308
|
+
}
|
|
1309
|
+
/** Inject the per-instance effects-exclude rule (#4): under high-contrast,
|
|
1310
|
+
* drop the destructive filter from selectors the host marks as essential
|
|
1311
|
+
* media (video, canvas, scientific imagery). Grayscale is handled by not
|
|
1312
|
+
* offering the control (curation), not here. */
|
|
1313
|
+
updateEffectsExclude(base) {
|
|
1314
|
+
if (typeof document === "undefined") return;
|
|
1315
|
+
const sel = (this.getAttribute("effects-exclude") ?? "").split(",").map((s) => s.trim()).filter(Boolean);
|
|
1316
|
+
const existing = document.getElementById(this._fxId);
|
|
1317
|
+
if (sel.length === 0) {
|
|
1318
|
+
existing?.remove();
|
|
1319
|
+
return;
|
|
1320
|
+
}
|
|
1321
|
+
const style = existing ?? document.createElement("style");
|
|
1322
|
+
if (!existing) {
|
|
1323
|
+
style.id = this._fxId;
|
|
1324
|
+
document.head.appendChild(style);
|
|
1325
|
+
}
|
|
1326
|
+
const list = sel.join(", ");
|
|
1327
|
+
style.textContent = `${base}.oks-a11y-contrast :is(${list}), ${base}.oks-a11y-contrast :is(${list}) * { filter: none !important; }`;
|
|
1328
|
+
}
|
|
1329
|
+
/** Inject the per-instance scoped effect CSS (`scope=`): the scopable effects
|
|
1330
|
+
* anchored to the host's container instead of `body`, so several panels
|
|
1331
|
+
* regionalise their own zone without clobbering each other. */
|
|
1332
|
+
updateScopeStyle(scope) {
|
|
1333
|
+
if (typeof document === "undefined") return;
|
|
1334
|
+
const existing = document.getElementById(this._scopeId);
|
|
1335
|
+
if (!scope) {
|
|
1336
|
+
existing?.remove();
|
|
1337
|
+
return;
|
|
1338
|
+
}
|
|
1339
|
+
const style = existing ?? document.createElement("style");
|
|
1340
|
+
if (!existing) {
|
|
1341
|
+
style.id = this._scopeId;
|
|
1342
|
+
document.head.appendChild(style);
|
|
1343
|
+
}
|
|
1344
|
+
style.textContent = scopedEffectCss(scope);
|
|
1345
|
+
}
|
|
916
1346
|
render() {
|
|
917
1347
|
const shadow = this.shadowRoot;
|
|
918
1348
|
if (!shadow) return;
|
|
919
|
-
this.
|
|
1349
|
+
this._controller?.();
|
|
920
1350
|
const position = this.getPosition();
|
|
921
1351
|
const positionMobile = this.getPositionMobile();
|
|
1352
|
+
const scope = this.getAttribute("scope")?.trim() || null;
|
|
1353
|
+
let enabled = resolveEnabledControls(this.getAttribute("controls"), this.getAttribute("exclude"));
|
|
1354
|
+
if (scope) enabled = scopedControls(enabled);
|
|
1355
|
+
const showTrigger = this.getAttribute("trigger") !== "none";
|
|
1356
|
+
const showPresets = (this.getAttribute("presets") ?? "").trim().toLowerCase() !== "none";
|
|
922
1357
|
const html = buildPanelHtml({
|
|
923
1358
|
t: getTranslation(this.getLocale()),
|
|
924
1359
|
triggerIcon: this.getTriggerIcon(),
|
|
925
|
-
position
|
|
1360
|
+
position,
|
|
1361
|
+
enabled,
|
|
1362
|
+
showTrigger,
|
|
1363
|
+
showPresets
|
|
926
1364
|
});
|
|
927
1365
|
shadow.innerHTML = `<style>${PANEL_CSS}${positionCss(position, positionMobile)}</style>${html}`;
|
|
928
|
-
|
|
1366
|
+
const scopeEl = scope ? document.querySelector(scope) : void 0;
|
|
1367
|
+
this._controller = bindPanelBehavior(shadow, {
|
|
929
1368
|
storageKey: this.getAttribute("storage-key") ?? void 0,
|
|
930
|
-
locale: this.getLocale()
|
|
1369
|
+
locale: this.getLocale(),
|
|
1370
|
+
enabled,
|
|
1371
|
+
nudgeMax: this.getNudgeMax(),
|
|
1372
|
+
scopeEl
|
|
931
1373
|
});
|
|
1374
|
+
this.updateScopeStyle(scope);
|
|
1375
|
+
this.updateEffectsExclude(scope ?? "body");
|
|
932
1376
|
}
|
|
933
1377
|
};
|
|
934
1378
|
if (typeof customElements !== "undefined" && !customElements.get("oksigenia-access-panel")) {
|