access-settings 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Yannick Bochatay https://github.com/YannickBochatay
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,120 @@
1
+ # access-settings
2
+ Web component for accessibility settings
3
+
4
+ ## Demo
5
+ https://yannickbochatay.github.io/access-settings/example
6
+
7
+ ## Installation
8
+
9
+ ### From CDN
10
+ The fastest and simplest way to get started.
11
+
12
+ ```html
13
+ <!DOCTYPE html>
14
+ <html lang="fr">
15
+ <head>
16
+ <meta charset="UTF-8">
17
+ <title>My accessible page</title>
18
+ <script src="https://cdn.jsdelivr.net/npm/access-settings@0.0.1/"></script>
19
+ </head>
20
+ <body>
21
+ <access-settings all></accessibility-settings>
22
+ </body>
23
+ </html>
24
+ ```
25
+
26
+ ### NPM
27
+ ```sh
28
+ npm install access-settings
29
+ ```
30
+ ```html
31
+ <script src="node_modules/access-settings/dist/index.js"></script>
32
+ ```
33
+
34
+ ### Direct download
35
+
36
+ You can download the [file directly from GitHub]((https://raw.githubusercontent.com/YannickBochatay/access-settings/refs/heads/main/dist/index.js)).
37
+
38
+
39
+ ## Usage
40
+
41
+ ### Attributes
42
+ #### all
43
+ To display all the options, add the `all` attribute. Otherwise add specific options to display.
44
+ ```html
45
+ <access-settings all>
46
+ </access-settings>
47
+ ```
48
+ ```html
49
+ <access-settings dyslexic-font invert-colors font-size line-height>
50
+ </access-settings>
51
+ ```
52
+
53
+ #### side
54
+ By default, the component has a fixed position at the top right of the screen. The drop-down menu will align to the right with the icon. To position it on the left, use the ‘side’ attribute.
55
+ ```html
56
+ <access-settings side="left">
57
+ </access-settings>
58
+ ```
59
+
60
+ #### rounded
61
+ To display the icon in a circle, use `rounded` attribute.
62
+ ```html
63
+ <access-settings all rounded>
64
+ </access-settings>
65
+ ```
66
+
67
+ ## Customisation
68
+
69
+ ### Position
70
+ Customise the position of the component in CSS
71
+ ```css
72
+ accessibility-settings {
73
+ position:absolute;
74
+ top:150px;
75
+ right:5px;
76
+ }
77
+ ```
78
+ Define `left` css property if the attribute `side` is set to left.
79
+
80
+ ### Icon style
81
+ ```css
82
+ access-settings::part(icon) {
83
+ background-color: brown;
84
+ fill:white;
85
+ }
86
+ ```
87
+
88
+ ### Change icon
89
+ ```html
90
+ <access-settings all>
91
+ <span slot="icon">⚙︎</span>
92
+ </access-settings>
93
+ ```
94
+
95
+ ### Add more options
96
+ ```html
97
+ <access-settings all>
98
+ <div slot="option">
99
+ <label>
100
+ <input type="checkbox">
101
+ One more option
102
+ </label>
103
+ </div>
104
+ <div slot="option">
105
+ <label>
106
+ <input type="checkbox"/>
107
+ Another option
108
+ </label>
109
+ </div>
110
+ </access-settings>
111
+ ```
112
+ Of course you'll have to write some javascript to make these new options work.
113
+
114
+ Why not a web component ? It could look like this :
115
+ ```html
116
+ <access-settings all>
117
+ <access-more-option1 slot="option"/>
118
+ <access-more-option2 slot="option"/>
119
+ </access-settings>
120
+ ```
package/dist/index.js ADDED
@@ -0,0 +1,373 @@
1
+ (() => {
2
+ // src/globalStyles.js
3
+ var globalStyles = document.createElement("style");
4
+ globalStyles.innerHTML = `
5
+ @font-face {
6
+ font-family: open-dyslexic;
7
+ src: url(https://fonts.cdnfonts.com/s/29616/open-dyslexic.woff);
8
+ }
9
+ :root.dyslexic {
10
+ font-family:open-dyslexic, sans-serif;
11
+ }
12
+ :root.invertedColors {
13
+ filter:invert(1);
14
+ }
15
+ @media (prefers-color-scheme: dark) {
16
+ :root:has(access-settings[invert-colors], access-settings[all]) {
17
+ filter:invert(1);
18
+ &.invertedColors {
19
+ filter:invert(0);
20
+ }
21
+ }
22
+ }
23
+ `;
24
+ document.head.append(globalStyles);
25
+
26
+ // src/createState.js
27
+ function createState(initialState) {
28
+ const listeners = [];
29
+ function createProxy(target) {
30
+ return new Proxy(target, {
31
+ set(target2, prop, value) {
32
+ target2[prop] = value;
33
+ listeners.forEach((callback) => callback(String(prop), value));
34
+ return true;
35
+ },
36
+ get(target2, prop) {
37
+ if (prop === "_isProxy") return true;
38
+ if (target2[prop]?._isProxy) return target2[prop];
39
+ if (target2[prop] && typeof target2[prop] === "object") return createProxy(target2[prop]);
40
+ return target2[prop];
41
+ }
42
+ });
43
+ }
44
+ return {
45
+ state: createProxy(initialState),
46
+ onStateChange(callback) {
47
+ listeners.push(callback);
48
+ },
49
+ offStateChange(callback) {
50
+ const index = listeners.indexOf(callback);
51
+ if (index !== -1) listeners.splice(index, 1);
52
+ }
53
+ };
54
+ }
55
+
56
+ // src/preferences.js
57
+ var root = document.documentElement;
58
+ function getInitialFontSize() {
59
+ let fontSize = getComputedStyle(root).fontSize;
60
+ return Number.parseInt(fontSize);
61
+ }
62
+ function getInitialLineHeight() {
63
+ let lineHeight = getComputedStyle(root).lineHeight;
64
+ return Number.parseInt(lineHeight) / getInitialFontSize();
65
+ }
66
+ var initialPrefs = {
67
+ dyslexicFont: false,
68
+ invertedColors: false,
69
+ contraste: 100,
70
+ fontSize: getInitialFontSize(),
71
+ lineHeight: getInitialLineHeight()
72
+ };
73
+ var { state: preferences, onStateChange, offStateChange } = createState(initialPrefs);
74
+ onStateChange((prop, value) => {
75
+ switch (prop) {
76
+ case "dyslexicFont":
77
+ if (value) root.classList.add("dyslexic");
78
+ else root.classList.remove("dyslexic");
79
+ break;
80
+ case "invertedColors":
81
+ if (value) root.classList.add("invertedColors");
82
+ else root.classList.remove("invertedColors");
83
+ break;
84
+ case "fontSize":
85
+ root.style.fontSize = value + "px";
86
+ break;
87
+ case "lineHeight":
88
+ root.style.lineHeight = value;
89
+ break;
90
+ }
91
+ });
92
+ var defaultPrefs = { ...initialPrefs };
93
+ function resetPrefs() {
94
+ for (let key in defaultPrefs) {
95
+ preferences[key] = defaultPrefs[key];
96
+ }
97
+ }
98
+ var STORAGE_NAME = "preferences";
99
+ onStateChange(() => {
100
+ localStorage.setItem(STORAGE_NAME, JSON.stringify(preferences));
101
+ });
102
+ var storedData = localStorage.getItem(STORAGE_NAME);
103
+ if (storedData) {
104
+ const data = JSON.parse(storedData);
105
+ for (let key in data) {
106
+ preferences[key] = data[key];
107
+ }
108
+ }
109
+
110
+ // src/style.js
111
+ var style = `
112
+ :host {
113
+ font-size:18px;
114
+ line-height:1.5;
115
+ position:fixed;
116
+ top:3px;
117
+ right:5px;
118
+ }
119
+ :host([side="left"]) {
120
+ right:unset;
121
+ left:5px;
122
+ details {
123
+ align-items: flex-start;
124
+ }
125
+ }
126
+ :host([all]), :host([dyslexic-font]) {
127
+ form .field[part=dyslexic-font] {
128
+ display:block;
129
+ }
130
+ }
131
+ :host([all]), :host([invert-colors]) {
132
+ form .field[part=invert-colors] {
133
+ display:block;
134
+ }
135
+ }
136
+ :host([all]), :host([font-size]) {
137
+ form .field[part=font-size] {
138
+ display:block;
139
+ }
140
+ }
141
+ :host([all]), :host([line-height]) {
142
+ form .field[part=line-height] {
143
+ display:block;
144
+ }
145
+ }
146
+ :host([rounded]) ::slotted([slot=icon]), :host([rounded]) #default-icon {
147
+ border-radius:50%;
148
+ }
149
+
150
+ details {
151
+ display:flex;
152
+ flex-direction:column;
153
+ align-items:flex-end;
154
+
155
+ summary {
156
+ cursor:pointer;
157
+ display:flex;
158
+ align-items:center;
159
+
160
+ #default-icon {
161
+ border:1px solid #ccc;
162
+ background-color:#ddd;
163
+ border-radius:5px;
164
+ width:40px;
165
+ height:40px;
166
+ &:hover {
167
+ background-color:#e1e1e1;
168
+ }
169
+ }
170
+ ::slotted([slot=icon]) {
171
+ border:1px solid #ccc;
172
+ background-color:#ddd;
173
+ border-radius:5px;
174
+ padding:0 8px;
175
+ font-size:30px;
176
+ }
177
+ }
178
+
179
+ form {
180
+ font-size:1em;
181
+ border:1px solid #ccc;
182
+ color:#222;
183
+ background-color:#fafafa;
184
+ line-height:2.5;
185
+ text-align: left;
186
+ border-radius:5px;
187
+
188
+ .field, ::slotted([slot=option]) {
189
+ padding: 0 25px 0 15px;
190
+ display:block;
191
+ &:hover {
192
+ background-color:#eee;
193
+ }
194
+ }
195
+
196
+ .field {
197
+ display:none;
198
+ }
199
+
200
+ input {
201
+ font-size:1em;
202
+ font-family:unset;
203
+ }
204
+
205
+ input[type=number] {
206
+ width:5ch;
207
+ border:1px solid #ccc;
208
+ border-radius:5px;
209
+ padding:3px;
210
+ }
211
+
212
+ [part=buttons] {
213
+ text-align:center;
214
+ }
215
+ }
216
+ }
217
+ `;
218
+
219
+ // src/template.js
220
+ var template = document.createElement("template");
221
+ template.innerHTML = `
222
+ <style>${style}</style>
223
+ <details part="details">
224
+ <summary part="summary" aria-label="accessibility settings">
225
+ <slot name="icon">
226
+ <svg viewBox="0 0 389.9 389.6" part="icon" id="default-icon">
227
+ <circle fill="none" cx="250.6" cy="146.4" r="35.7" transform="matrix(.160226 -.98708 .98708 .160226 6.75 311.71)"></circle>
228
+ <path d="M191.4 130.7c-23.693 0-42.9-19.207-42.9-42.9s19.207-42.9 42.9-42.9 42.9 19.207 42.9 42.9a42.89 42.89 0 0 1-42.9
229
+ 42.9zm0-71.5c-13.69-.038-25.498 9.605-28.197 23.026a28.68 28.68 0 0 0 17.105 32.135c12.641 5.256 27.234.846 34.848-10.531A28.68
230
+ 28.68 0 0 0 211.6 67.6a29.06 29.06 0 0 0-20.2-8.4zm52.5 278.6a21.46 21.46 0 0 1-19.5-12.6l-33.1-80.3-32.7 80.1a21.41 21.41 0 0
231
+ 1-37.1 4.1 21.57 21.57 0 0 1-2.1-21.5l34.4-87.5a26.63 26.63 0 0 0 1.9-10.4v-16.4a7.09 7.09 0 0 0-6.5-7.1l-60.6-5.5c-11.791
232
+ -.911-20.611-11.209-19.7-23s11.209-20.611 23-19.7l75.1 6.7a97.18 97.18 0 0 0 7.7.3h33.4a99.08 99.08 0 0 0 7.7-.3l75-6.7h.1c11.791
233
+ -.911 22.089 7.909 23 19.7s-7.909 22.089-19.7 23l-60.5 5.5a7.09 7.09 0 0 0-6.5 7.1v16.4a28.29 28.29 0 0 0 2 10.4l34.5 87.9a21.36
234
+ 21.36 0 0 1-1.8 20.2 22.06 22.06 0 0 1-18 9.6zm-52.5-107.1a14.11 14.11 0 0 1 13.1 8.8l33 80.1a7.62 7.62 0 0 0 3.9 3.6 7.13 7.13
235
+ 0 0 0 9-9.6l-34.6-88.3a42.14 42.14 0 0 1-3-15.7v-16.4c-.054-11.101 8.438-20.376 19.5-21.3l60.6-5.5a7 7 0 0 0 4.9-2.4 6.61 6.61
236
+ 0 0 0 1.7-5.2 7 7 0 0 0-7.6-6.6l-74.9 6.7a88.33 88.33 0 0 1-8.9.4h-33.4a87 87 0 0 1-8.9-.4l-75-6.7a7.12 7.12 0 0 0-1 14.2l60.7
237
+ 5.5c11.062.924 19.554 10.199 19.5 21.3v16.4a42.14 42.14 0 0 1-3 15.7l-34.5 87.9a7.09 7.09 0 0 0 .3 7.3 7.19 7.19 0 0 0 6.6 3.2
238
+ 7 7 0 0 0 5.9-4.3l32.9-79.9a14 14 0 0 1 13.2-8.8z">
239
+ </path>
240
+ </svg>
241
+ </slot>
242
+ </summary>
243
+ <form part="form">
244
+ <div class="field" part="dyslexic-font">
245
+ <input type="checkbox" id="dyslexic-font">
246
+ <label for="dyslexic-font">Police dyslexie</label>
247
+ </div>
248
+ <div class="field" part="invert-colors">
249
+ <input type="checkbox" id="inverted-colors">
250
+ <label for="inverted-colors">Couleurs invers\xE9es</label>
251
+ </div>
252
+ <div class="field" part="font-size">
253
+ <input type="number" id="font-size">
254
+ <label for="font-size">Taille de police</label>
255
+ </div>
256
+ <div class="field" part="line-height">
257
+ <input type="number" id="line-height" step="0.1">
258
+ <label for="line-height">Interligne</label>
259
+ </div>
260
+ <slot name="option"></slot>
261
+ <div part="buttons">
262
+ <input type="button" id="reset" value="R\xE9initialiser"/>
263
+ <input type="button" id="close" value="Terminer"/>
264
+ </div>
265
+ </form>
266
+ </details>
267
+ `;
268
+
269
+ // src/languages.json
270
+ var languages_default = {
271
+ fr: {
272
+ "dyslexic-font": "Police dyslexie",
273
+ "inverted-colors": "Couleurs invers\xE9es",
274
+ "font-size": "Taille de police",
275
+ "line-height": "Hauteur de ligne",
276
+ reset: "R\xE9initialiser",
277
+ close: "Terminer"
278
+ },
279
+ en: {
280
+ "dyslexic-font": "Dyslexic font",
281
+ "inverted-colors": "Inverted colors",
282
+ "font-size": "Font size",
283
+ "line-height": "Line height",
284
+ reset: "Reset",
285
+ close: "Close"
286
+ }
287
+ };
288
+
289
+ // src/index.js
290
+ var AccessSettings = class extends HTMLElement {
291
+ static languages = languages_default;
292
+ #fontField;
293
+ #colorsField;
294
+ #fontSizeField;
295
+ #lineHeightField;
296
+ #observer;
297
+ constructor() {
298
+ super();
299
+ const root2 = this.attachShadow({ mode: "open" });
300
+ root2.append(template.content.cloneNode(true));
301
+ this.#fontField = root2.querySelector("#dyslexic-font");
302
+ this.#colorsField = root2.querySelector("#inverted-colors");
303
+ this.#fontSizeField = root2.querySelector("#font-size");
304
+ this.#lineHeightField = root2.querySelector("#line-height");
305
+ this.#fontField.addEventListener("change", (e) => preferences.dyslexicFont = e.target.checked);
306
+ this.#colorsField.addEventListener("change", (e) => preferences.invertedColors = e.target.checked);
307
+ this.#fontSizeField.addEventListener("change", (e) => preferences.fontSize = e.target.value);
308
+ this.#lineHeightField.addEventListener("change", (e) => preferences.lineHeight = e.target.value);
309
+ root2.querySelector("#reset").addEventListener("click", resetPrefs);
310
+ root2.querySelector("#close").addEventListener("click", () => {
311
+ root2.querySelector("details").open = false;
312
+ });
313
+ this.#observer = new MutationObserver((mutationList, observer) => {
314
+ for (const mutation of mutationList) {
315
+ if (mutation.attributeName === "lang") this.#handleLangChange();
316
+ }
317
+ });
318
+ }
319
+ #handleStateChange = () => {
320
+ this.#fontField.checked = preferences.dyslexicFont;
321
+ this.#colorsField.checked = preferences.invertedColors;
322
+ this.#fontSizeField.value = preferences.fontSize;
323
+ this.#lineHeightField.value = preferences.lineHeight;
324
+ };
325
+ #handleLangChange() {
326
+ const lang = document.documentElement.lang;
327
+ const locale = this.constructor.languages[lang];
328
+ const labels = this.shadowRoot.querySelectorAll("label");
329
+ for (let label of labels) {
330
+ let key = label.getAttribute("for");
331
+ if (locale[key]) label.textContent = locale[key];
332
+ }
333
+ for (let id of ["close", "reset"]) {
334
+ this.shadowRoot.querySelector(`#${id}`).value = locale[id];
335
+ }
336
+ }
337
+ connectedCallback() {
338
+ this.#handleStateChange();
339
+ this.#handleLangChange();
340
+ onStateChange(this.#handleStateChange);
341
+ this.#observer.observe(document.documentElement, { attributes: true });
342
+ }
343
+ disconnectedCallback() {
344
+ offStateChange(this.#handleStateChange);
345
+ this.#observer.disconnect();
346
+ }
347
+ };
348
+ customElements.define("access-settings", AccessSettings);
349
+ })();
350
+ /**
351
+ * @license
352
+ * MIT License
353
+
354
+ Copyright (c) 2026 Yannick Bochatay https://github.com/YannickBochatay
355
+
356
+ Permission is hereby granted, free of charge, to any person obtaining a copy
357
+ of this software and associated documentation files (the "Software"), to deal
358
+ in the Software without restriction, including without limitation the rights
359
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
360
+ copies of the Software, and to permit persons to whom the Software is
361
+ furnished to do so, subject to the following conditions:
362
+
363
+ The above copyright notice and this permission notice shall be included in all
364
+ copies or substantial portions of the Software.
365
+
366
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
367
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
368
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
369
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
370
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
371
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
372
+ SOFTWARE.
373
+ **/