@oksigenia/access-panel 0.7.3 → 0.8.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.
@@ -1,6 +1,11 @@
1
+ import { b as PanelChangeDetail } from './behavior-CEe_YuMU.js';
2
+
1
3
  declare class OksigeniaAccessPanelElement extends HTMLElement {
2
4
  static get observedAttributes(): readonly string[];
3
5
  private _controller;
6
+ /** `initial-state` is read on the first render only: later re-renders
7
+ * (another attribute changing) must not undo what the visitor did since. */
8
+ private _initialStateRead;
4
9
  private readonly _fxId;
5
10
  private readonly _scopeId;
6
11
  constructor();
@@ -33,6 +38,9 @@ declare global {
33
38
  interface HTMLElementTagNameMap {
34
39
  'oksigenia-access-panel': OksigeniaAccessPanelElement;
35
40
  }
41
+ interface HTMLElementEventMap {
42
+ 'oksiac:change': CustomEvent<PanelChangeDetail>;
43
+ }
36
44
  }
37
45
 
38
46
  export { OksigeniaAccessPanelElement };
@@ -283,14 +283,42 @@ var DEFAULT_STATE = Object.freeze({
283
283
  readingMask: false,
284
284
  bigTargets: false
285
285
  });
286
+ var LEVEL_MAX = Object.freeze({
287
+ zoom: 4,
288
+ lh: 3,
289
+ align: 3,
290
+ ls: 3,
291
+ colorblind: 3
292
+ });
293
+ function sanitizeState(input) {
294
+ if (!input || typeof input !== "object" || Array.isArray(input)) return null;
295
+ const src = input;
296
+ const out = { ...DEFAULT_STATE };
297
+ for (const key of Object.keys(DEFAULT_STATE)) {
298
+ const v = src[key];
299
+ if (v === void 0) continue;
300
+ if (key in LEVEL_MAX) {
301
+ if (typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= LEVEL_MAX[key]) {
302
+ out[key] = v;
303
+ }
304
+ } else if (v === true || v === 1) {
305
+ out[key] = true;
306
+ }
307
+ }
308
+ return out;
309
+ }
310
+ function parseState(json) {
311
+ if (json == null || json.trim() === "") return null;
312
+ try {
313
+ return sanitizeState(JSON.parse(json));
314
+ } catch {
315
+ return null;
316
+ }
317
+ }
286
318
  function loadState(key) {
287
319
  try {
288
320
  if (typeof localStorage === "undefined") return { ...DEFAULT_STATE };
289
- const raw = localStorage.getItem(key);
290
- if (!raw) return { ...DEFAULT_STATE };
291
- const parsed = JSON.parse(raw);
292
- if (!parsed || typeof parsed !== "object") return { ...DEFAULT_STATE };
293
- return { ...DEFAULT_STATE, ...parsed };
321
+ return parseState(localStorage.getItem(key)) ?? { ...DEFAULT_STATE };
294
322
  } catch {
295
323
  return { ...DEFAULT_STATE };
296
324
  }
@@ -385,7 +413,19 @@ function bindPanelBehavior(root, opts = {}) {
385
413
  if (!panel || !closeBtn || !resetBtn) {
386
414
  return noopController();
387
415
  }
388
- let state = loadState(storageKey);
416
+ let state;
417
+ if (opts.initialState) {
418
+ state = { ...opts.initialState };
419
+ saveState(storageKey, state);
420
+ } else {
421
+ state = loadState(storageKey);
422
+ }
423
+ const emitChange = () => {
424
+ const host = root.host;
425
+ if (!host) return;
426
+ const detail = { state: { ...state } };
427
+ host.dispatchEvent(new CustomEvent("oksiac:change", { bubbles: true, composed: true, detail }));
428
+ };
389
429
  const scoped = opts.scopeEl !== void 0;
390
430
  const GLOBAL_KEY = "oksiac::global";
391
431
  const GLOBAL_MAP = [
@@ -561,11 +601,13 @@ function bindPanelBehavior(root, opts = {}) {
561
601
  }
562
602
  applyState();
563
603
  saveState(storageKey, state);
604
+ emitChange();
564
605
  };
565
606
  const onReset = () => {
566
607
  state = { ...DEFAULT_STATE };
567
608
  applyState();
568
609
  saveState(storageKey, state);
610
+ emitChange();
569
611
  if (scoped) {
570
612
  const cleared = { bigCursor: false, readingGuide: false, readingMask: false };
571
613
  saveGlobal(cleared);
@@ -939,7 +981,7 @@ var PANEL_CSS = `
939
981
  font-size: 11px;
940
982
  font-weight: 800;
941
983
  text-transform: uppercase;
942
- color: #888;
984
+ color: #595959;
943
985
  }
944
986
  /* Screen-reader-only: keeps the category headings in the accessibility tree
945
987
  (flat scoped layout) while taking them out of the grid flow so they don't
@@ -1127,6 +1169,12 @@ body.oks-ls-1 * { letter-spacing: 0.05em !important; }
1127
1169
  body.oks-ls-2 * { letter-spacing: 0.10em !important; }
1128
1170
  body.oks-ls-3 * { letter-spacing: 0.16em !important; }
1129
1171
 
1172
+ /* Pages that declare a dark theme (Bootstrap 5.3's data-bs-theme, which Moodle
1173
+ 5.3's colour modes use, or data-theme): the default focus blue falls under 3:1
1174
+ against a dark background, so the fallback moves to a lighter blue. An explicit
1175
+ --oks-focus-color still wins. */
1176
+ html[data-bs-theme="dark"], html[data-theme="dark"] { --oks-focus-auto: #6ea8fe; }
1177
+
1130
1178
  /* Focus highlight colour is themeable via --oks-focus-color (default #005fcc):
1131
1179
  set it on :root for a dark theme so the highlight stays visible. The
1132
1180
  persistent "all interactive" dashed derives a 45% tint from it; the glow can
@@ -1136,13 +1184,13 @@ body.oks-a11y-focus button:not(oksigenia-access-panel):not(oksigenia-access-pane
1136
1184
  body.oks-a11y-focus input,
1137
1185
  body.oks-a11y-focus select,
1138
1186
  body.oks-a11y-focus textarea {
1139
- outline: 2px dashed color-mix(in srgb, var(--oks-focus-color, #005fcc) 45%, transparent) !important;
1187
+ outline: 2px dashed color-mix(in srgb, var(--oks-focus-color, var(--oks-focus-auto, #005fcc)) 45%, transparent) !important;
1140
1188
  outline-offset: 2px !important;
1141
1189
  }
1142
1190
  body.oks-a11y-focus *:focus-visible {
1143
- outline: 3px solid var(--oks-focus-color, #005fcc) !important;
1191
+ outline: 3px solid var(--oks-focus-color, var(--oks-focus-auto, #005fcc)) !important;
1144
1192
  outline-offset: 3px !important;
1145
- box-shadow: 0 0 0 6px var(--oks-focus-glow, color-mix(in srgb, var(--oks-focus-color, #005fcc) 25%, transparent)) !important;
1193
+ box-shadow: 0 0 0 6px var(--oks-focus-glow, color-mix(in srgb, var(--oks-focus-color, var(--oks-focus-auto, #005fcc)) 25%, transparent)) !important;
1146
1194
  }
1147
1195
 
1148
1196
  body.oks-a11y-contrast.oks-a11y-focus *:focus,
@@ -1249,7 +1297,8 @@ ${s}.oks-zoom-2 { font-size: 120% !important; }
1249
1297
  ${s}.oks-zoom-3 { font-size: 135% !important; }
1250
1298
  ${s}.oks-zoom-4 { font-size: 150% !important; }
1251
1299
  `;
1252
- return `${zoom}
1300
+ return `html[data-bs-theme="dark"], html[data-theme="dark"] { --oks-focus-auto: #6ea8fe; }
1301
+ ${zoom}
1253
1302
  ${s}.oks-lh-1 * { line-height: 1.6 !important; }
1254
1303
  ${s}.oks-lh-2 * { line-height: 1.9 !important; }
1255
1304
  ${s}.oks-lh-3 * { line-height: 2.2 !important; }
@@ -1281,13 +1330,13 @@ ${s}.oks-a11y-focus button,
1281
1330
  ${s}.oks-a11y-focus input,
1282
1331
  ${s}.oks-a11y-focus select,
1283
1332
  ${s}.oks-a11y-focus textarea {
1284
- outline: 2px dashed color-mix(in srgb, var(--oks-focus-color, #005fcc) 45%, transparent) !important;
1333
+ outline: 2px dashed color-mix(in srgb, var(--oks-focus-color, var(--oks-focus-auto, #005fcc)) 45%, transparent) !important;
1285
1334
  outline-offset: 2px !important;
1286
1335
  }
1287
1336
  ${s}.oks-a11y-focus *:focus-visible {
1288
- outline: 3px solid var(--oks-focus-color, #005fcc) !important;
1337
+ outline: 3px solid var(--oks-focus-color, var(--oks-focus-auto, #005fcc)) !important;
1289
1338
  outline-offset: 3px !important;
1290
- box-shadow: 0 0 0 6px var(--oks-focus-glow, color-mix(in srgb, var(--oks-focus-color, #005fcc) 25%, transparent)) !important;
1339
+ box-shadow: 0 0 0 6px var(--oks-focus-glow, color-mix(in srgb, var(--oks-focus-color, var(--oks-focus-auto, #005fcc)) 25%, transparent)) !important;
1291
1340
  }
1292
1341
 
1293
1342
  ${s}.oks-a11y-contrast, ${s}.oks-a11y-contrast * {
@@ -1383,6 +1432,9 @@ var OksigeniaAccessPanelElement = class extends HTMLElement {
1383
1432
  return OBSERVED;
1384
1433
  }
1385
1434
  _controller = null;
1435
+ /** `initial-state` is read on the first render only: later re-renders
1436
+ * (another attribute changing) must not undo what the visitor did since. */
1437
+ _initialStateRead = false;
1386
1438
  _fxId = `oks-access-fx-${++fxSeq}`;
1387
1439
  _scopeId = `oks-access-scope-${this._fxId.split("-").pop()}`;
1388
1440
  constructor() {
@@ -1498,12 +1550,15 @@ var OksigeniaAccessPanelElement = class extends HTMLElement {
1498
1550
  });
1499
1551
  shadow.innerHTML = `<style>${PANEL_CSS}${positionCss(position, positionMobile)}</style>${html}`;
1500
1552
  const scopeEl = scope ? document.querySelector(scope) : void 0;
1553
+ const initialState = this._initialStateRead ? null : parseState(this.getAttribute("initial-state"));
1554
+ this._initialStateRead = true;
1501
1555
  this._controller = bindPanelBehavior(shadow, {
1502
1556
  storageKey: this.getAttribute("storage-key") ?? void 0,
1503
1557
  locale: this.getLocale(),
1504
1558
  enabled,
1505
1559
  nudgeMax: this.getNudgeMax(),
1506
- scopeEl
1560
+ scopeEl,
1561
+ initialState
1507
1562
  });
1508
1563
  this.updateScopeStyle(scope);
1509
1564
  this.updateEffectsExclude(scope ?? "body");