@oksigenia/access-panel 0.4.4 → 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 +18 -0
- package/README.md +26 -2
- package/dist/index.d.ts +60 -21
- package/dist/index.js +301 -66
- package/dist/index.js.map +1 -1
- package/dist/web-component.d.ts +14 -1
- package/dist/web-component.js +375 -74
- package/dist/web-component.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -37,75 +37,173 @@ var ICON_COLORBLIND = '<svg viewBox="0 0 24 24" fill="currentColor"><path d="M12
|
|
|
37
37
|
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>';
|
|
38
38
|
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>';
|
|
39
39
|
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>';
|
|
40
|
+
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>';
|
|
40
41
|
var TRIGGER_ICONS = {
|
|
41
42
|
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>',
|
|
42
43
|
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>',
|
|
43
44
|
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>',
|
|
44
|
-
universal:
|
|
45
|
+
universal: UNIVERSAL_GLYPH,
|
|
46
|
+
porthole: UNIVERSAL_GLYPH
|
|
45
47
|
};
|
|
46
48
|
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>';
|
|
47
49
|
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>';
|
|
48
50
|
|
|
51
|
+
// src/controls.ts
|
|
52
|
+
var ALL_CONTROLS = [
|
|
53
|
+
"text-size",
|
|
54
|
+
"line-height",
|
|
55
|
+
"text-align",
|
|
56
|
+
"readable-font",
|
|
57
|
+
"dyslexia-font",
|
|
58
|
+
"letter-spacing",
|
|
59
|
+
"contrast",
|
|
60
|
+
"grayscale",
|
|
61
|
+
"hide-images",
|
|
62
|
+
"highlight-links",
|
|
63
|
+
"colorblind",
|
|
64
|
+
"reading-guide",
|
|
65
|
+
"reading-mask",
|
|
66
|
+
"big-cursor",
|
|
67
|
+
"big-targets",
|
|
68
|
+
"pause-anim",
|
|
69
|
+
"focus"
|
|
70
|
+
];
|
|
71
|
+
var CONTROL_STATE_KEY = {
|
|
72
|
+
"text-size": "zoom",
|
|
73
|
+
"line-height": "lh",
|
|
74
|
+
"text-align": "align",
|
|
75
|
+
"readable-font": "font",
|
|
76
|
+
"dyslexia-font": "dyslexia",
|
|
77
|
+
"letter-spacing": "ls",
|
|
78
|
+
"contrast": "contrast",
|
|
79
|
+
"grayscale": "grayOverlay",
|
|
80
|
+
"hide-images": "hideImages",
|
|
81
|
+
"highlight-links": "highlightLinks",
|
|
82
|
+
"colorblind": "colorblind",
|
|
83
|
+
"reading-guide": "readingGuide",
|
|
84
|
+
"reading-mask": "readingMask",
|
|
85
|
+
"big-cursor": "bigCursor",
|
|
86
|
+
"big-targets": "bigTargets",
|
|
87
|
+
"pause-anim": "pauseAnim",
|
|
88
|
+
"focus": "focusOutline"
|
|
89
|
+
};
|
|
90
|
+
var STATE_KEY_CONTROL = Object.fromEntries(
|
|
91
|
+
Object.entries(CONTROL_STATE_KEY).map(([control, key]) => [key, control])
|
|
92
|
+
);
|
|
93
|
+
var PRESETS = {
|
|
94
|
+
lowvision: { zoom: 2, contrast: true, highlightLinks: true, bigCursor: true, focusOutline: true },
|
|
95
|
+
dyslexia: { dyslexia: true, lh: 2, ls: 2, readingGuide: true },
|
|
96
|
+
motor: { bigCursor: true, bigTargets: true, focusOutline: true },
|
|
97
|
+
calm: { hideImages: true, pauseAnim: true }
|
|
98
|
+
};
|
|
99
|
+
var PRESET_IDS = ["lowvision", "dyslexia", "motor", "calm"];
|
|
100
|
+
function presetControlIds(id) {
|
|
101
|
+
return Object.keys(PRESETS[id]).map((k) => STATE_KEY_CONTROL[k]);
|
|
102
|
+
}
|
|
103
|
+
function resolveEnabledControls(controlsAttr, excludeAttr) {
|
|
104
|
+
const valid = new Set(ALL_CONTROLS);
|
|
105
|
+
const parse = (raw) => (raw ?? "").split(",").map((s) => s.trim().toLowerCase()).filter((s) => valid.has(s));
|
|
106
|
+
const whitelist = controlsAttr != null ? parse(controlsAttr) : null;
|
|
107
|
+
let enabled = whitelist && whitelist.length > 0 ? new Set(whitelist) : new Set(ALL_CONTROLS);
|
|
108
|
+
if (excludeAttr != null) {
|
|
109
|
+
for (const id of parse(excludeAttr)) enabled.delete(id);
|
|
110
|
+
}
|
|
111
|
+
return enabled;
|
|
112
|
+
}
|
|
113
|
+
function filterPresetForEnabled(id, enabled) {
|
|
114
|
+
const out = {};
|
|
115
|
+
for (const [k, v] of Object.entries(PRESETS[id])) {
|
|
116
|
+
if (enabled.has(STATE_KEY_CONTROL[k])) out[k] = v;
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
function presetIsAvailable(id, enabled) {
|
|
121
|
+
return presetControlIds(id).filter((c) => enabled.has(c)).length >= 2;
|
|
122
|
+
}
|
|
123
|
+
|
|
49
124
|
// src/render.ts
|
|
125
|
+
var PRESET_ICON = {
|
|
126
|
+
lowvision: ICON_CONTRAST,
|
|
127
|
+
dyslexia: ICON_DYSLEXIA,
|
|
128
|
+
motor: ICON_CURSOR,
|
|
129
|
+
calm: ICON_HIDE
|
|
130
|
+
};
|
|
50
131
|
function buildPanelHtml(opts) {
|
|
51
132
|
const { t, triggerIcon } = opts;
|
|
133
|
+
const enabled = opts.enabled ?? new Set(ALL_CONTROLS);
|
|
134
|
+
const showTrigger = opts.showTrigger ?? true;
|
|
135
|
+
const showPresets = opts.showPresets ?? true;
|
|
52
136
|
const trig = TRIGGER_ICONS[triggerIcon];
|
|
53
|
-
const
|
|
54
|
-
|
|
137
|
+
const multi = (id, prefix, levels, label, icon, full = false) => {
|
|
138
|
+
if (!enabled.has(id)) return "";
|
|
139
|
+
const action = id === "colorblind" ? "colorblind" : "multi";
|
|
55
140
|
const fullClass = full ? " full-width" : "";
|
|
56
141
|
const dots = Array.from({ length: levels }, () => "<span></span>").join("");
|
|
57
|
-
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>`;
|
|
142
|
+
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>`;
|
|
143
|
+
};
|
|
144
|
+
const toggle = (id, klass, label, icon) => {
|
|
145
|
+
if (!enabled.has(id)) return "";
|
|
146
|
+
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>`;
|
|
147
|
+
};
|
|
148
|
+
const overlay = (id, target, label, icon) => {
|
|
149
|
+
if (!enabled.has(id)) return "";
|
|
150
|
+
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>`;
|
|
151
|
+
};
|
|
152
|
+
const simple = (id, action, label, icon) => {
|
|
153
|
+
if (!enabled.has(id)) return "";
|
|
154
|
+
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>`;
|
|
155
|
+
};
|
|
156
|
+
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>`;
|
|
157
|
+
const section = (title, rows) => {
|
|
158
|
+
const kept = rows.filter(Boolean);
|
|
159
|
+
if (kept.length === 0) return "";
|
|
160
|
+
return `<h4 class="oks-access-title">${escapeHtml(title)}</h4><div class="oks-access-grid">${kept.join("")}</div>`;
|
|
161
|
+
};
|
|
162
|
+
const presetLabels = {
|
|
163
|
+
lowvision: t.pLow,
|
|
164
|
+
dyslexia: t.pDys,
|
|
165
|
+
motor: t.pMot,
|
|
166
|
+
calm: t.pCalm
|
|
58
167
|
};
|
|
59
|
-
const
|
|
60
|
-
const
|
|
61
|
-
const
|
|
62
|
-
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>`;
|
|
63
|
-
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>`;
|
|
64
|
-
return `
|
|
168
|
+
const presetButtons = showPresets ? PRESET_IDS.filter((id) => presetIsAvailable(id, enabled)).map((id) => preset(id, presetLabels[id])) : [];
|
|
169
|
+
const presetsBlock = presetButtons.length ? `<h4 class="oks-access-title">${escapeHtml(t.presets)}</h4><div class="oks-access-presets">${presetButtons.join("")}</div>` : "";
|
|
170
|
+
const triggerBlock = showTrigger ? `
|
|
65
171
|
<div class="oks-access-wrapper" id="oks-wrapper" data-position="${opts.position}">
|
|
66
|
-
<button class="oks-access-btn" id="oks-trigger" aria-label="${escapeAttr(t.title)}" aria-expanded="false" aria-controls="oks-panel" type="button">
|
|
172
|
+
<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">
|
|
67
173
|
${trig}
|
|
68
174
|
</button>
|
|
69
175
|
<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>
|
|
70
|
-
</div
|
|
176
|
+
</div>` : "";
|
|
177
|
+
return `${triggerBlock}
|
|
71
178
|
<div class="oks-access-panel" id="oks-panel" role="dialog" aria-modal="true" aria-labelledby="oks-panel-title" inert>
|
|
72
179
|
<div class="oks-access-header">
|
|
73
180
|
<h3 id="oks-panel-title">${escapeHtml(t.title)}</h3>
|
|
74
181
|
<button class="oks-access-close" id="oks-close" aria-label="${escapeAttr(t.close)}" type="button">${ICON_CLOSE}</button>
|
|
75
182
|
</div>
|
|
76
183
|
<div class="oks-access-content">
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
${grid([
|
|
86
|
-
multi("multi", "oks-zoom", 4, t.size, ICON_TXT),
|
|
87
|
-
multi("multi", "oks-lh", 3, t.lh, ICON_LH),
|
|
88
|
-
multi("multi", "oks-align", 3, t.align, ICON_ALIGN),
|
|
89
|
-
toggle("oks-a11y-font", t.font, ICON_FONT),
|
|
90
|
-
toggle("oks-dyslexia", t.dyslexia, ICON_DYSLEXIA),
|
|
91
|
-
multi("multi", "oks-ls", 3, t.ls, ICON_LS)
|
|
184
|
+
${presetsBlock}
|
|
185
|
+
${section(t.txt, [
|
|
186
|
+
multi("text-size", "oks-zoom", 4, t.size, ICON_TXT),
|
|
187
|
+
multi("line-height", "oks-lh", 3, t.lh, ICON_LH),
|
|
188
|
+
multi("text-align", "oks-align", 3, t.align, ICON_ALIGN),
|
|
189
|
+
toggle("readable-font", "oks-a11y-font", t.font, ICON_FONT),
|
|
190
|
+
toggle("dyslexia-font", "oks-dyslexia", t.dyslexia, ICON_DYSLEXIA),
|
|
191
|
+
multi("letter-spacing", "oks-ls", 3, t.ls, ICON_LS)
|
|
92
192
|
])}
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
toggle("oks-a11y-
|
|
98
|
-
toggle("oks-a11y-links", t.links, ICON_LINK),
|
|
193
|
+
${section(t.vis, [
|
|
194
|
+
toggle("contrast", "oks-a11y-contrast", t.contrast, ICON_CONTRAST),
|
|
195
|
+
overlay("grayscale", "oks-overlay-gray", t.gray, ICON_GRAY),
|
|
196
|
+
toggle("hide-images", "oks-a11y-hide", t.hide, ICON_HIDE),
|
|
197
|
+
toggle("highlight-links", "oks-a11y-links", t.links, ICON_LINK),
|
|
99
198
|
multi("colorblind", "oks-colorblind", 3, t.cb, ICON_COLORBLIND, true)
|
|
100
199
|
])}
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
toggle("oks-
|
|
106
|
-
toggle("oks-a11y-
|
|
107
|
-
toggle("oks-a11y-
|
|
108
|
-
toggle("oks-a11y-focus", t.focus, ICON_FOCUS)
|
|
200
|
+
${section(t.ori, [
|
|
201
|
+
simple("reading-guide", "guide", t.guide, ICON_GUIDE),
|
|
202
|
+
simple("reading-mask", "mask", t.mask, ICON_MASK),
|
|
203
|
+
toggle("big-cursor", "oks-big-cursor", t.cursor, ICON_CURSOR),
|
|
204
|
+
toggle("big-targets", "oks-a11y-bigtargets", t.targets, ICON_TARGETS),
|
|
205
|
+
toggle("pause-anim", "oks-a11y-pause", t.pause, ICON_PAUSE),
|
|
206
|
+
toggle("focus", "oks-a11y-focus", t.focus, ICON_FOCUS)
|
|
109
207
|
])}
|
|
110
208
|
</div>
|
|
111
209
|
<div class="oks-access-footer">
|
|
@@ -130,14 +228,20 @@ function positionRules(position) {
|
|
|
130
228
|
switch (position) {
|
|
131
229
|
case "top-left":
|
|
132
230
|
return { wrap: "top: 20px; left: 20px;", panel: "top: 80px; left: 20px;" };
|
|
231
|
+
case "top-center":
|
|
232
|
+
return { wrap: "top: 20px; left: 50%; transform: translateX(-50%);", panel: "top: 80px; left: 50%; transform: translateX(-50%);" };
|
|
133
233
|
case "top-right":
|
|
134
234
|
return { wrap: "top: 20px; right: 20px;", panel: "top: 80px; right: 20px;" };
|
|
135
235
|
case "mid-left":
|
|
136
236
|
return { wrap: "top: 50%; left: 20px; transform: translateY(-50%);", panel: "top: 50%; left: 90px; transform: translateY(-50%);" };
|
|
237
|
+
case "mid-center":
|
|
238
|
+
return { wrap: "top: 50%; left: 50%; transform: translate(-50%, -50%);", panel: "top: 50%; left: 50%; transform: translate(-50%, -50%);" };
|
|
137
239
|
case "mid-right":
|
|
138
240
|
return { wrap: "top: 50%; right: 20px; transform: translateY(-50%);", panel: "top: 50%; right: 90px; transform: translateY(-50%);" };
|
|
139
241
|
case "bottom-left":
|
|
140
242
|
return { wrap: "bottom: 20px; left: 20px;", panel: "bottom: 100px; left: 20px;" };
|
|
243
|
+
case "bottom-center":
|
|
244
|
+
return { wrap: "bottom: 20px; left: 50%; transform: translateX(-50%);", panel: "bottom: 100px; left: 50%; transform: translateX(-50%);" };
|
|
141
245
|
case "bottom-right":
|
|
142
246
|
return { wrap: "bottom: 20px; right: 20px;", panel: "bottom: 100px; right: 20px;" };
|
|
143
247
|
}
|
|
@@ -231,38 +335,45 @@ var TOGGLE_KEYS = {
|
|
|
231
335
|
"oks-a11y-focus": "focusOutline",
|
|
232
336
|
"oks-a11y-bigtargets": "bigTargets"
|
|
233
337
|
};
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
};
|
|
338
|
+
function noopController() {
|
|
339
|
+
return Object.assign(() => {
|
|
340
|
+
}, { open() {
|
|
341
|
+
}, close() {
|
|
342
|
+
}, toggle() {
|
|
343
|
+
} });
|
|
344
|
+
}
|
|
345
|
+
function deepActiveElement() {
|
|
346
|
+
let a = document.activeElement;
|
|
347
|
+
while (a?.shadowRoot?.activeElement) a = a.shadowRoot.activeElement;
|
|
348
|
+
return a;
|
|
349
|
+
}
|
|
240
350
|
function bindPanelBehavior(root, opts = {}) {
|
|
241
351
|
const storageKey = opts.storageKey ?? "oksiacSettings";
|
|
242
352
|
const t = getTranslation(opts.locale ?? "en");
|
|
353
|
+
const enabled = opts.enabled ?? new Set(ALL_CONTROLS);
|
|
354
|
+
const nudgeMax = Math.max(0, opts.nudgeMax ?? 0);
|
|
243
355
|
const trigger = root.getElementById("oks-trigger");
|
|
244
356
|
const panel = root.getElementById("oks-panel");
|
|
245
357
|
const closeBtn = root.getElementById("oks-close");
|
|
246
358
|
const resetBtn = root.getElementById("oks-reset");
|
|
247
359
|
const wrapper = root.getElementById("oks-wrapper");
|
|
248
360
|
const opts$ = Array.from(root.querySelectorAll(".oks-access-opt, .oks-preset"));
|
|
249
|
-
if (!
|
|
250
|
-
return ()
|
|
251
|
-
};
|
|
361
|
+
if (!panel || !closeBtn || !resetBtn) {
|
|
362
|
+
return noopController();
|
|
252
363
|
}
|
|
253
364
|
let state = loadState(storageKey);
|
|
254
365
|
function applyState() {
|
|
255
366
|
const body = document.body;
|
|
256
|
-
const
|
|
367
|
+
const rootEl = document.documentElement;
|
|
257
368
|
for (const cls of Array.from(body.classList)) {
|
|
258
369
|
if (cls.startsWith("oks-")) body.classList.remove(cls);
|
|
259
370
|
}
|
|
260
|
-
[1, 2, 3].forEach((l) =>
|
|
371
|
+
[1, 2, 3].forEach((l) => rootEl.classList.remove(`oks-colorblind-${l}`));
|
|
261
372
|
if (state.zoom > 0) body.classList.add(`oks-zoom-${state.zoom}`);
|
|
262
373
|
if (state.lh > 0) body.classList.add(`oks-lh-${state.lh}`);
|
|
263
374
|
if (state.align > 0) body.classList.add(`oks-align-${state.align}`);
|
|
264
375
|
if (state.ls > 0) body.classList.add(`oks-ls-${state.ls}`);
|
|
265
|
-
if (state.colorblind > 0)
|
|
376
|
+
if (state.colorblind > 0) rootEl.classList.add(`oks-colorblind-${state.colorblind}`);
|
|
266
377
|
if (state.font) body.classList.add("oks-a11y-font");
|
|
267
378
|
if (state.dyslexia) body.classList.add("oks-dyslexia");
|
|
268
379
|
if (state.contrast) body.classList.add("oks-a11y-contrast");
|
|
@@ -339,9 +450,8 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
339
450
|
} else if (action === "mask") {
|
|
340
451
|
state.readingMask = !state.readingMask;
|
|
341
452
|
} else if (action === "preset") {
|
|
342
|
-
const id = btn.getAttribute("data-preset")
|
|
343
|
-
|
|
344
|
-
if (preset) Object.assign(state, preset);
|
|
453
|
+
const id = btn.getAttribute("data-preset");
|
|
454
|
+
if (id && PRESETS[id]) Object.assign(state, filterPresetForEnabled(id, enabled));
|
|
345
455
|
btn.classList.add("is-flashing");
|
|
346
456
|
setTimeout(() => btn.classList.remove("is-flashing"), 250);
|
|
347
457
|
}
|
|
@@ -353,28 +463,50 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
353
463
|
applyState();
|
|
354
464
|
saveState(storageKey, state);
|
|
355
465
|
};
|
|
466
|
+
let opener = null;
|
|
467
|
+
let ignoreDocClose = false;
|
|
356
468
|
const openPanel = () => {
|
|
469
|
+
if (panel.classList.contains("is-open")) return;
|
|
470
|
+
opener = deepActiveElement();
|
|
357
471
|
panel.classList.add("is-open");
|
|
358
472
|
panel.removeAttribute("inert");
|
|
359
|
-
trigger
|
|
473
|
+
trigger?.setAttribute("aria-expanded", "true");
|
|
474
|
+
ignoreDocClose = true;
|
|
475
|
+
setTimeout(() => {
|
|
476
|
+
ignoreDocClose = false;
|
|
477
|
+
}, 0);
|
|
360
478
|
const first = panel.querySelector("button:not([disabled])");
|
|
361
479
|
first?.focus();
|
|
362
480
|
};
|
|
363
481
|
const closePanel = () => {
|
|
482
|
+
if (!panel.classList.contains("is-open")) return;
|
|
364
483
|
panel.classList.remove("is-open");
|
|
365
484
|
panel.setAttribute("inert", "");
|
|
366
|
-
trigger
|
|
367
|
-
trigger.focus();
|
|
485
|
+
trigger?.setAttribute("aria-expanded", "false");
|
|
486
|
+
if (trigger) trigger.focus();
|
|
487
|
+
else if (opener?.isConnected) opener.focus();
|
|
488
|
+
opener = null;
|
|
368
489
|
};
|
|
369
|
-
const
|
|
370
|
-
e.stopPropagation();
|
|
490
|
+
const togglePanel = () => {
|
|
371
491
|
if (panel.classList.contains("is-open")) closePanel();
|
|
372
492
|
else openPanel();
|
|
373
493
|
};
|
|
494
|
+
let suppressClick = false;
|
|
495
|
+
const onTriggerClick = (e) => {
|
|
496
|
+
e.stopPropagation();
|
|
497
|
+
if (suppressClick) {
|
|
498
|
+
suppressClick = false;
|
|
499
|
+
return;
|
|
500
|
+
}
|
|
501
|
+
togglePanel();
|
|
502
|
+
};
|
|
374
503
|
const onDocClick = (e) => {
|
|
375
504
|
if (!panel.classList.contains("is-open")) return;
|
|
505
|
+
if (ignoreDocClose) return;
|
|
376
506
|
const path = e.composedPath();
|
|
377
|
-
if (path.includes(panel)
|
|
507
|
+
if (path.includes(panel)) return;
|
|
508
|
+
if (trigger && path.includes(trigger)) return;
|
|
509
|
+
if (wrapper && path.includes(wrapper)) return;
|
|
378
510
|
closePanel();
|
|
379
511
|
};
|
|
380
512
|
const onKeyDown = (e) => {
|
|
@@ -401,6 +533,88 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
401
533
|
}
|
|
402
534
|
}
|
|
403
535
|
};
|
|
536
|
+
const NUDGE_KEY = `${storageKey}::pos`;
|
|
537
|
+
const clampMax = (v) => Math.max(-nudgeMax, Math.min(nudgeMax, v));
|
|
538
|
+
let nudge = { x: 0, y: 0 };
|
|
539
|
+
const loadNudge = () => {
|
|
540
|
+
if (nudgeMax <= 0) return;
|
|
541
|
+
try {
|
|
542
|
+
const raw = localStorage.getItem(NUDGE_KEY);
|
|
543
|
+
if (!raw) return;
|
|
544
|
+
const p = JSON.parse(raw);
|
|
545
|
+
if (typeof p?.x === "number" && typeof p?.y === "number") {
|
|
546
|
+
nudge = { x: clampMax(p.x), y: clampMax(p.y) };
|
|
547
|
+
}
|
|
548
|
+
} catch {
|
|
549
|
+
}
|
|
550
|
+
};
|
|
551
|
+
const saveNudge = () => {
|
|
552
|
+
try {
|
|
553
|
+
if (nudge.x === 0 && nudge.y === 0) localStorage.removeItem(NUDGE_KEY);
|
|
554
|
+
else localStorage.setItem(NUDGE_KEY, JSON.stringify(nudge));
|
|
555
|
+
} catch {
|
|
556
|
+
}
|
|
557
|
+
};
|
|
558
|
+
const applyNudge = () => {
|
|
559
|
+
if (wrapper) wrapper.style.translate = `${nudge.x}px ${nudge.y}px`;
|
|
560
|
+
};
|
|
561
|
+
const clampOffset = (x, y) => {
|
|
562
|
+
x = clampMax(x);
|
|
563
|
+
y = clampMax(y);
|
|
564
|
+
if (wrapper) {
|
|
565
|
+
const r = wrapper.getBoundingClientRect();
|
|
566
|
+
const pad = 4;
|
|
567
|
+
const baseLeft = r.left - nudge.x;
|
|
568
|
+
const baseTop = r.top - nudge.y;
|
|
569
|
+
const vw = window.innerWidth || 0;
|
|
570
|
+
const vh = window.innerHeight || 0;
|
|
571
|
+
if (r.width > 0 && vw > 0) x = Math.max(pad - baseLeft, Math.min(vw - pad - r.width - baseLeft, x));
|
|
572
|
+
if (r.height > 0 && vh > 0) y = Math.max(pad - baseTop, Math.min(vh - pad - r.height - baseTop, y));
|
|
573
|
+
}
|
|
574
|
+
return { x, y };
|
|
575
|
+
};
|
|
576
|
+
let dragStart = null;
|
|
577
|
+
let didDrag = false;
|
|
578
|
+
const DRAG_THRESHOLD = 4;
|
|
579
|
+
const onPointerDown = (e) => {
|
|
580
|
+
if (nudgeMax <= 0 || e.button !== 0) return;
|
|
581
|
+
dragStart = { px: e.clientX, py: e.clientY, ox: nudge.x, oy: nudge.y };
|
|
582
|
+
didDrag = false;
|
|
583
|
+
trigger?.setPointerCapture?.(e.pointerId);
|
|
584
|
+
};
|
|
585
|
+
const onPointerMove = (e) => {
|
|
586
|
+
if (!dragStart) return;
|
|
587
|
+
const dx = e.clientX - dragStart.px;
|
|
588
|
+
const dy = e.clientY - dragStart.py;
|
|
589
|
+
if (!didDrag && Math.hypot(dx, dy) < DRAG_THRESHOLD) return;
|
|
590
|
+
didDrag = true;
|
|
591
|
+
nudge = clampOffset(dragStart.ox + dx, dragStart.oy + dy);
|
|
592
|
+
applyNudge();
|
|
593
|
+
e.preventDefault();
|
|
594
|
+
};
|
|
595
|
+
const onPointerUp = () => {
|
|
596
|
+
if (!dragStart) return;
|
|
597
|
+
dragStart = null;
|
|
598
|
+
if (didDrag) {
|
|
599
|
+
saveNudge();
|
|
600
|
+
suppressClick = true;
|
|
601
|
+
}
|
|
602
|
+
};
|
|
603
|
+
const onTriggerKey = (e) => {
|
|
604
|
+
if (nudgeMax <= 0) return;
|
|
605
|
+
const step = e.shiftKey ? 1 : 10;
|
|
606
|
+
let dx = 0;
|
|
607
|
+
let dy = 0;
|
|
608
|
+
if (e.key === "ArrowLeft") dx = -step;
|
|
609
|
+
else if (e.key === "ArrowRight") dx = step;
|
|
610
|
+
else if (e.key === "ArrowUp") dy = -step;
|
|
611
|
+
else if (e.key === "ArrowDown") dy = step;
|
|
612
|
+
else return;
|
|
613
|
+
e.preventDefault();
|
|
614
|
+
nudge = clampOffset(nudge.x + dx, nudge.y + dy);
|
|
615
|
+
applyNudge();
|
|
616
|
+
saveNudge();
|
|
617
|
+
};
|
|
404
618
|
const onMove = (e) => {
|
|
405
619
|
if (!state.readingGuide && !state.readingMask) return;
|
|
406
620
|
const y = e.touches?.[0]?.clientY ?? e.clientY;
|
|
@@ -414,7 +628,7 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
414
628
|
if (mask) mask.style.setProperty("--oks-mask-y", `${y}px`);
|
|
415
629
|
}
|
|
416
630
|
};
|
|
417
|
-
trigger
|
|
631
|
+
trigger?.addEventListener("click", onTriggerClick);
|
|
418
632
|
closeBtn.addEventListener("click", closePanel);
|
|
419
633
|
resetBtn.addEventListener("click", onReset);
|
|
420
634
|
for (const btn of opts$) btn.addEventListener("click", onOptClick);
|
|
@@ -422,9 +636,19 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
422
636
|
document.addEventListener("keydown", onKeyDown);
|
|
423
637
|
document.addEventListener("mousemove", onMove);
|
|
424
638
|
document.addEventListener("touchmove", onMove, { passive: true });
|
|
639
|
+
if (nudgeMax > 0 && trigger) {
|
|
640
|
+
loadNudge();
|
|
641
|
+
applyNudge();
|
|
642
|
+
trigger.style.cursor = "grab";
|
|
643
|
+
trigger.style.touchAction = "none";
|
|
644
|
+
trigger.addEventListener("pointerdown", onPointerDown);
|
|
645
|
+
document.addEventListener("pointermove", onPointerMove);
|
|
646
|
+
document.addEventListener("pointerup", onPointerUp);
|
|
647
|
+
trigger.addEventListener("keydown", onTriggerKey);
|
|
648
|
+
}
|
|
425
649
|
applyState();
|
|
426
|
-
|
|
427
|
-
trigger
|
|
650
|
+
const dispose = () => {
|
|
651
|
+
trigger?.removeEventListener("click", onTriggerClick);
|
|
428
652
|
closeBtn.removeEventListener("click", closePanel);
|
|
429
653
|
resetBtn.removeEventListener("click", onReset);
|
|
430
654
|
for (const btn of opts$) btn.removeEventListener("click", onOptClick);
|
|
@@ -432,7 +656,12 @@ function bindPanelBehavior(root, opts = {}) {
|
|
|
432
656
|
document.removeEventListener("keydown", onKeyDown);
|
|
433
657
|
document.removeEventListener("mousemove", onMove);
|
|
434
658
|
document.removeEventListener("touchmove", onMove);
|
|
659
|
+
trigger?.removeEventListener("pointerdown", onPointerDown);
|
|
660
|
+
document.removeEventListener("pointermove", onPointerMove);
|
|
661
|
+
document.removeEventListener("pointerup", onPointerUp);
|
|
662
|
+
trigger?.removeEventListener("keydown", onTriggerKey);
|
|
435
663
|
};
|
|
664
|
+
return Object.assign(dispose, { open: openPanel, close: closePanel, toggle: togglePanel });
|
|
436
665
|
}
|
|
437
666
|
function ensureOverlay() {
|
|
438
667
|
let el = document.getElementById("oks-overlay-gray");
|
|
@@ -487,6 +716,12 @@ var PANEL_CSS = `
|
|
|
487
716
|
width: 60%;
|
|
488
717
|
height: 60%;
|
|
489
718
|
}
|
|
719
|
+
/* Porthole preset: a brushed-metal ring framing the standard glyph. The glyph
|
|
720
|
+
itself is untouched (discoverability), only the button gets the frame.
|
|
721
|
+
Hosts wanting a bespoke frame can target ::part(trigger) from light DOM. */
|
|
722
|
+
.oks-access-btn[data-trigger-icon="porthole"] {
|
|
723
|
+
box-shadow: 0 0 0 4px #b0bec5, 0 0 0 7px #546e7a, 0 6px 18px rgba(0,0,0,0.3);
|
|
724
|
+
}
|
|
490
725
|
.oks-active-badge {
|
|
491
726
|
position: absolute;
|
|
492
727
|
top: -2px;
|
|
@@ -838,6 +1073,6 @@ body.oks-a11y-bigtargets summary:not(oksigenia-access-panel):not(oksigenia-acces
|
|
|
838
1073
|
}
|
|
839
1074
|
`;
|
|
840
1075
|
|
|
841
|
-
export { COLORBLIND_FILTERS_SVG, DEFAULT_STATE, EFFECT_CSS, PANEL_CSS, bindPanelBehavior, buildPanelHtml, getTranslation, isStateEmpty, loadState, positionCss, saveState, supportedLocales };
|
|
1076
|
+
export { ALL_CONTROLS, COLORBLIND_FILTERS_SVG, DEFAULT_STATE, EFFECT_CSS, PANEL_CSS, PRESETS, PRESET_IDS, bindPanelBehavior, buildPanelHtml, getTranslation, isStateEmpty, loadState, positionCss, resolveEnabledControls, saveState, supportedLocales };
|
|
842
1077
|
//# sourceMappingURL=index.js.map
|
|
843
1078
|
//# sourceMappingURL=index.js.map
|