@wpmoo/ui 2.2.0 → 2.3.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/README.md +28 -2
- package/THIRD_PARTY_NOTICES.md +1 -1
- package/certification.json +2 -1
- package/dist/assets/css/moo-ui.css +1 -1
- package/dist/assets/css/moo-ui.min.css +1 -1
- package/dist/assets/css/moo.css +1 -1
- package/dist/assets/css/moo.min.css +1 -1
- package/dist/js/chart.js +1 -1
- package/dist/js/chart.min.js +1 -1
- package/dist/js/combobox.js +1 -1
- package/dist/js/consent.js +342 -0
- package/dist/js/context-menu.js +1 -1
- package/dist/js/datatable.js +1 -1
- package/dist/js/datepicker.js +1 -1
- package/dist/js/datepicker.min.js +1 -1
- package/dist/js/floating-panel.js +1 -1
- package/dist/js/image-gallery.js +1 -1
- package/dist/js/moo-ui.js +324 -4
- package/dist/js/moo-ui.min.js +2 -2
- package/dist/js/overlay-scrollbar.js +1 -1
- package/dist/js/password-requirements.js +1 -1
- package/dist/js/sheet.js +1 -1
- package/dist/js/sidebar.js +1 -1
- package/dist/js/slider.js +1 -1
- package/dist/js/state.js +1 -1
- package/dist/js/table-of-contents.js +1 -1
- package/dist/js/toc.js +1 -1
- package/dist/release-manifest.json +4 -4
- package/package.json +3 -1
- package/scss/mixins/_banner.scss +1 -1
package/README.md
CHANGED
|
@@ -210,6 +210,26 @@ runs the slideshow and full-screen buttons, adds arrow keys and loads only the
|
|
|
210
210
|
current photo and its neighbours. See the
|
|
211
211
|
[Image Gallery block](https://ui.wpmoo.org/blocks/image-gallery/) for markup.
|
|
212
212
|
|
|
213
|
+
For the Consent block, bind the card once per document. Gated scripts,
|
|
214
|
+
frames and images carry `data-consent-category` and `data-consent-src` and
|
|
215
|
+
are activated only after their category is granted:
|
|
216
|
+
|
|
217
|
+
```js
|
|
218
|
+
import { initConsent } from "@wpmoo/ui/consent.js";
|
|
219
|
+
const consent = initConsent(document);
|
|
220
|
+
consent?.whenGranted("statistics", () => loadStatistics());
|
|
221
|
+
document.addEventListener("moo:consent:change", (event) => console.log(event.detail.choices));
|
|
222
|
+
```
|
|
223
|
+
|
|
224
|
+
Without the module the card stays hidden and nothing optional loads. The
|
|
225
|
+
choice is stored in a first-party cookie with the configuration revision and
|
|
226
|
+
a time stamp; `consentRevision(categories, reask)` computes that revision.
|
|
227
|
+
Adding or removing a category or service asks again; for a material change
|
|
228
|
+
to a service's provider, purpose or storage, raise `reask`. Withdrawal removes
|
|
229
|
+
declared first-party cookies on the root path and storage keys; cookies set on
|
|
230
|
+
another path or by another host cannot be removed by the page.
|
|
231
|
+
See the [Consent block](https://ui.wpmoo.org/blocks/consent/) for markup.
|
|
232
|
+
|
|
213
233
|
For an opt-in document overlay scrollbar, load the stylesheet first and pass
|
|
214
234
|
the resolved page theme owner explicitly:
|
|
215
235
|
|
|
@@ -290,6 +310,12 @@ calm.
|
|
|
290
310
|
|
|
291
311
|
## Status And Support
|
|
292
312
|
|
|
313
|
+
The `2.3.0` minor release adds the Consent block: a first-visit consent card
|
|
314
|
+
with categories and services, a settings trigger, an embed placeholder and a
|
|
315
|
+
policy table, with the optional `@wpmoo/ui/consent.js` module that stores the
|
|
316
|
+
choice in a first-party cookie and loads gated scripts, frames and images only
|
|
317
|
+
after consent. Accordion items gain an optional action beside the trigger.
|
|
318
|
+
|
|
293
319
|
The `2.2.0` minor release adds the Item component, Image Gallery and Video
|
|
294
320
|
Dialog blocks, and component usage guides. Image Gallery includes five layouts,
|
|
295
321
|
a lazy-loaded lightbox, slideshow and full-screen controls through the optional
|
|
@@ -328,7 +354,7 @@ paths live in the [Installation guide](https://ui.wpmoo.org/installation/).
|
|
|
328
354
|
Moo UI source code is MIT licensed. License details live in
|
|
329
355
|
[LICENSE](LICENSE) and the [License page](https://ui.wpmoo.org/license/).
|
|
330
356
|
Third-party distribution details and notices are kept separately in
|
|
331
|
-
[THIRD_PARTY_NOTICES.md](https://github.com/wpmoo-org/ui/blob/v2.
|
|
357
|
+
[THIRD_PARTY_NOTICES.md](https://github.com/wpmoo-org/ui/blob/v2.3.0/THIRD_PARTY_NOTICES.md).
|
|
332
358
|
Catalog visual assets and their license are repository-only and are not
|
|
333
359
|
included in the npm package. Asset terms live in the
|
|
334
|
-
[visual asset license](https://github.com/wpmoo-org/ui/blob/v2.
|
|
360
|
+
[visual asset license](https://github.com/wpmoo-org/ui/blob/v2.3.0/ASSET_LICENSE.md).
|
package/THIRD_PARTY_NOTICES.md
CHANGED
|
@@ -83,5 +83,5 @@ The catalog serves its native ESM file and unchanged license separately under
|
|
|
83
83
|
WPMoo-generated visual assets, including image assets under `site/static/images/`,
|
|
84
84
|
are original WPMoo work and are not licensed under MIT unless explicitly
|
|
85
85
|
stated. See the
|
|
86
|
-
[repository visual asset license](https://github.com/wpmoo-org/ui/blob/v2.
|
|
86
|
+
[repository visual asset license](https://github.com/wpmoo-org/ui/blob/v2.3.0/ASSET_LICENSE.md).
|
|
87
87
|
These assets and their license file are not included in the npm package.
|
package/certification.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"schemaVersion": "0.1",
|
|
3
3
|
"status": "preview",
|
|
4
|
-
"coreVersion": "2.
|
|
4
|
+
"coreVersion": "2.3.0",
|
|
5
5
|
"bootstrap": {
|
|
6
6
|
"canonicalVersion": "5.3.3",
|
|
7
7
|
"targetRange": ">=5.3.0 <5.4",
|
|
@@ -35,6 +35,7 @@
|
|
|
35
35
|
"./sheet.js",
|
|
36
36
|
"./floating-panel.js",
|
|
37
37
|
"./image-gallery.js",
|
|
38
|
+
"./consent.js",
|
|
38
39
|
"./chart.js",
|
|
39
40
|
"./chart.min.js",
|
|
40
41
|
"./datepicker.js",
|
package/dist/assets/css/moo.css
CHANGED
package/dist/js/chart.js
CHANGED
package/dist/js/chart.min.js
CHANGED
package/dist/js/combobox.js
CHANGED
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* Moo UI consent.js v2.3.0 (https://wpmoo.org/)
|
|
3
|
+
* Copyright 2026 WPMoo Authors
|
|
4
|
+
* Licensed under MIT (https://github.com/wpmoo-org/ui/blob/main/LICENSE)
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const instances = /* @__PURE__ */ new WeakMap();
|
|
8
|
+
const documents = /* @__PURE__ */ new WeakMap();
|
|
9
|
+
const RECORD_VERSION = 1;
|
|
10
|
+
const DAY_SECONDS = 86400;
|
|
11
|
+
const FOCUSABLE = "button, a[href], input:not([type='hidden']), select, textarea, [tabindex]:not([tabindex='-1'])";
|
|
12
|
+
function webSource(value, base) {
|
|
13
|
+
if (!value) return "";
|
|
14
|
+
try {
|
|
15
|
+
const url = new URL(value, base);
|
|
16
|
+
return url.protocol === "https:" || url.protocol === "http:" ? url.href : "";
|
|
17
|
+
} catch {
|
|
18
|
+
return "";
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function matches(pattern, name) {
|
|
22
|
+
return pattern.endsWith("*") ? name.startsWith(pattern.slice(0, -1)) : name === pattern;
|
|
23
|
+
}
|
|
24
|
+
async function consentRevision(categories, reask = 0) {
|
|
25
|
+
const lines = [];
|
|
26
|
+
for (const category of categories || []) {
|
|
27
|
+
for (const service of category.services || []) lines.push(`${category.id}/${service.id}`);
|
|
28
|
+
}
|
|
29
|
+
lines.sort();
|
|
30
|
+
if (reask > 0) lines.push(`reask:${reask}`);
|
|
31
|
+
const bytes = new TextEncoder().encode(lines.join("\n"));
|
|
32
|
+
const digest = await globalThis.crypto.subtle.digest("SHA-256", bytes);
|
|
33
|
+
return Array.from(new Uint8Array(digest), (byte) => byte.toString(16).padStart(2, "0")).join("").slice(0, 12);
|
|
34
|
+
}
|
|
35
|
+
class ConsentManager {
|
|
36
|
+
/** The manager bound to a [data-consent] element or to a document. */
|
|
37
|
+
static getInstance(target) {
|
|
38
|
+
if (target?.nodeType === 9) return documents.get(target) || null;
|
|
39
|
+
return target?.nodeType === 1 ? instances.get(target) || null : null;
|
|
40
|
+
}
|
|
41
|
+
static getOrCreateInstance(element, options) {
|
|
42
|
+
return ConsentManager.getInstance(element) || new ConsentManager(element, options);
|
|
43
|
+
}
|
|
44
|
+
constructor(element, { reload } = {}) {
|
|
45
|
+
if (element?.nodeType !== 1 || !element.matches("[data-consent]") || !element.id) {
|
|
46
|
+
throw new TypeError("ConsentManager requires a named [data-consent] element.");
|
|
47
|
+
}
|
|
48
|
+
const existing = instances.get(element);
|
|
49
|
+
if (existing) return existing;
|
|
50
|
+
this._element = element;
|
|
51
|
+
this._document = element.ownerDocument;
|
|
52
|
+
if (documents.has(this._document)) {
|
|
53
|
+
throw new TypeError("ConsentManager allows one consent card per document.");
|
|
54
|
+
}
|
|
55
|
+
this._window = this._document.defaultView;
|
|
56
|
+
const source = element.querySelector("script[data-consent-config]");
|
|
57
|
+
if (!source) throw new TypeError("ConsentManager requires a [data-consent-config] script.");
|
|
58
|
+
this._config = JSON.parse(source.textContent);
|
|
59
|
+
this._cookie = { name: "moo_consent", days: 365, ...this._config.cookie || {} };
|
|
60
|
+
this._categories = this._config.categories || [];
|
|
61
|
+
this._optional = this._categories.filter((category) => !category.required).map((category) => category.id);
|
|
62
|
+
this._reload = reload || (() => this._window.location.reload());
|
|
63
|
+
this._waiting = /* @__PURE__ */ new Map();
|
|
64
|
+
this._listeners = [];
|
|
65
|
+
this._trapping = false;
|
|
66
|
+
this._returnFocus = null;
|
|
67
|
+
const record = this._read();
|
|
68
|
+
this._valid = Boolean(record);
|
|
69
|
+
this._choices = this._normalize(record ? record.c : {});
|
|
70
|
+
const control = (suffix) => this._document.getElementById(`${element.id}-${suffix}`);
|
|
71
|
+
const actions = [
|
|
72
|
+
[control("accept"), () => this.acceptAll()],
|
|
73
|
+
[control("decline"), () => this.declineAll()],
|
|
74
|
+
[control("save"), () => this.save(this._switchChoices(), "save")]
|
|
75
|
+
];
|
|
76
|
+
for (const [button, action] of actions) {
|
|
77
|
+
if (button) this._listen(button, "click", action);
|
|
78
|
+
}
|
|
79
|
+
this._listen(this._document, "click", (event) => this._onDocumentClick(event));
|
|
80
|
+
this._listen(element, "keydown", (event) => this._onKeydown(event));
|
|
81
|
+
instances.set(element, this);
|
|
82
|
+
documents.set(this._document, this);
|
|
83
|
+
this.activate(this._document);
|
|
84
|
+
if (!this._valid) this._show();
|
|
85
|
+
this.ready = Promise.resolve(this);
|
|
86
|
+
queueMicrotask(() => this._emit("ready", { choices: this.choices, valid: this._valid, revision: this.revision }));
|
|
87
|
+
}
|
|
88
|
+
get revision() {
|
|
89
|
+
return String(this._config.revision);
|
|
90
|
+
}
|
|
91
|
+
get valid() {
|
|
92
|
+
return this._valid;
|
|
93
|
+
}
|
|
94
|
+
get choices() {
|
|
95
|
+
return { ...this._choices };
|
|
96
|
+
}
|
|
97
|
+
get isOpen() {
|
|
98
|
+
return !this._element.hidden;
|
|
99
|
+
}
|
|
100
|
+
granted(id) {
|
|
101
|
+
const category = this._categories.find((entry) => entry.id === id);
|
|
102
|
+
if (!category) return false;
|
|
103
|
+
return category.required ? true : this._valid && this._choices[id] === true;
|
|
104
|
+
}
|
|
105
|
+
/** Runs callback now if granted, otherwise on grant. Returns an unsubscribe function. */
|
|
106
|
+
whenGranted(id, callback) {
|
|
107
|
+
if (this.granted(id)) {
|
|
108
|
+
this._run(callback);
|
|
109
|
+
return () => {
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (!this._waiting.has(id)) this._waiting.set(id, /* @__PURE__ */ new Set());
|
|
113
|
+
this._waiting.get(id).add(callback);
|
|
114
|
+
return () => this._waiting.get(id)?.delete(callback);
|
|
115
|
+
}
|
|
116
|
+
acceptAll() {
|
|
117
|
+
return this.save(Object.fromEntries(this._optional.map((id) => [id, true])), "accept-all");
|
|
118
|
+
}
|
|
119
|
+
declineAll() {
|
|
120
|
+
return this.save({}, "decline");
|
|
121
|
+
}
|
|
122
|
+
grant(id) {
|
|
123
|
+
return this.save({ ...this._choices, [id]: true }, "grant");
|
|
124
|
+
}
|
|
125
|
+
deny(id) {
|
|
126
|
+
return this.save({ ...this._choices, [id]: false }, "deny");
|
|
127
|
+
}
|
|
128
|
+
/** Stores the choice; optional categories not named become false. */
|
|
129
|
+
save(choices, source = "save") {
|
|
130
|
+
const previous = this._valid ? this.choices : this._normalize({});
|
|
131
|
+
const wasValid = this._valid;
|
|
132
|
+
const next = this._normalize(choices);
|
|
133
|
+
this._write(next);
|
|
134
|
+
this._valid = true;
|
|
135
|
+
this._choices = next;
|
|
136
|
+
const granted = this._optional.filter((id) => next[id] && !previous[id]);
|
|
137
|
+
const revoked = wasValid ? this._optional.filter((id) => previous[id] && !next[id]) : [];
|
|
138
|
+
this.close();
|
|
139
|
+
this.activate(this._document);
|
|
140
|
+
for (const id of granted) {
|
|
141
|
+
const waiting = this._waiting.get(id);
|
|
142
|
+
this._waiting.delete(id);
|
|
143
|
+
waiting?.forEach((callback) => this._run(callback));
|
|
144
|
+
}
|
|
145
|
+
this._emit("change", { choices: this.choices, previous, granted, revoked, source });
|
|
146
|
+
if (revoked.length) {
|
|
147
|
+
revoked.forEach((id) => this._forget(id));
|
|
148
|
+
this._reload();
|
|
149
|
+
}
|
|
150
|
+
return this.choices;
|
|
151
|
+
}
|
|
152
|
+
/** Opens the card with the stored choices; traps focus until closed. */
|
|
153
|
+
open(trigger = null) {
|
|
154
|
+
this._returnFocus = trigger;
|
|
155
|
+
this._trapping = true;
|
|
156
|
+
this._show();
|
|
157
|
+
this._document.getElementById(`${this._element.id}-title`)?.focus({ preventScroll: true });
|
|
158
|
+
this._emit("open", {});
|
|
159
|
+
}
|
|
160
|
+
/** Hides the card; it stays open while no valid choice exists. */
|
|
161
|
+
close() {
|
|
162
|
+
if (!this._valid) return false;
|
|
163
|
+
const wasOpen = this.isOpen;
|
|
164
|
+
const returnFocus = this._trapping ? this._returnFocus : null;
|
|
165
|
+
this._element.hidden = true;
|
|
166
|
+
this._trapping = false;
|
|
167
|
+
this._returnFocus = null;
|
|
168
|
+
if (returnFocus?.isConnected) returnFocus.focus({ preventScroll: true });
|
|
169
|
+
if (wasOpen) this._emit("close", {});
|
|
170
|
+
return true;
|
|
171
|
+
}
|
|
172
|
+
/** Activates gated elements under root whose category is granted. */
|
|
173
|
+
activate(root = this._document) {
|
|
174
|
+
this._activateWhere(root, (id) => this.granted(id));
|
|
175
|
+
}
|
|
176
|
+
dispose() {
|
|
177
|
+
if (instances.get(this._element) !== this) return;
|
|
178
|
+
this._listeners.forEach(([target, type, handler]) => target.removeEventListener(type, handler));
|
|
179
|
+
this._listeners = [];
|
|
180
|
+
this._waiting.clear();
|
|
181
|
+
instances.delete(this._element);
|
|
182
|
+
documents.delete(this._document);
|
|
183
|
+
}
|
|
184
|
+
// A failing site callback must not stop the rest of a save, such as the
|
|
185
|
+
// cleanup and reload after a withdrawal; the error is still reported.
|
|
186
|
+
_run(callback) {
|
|
187
|
+
try {
|
|
188
|
+
callback(this);
|
|
189
|
+
} catch (error) {
|
|
190
|
+
if (typeof this._window.reportError === "function") this._window.reportError(error);
|
|
191
|
+
else this._window.console?.error(error);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
_activateWhere(root, allowed) {
|
|
195
|
+
for (const element of root.querySelectorAll("[data-consent-category]")) {
|
|
196
|
+
if (allowed(element.getAttribute("data-consent-category"))) this._activateElement(element);
|
|
197
|
+
}
|
|
198
|
+
for (const placeholder of root.querySelectorAll("[data-consent-placeholder]")) {
|
|
199
|
+
if (allowed(placeholder.getAttribute("data-consent-placeholder"))) placeholder.hidden = true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
_listen(target, type, handler) {
|
|
203
|
+
target.addEventListener(type, handler);
|
|
204
|
+
this._listeners.push([target, type, handler]);
|
|
205
|
+
}
|
|
206
|
+
_emit(name, detail) {
|
|
207
|
+
this._document.dispatchEvent(new this._window.CustomEvent(`moo:consent:${name}`, { bubbles: true, detail }));
|
|
208
|
+
}
|
|
209
|
+
_normalize(choices) {
|
|
210
|
+
return Object.fromEntries(this._optional.map((id) => [id, choices?.[id] === true || choices?.[id] === 1]));
|
|
211
|
+
}
|
|
212
|
+
_switch(id) {
|
|
213
|
+
return this._document.getElementById(`${this._element.id}-category-${id}`);
|
|
214
|
+
}
|
|
215
|
+
_switchChoices() {
|
|
216
|
+
return Object.fromEntries(this._optional.map((id) => [id, Boolean(this._switch(id)?.checked)]));
|
|
217
|
+
}
|
|
218
|
+
_show() {
|
|
219
|
+
for (const id of this._optional) {
|
|
220
|
+
const input = this._switch(id);
|
|
221
|
+
if (input) input.checked = this.granted(id);
|
|
222
|
+
}
|
|
223
|
+
this._element.hidden = false;
|
|
224
|
+
}
|
|
225
|
+
_read() {
|
|
226
|
+
const prefix = `${this._cookie.name}=`;
|
|
227
|
+
const raw = this._document.cookie.split("; ").find((part) => part.startsWith(prefix));
|
|
228
|
+
if (!raw) return null;
|
|
229
|
+
try {
|
|
230
|
+
const record = JSON.parse(decodeURIComponent(raw.slice(prefix.length)));
|
|
231
|
+
const age = Date.now() / 1e3 - Number(record.t);
|
|
232
|
+
if (record.v !== RECORD_VERSION || record.r !== this.revision) return null;
|
|
233
|
+
if (!(age >= 0 && age < this._cookie.days * DAY_SECONDS)) return null;
|
|
234
|
+
return record;
|
|
235
|
+
} catch {
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
_write(choices) {
|
|
240
|
+
const record = {
|
|
241
|
+
v: RECORD_VERSION,
|
|
242
|
+
r: this.revision,
|
|
243
|
+
t: Math.floor(Date.now() / 1e3),
|
|
244
|
+
c: Object.fromEntries(Object.entries(choices).map(([id, value]) => [id, value ? 1 : 0]))
|
|
245
|
+
};
|
|
246
|
+
const secure = this._window.location.protocol === "https:" ? "; Secure" : "";
|
|
247
|
+
this._document.cookie = `${this._cookie.name}=${encodeURIComponent(JSON.stringify(record))}; Path=/; Max-Age=${this._cookie.days * DAY_SECONDS}; SameSite=Lax${secure}`;
|
|
248
|
+
}
|
|
249
|
+
_forget(id) {
|
|
250
|
+
const category = this._categories.find((entry) => entry.id === id);
|
|
251
|
+
const host = this._window.location.hostname;
|
|
252
|
+
const parts = host.split(".");
|
|
253
|
+
const domains = [""];
|
|
254
|
+
for (let index = 0; index < parts.length - 1; index += 1) domains.push(`; Domain=${parts.slice(index).join(".")}`);
|
|
255
|
+
const cookieNames = this._document.cookie.split("; ").map((part) => part.split("=")[0]).filter(Boolean);
|
|
256
|
+
for (const item of category?.storage || []) {
|
|
257
|
+
if (item.type === "cookie") {
|
|
258
|
+
for (const name of cookieNames.filter((candidate) => matches(item.name, candidate))) {
|
|
259
|
+
for (const domain of domains) this._document.cookie = `${name}=; Path=/; Max-Age=0${domain}`;
|
|
260
|
+
}
|
|
261
|
+
} else {
|
|
262
|
+
try {
|
|
263
|
+
const storage = item.type === "local" ? this._window.localStorage : this._window.sessionStorage;
|
|
264
|
+
Object.keys(storage).filter((key) => matches(item.name, key)).forEach((key) => storage.removeItem(key));
|
|
265
|
+
} catch {
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
_activateElement(element) {
|
|
271
|
+
if (element.hasAttribute("data-consent-active")) return;
|
|
272
|
+
const declared = element.getAttribute("data-consent-src");
|
|
273
|
+
const src = webSource(declared, this._document.baseURI);
|
|
274
|
+
if (declared && !src) return;
|
|
275
|
+
if (element.localName === "script") {
|
|
276
|
+
const script = this._document.createElement("script");
|
|
277
|
+
for (const { name, value } of element.attributes) {
|
|
278
|
+
if (name !== "type" && !name.startsWith("data-consent-")) script.setAttribute(name, value);
|
|
279
|
+
}
|
|
280
|
+
const type = element.getAttribute("data-consent-type");
|
|
281
|
+
if (type) script.type = type;
|
|
282
|
+
if (src) script.src = src;
|
|
283
|
+
else script.textContent = element.textContent;
|
|
284
|
+
script.setAttribute("data-consent-category", element.getAttribute("data-consent-category"));
|
|
285
|
+
script.setAttribute("data-consent-active", "");
|
|
286
|
+
element.replaceWith(script);
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
element.setAttribute("data-consent-active", "");
|
|
290
|
+
if (src) element.setAttribute("src", src);
|
|
291
|
+
element.hidden = false;
|
|
292
|
+
}
|
|
293
|
+
_onDocumentClick(event) {
|
|
294
|
+
const control = event.target.closest?.("[data-consent-open], [data-consent-grant]");
|
|
295
|
+
if (!control || control.matches(":disabled")) return;
|
|
296
|
+
if (control.hasAttribute("data-consent-open")) {
|
|
297
|
+
if (control.getAttribute("data-consent-open") !== this._element.id) return;
|
|
298
|
+
event.preventDefault();
|
|
299
|
+
this.open(control);
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
const id = control.getAttribute("data-consent-grant");
|
|
303
|
+
if (!this._categories.some((category) => category.id === id)) return;
|
|
304
|
+
event.preventDefault();
|
|
305
|
+
const placeholder = control.closest("[data-consent-placeholder]");
|
|
306
|
+
const scope = control.closest("[data-consent-embed]") || placeholder?.parentElement;
|
|
307
|
+
if (placeholder?.querySelector("[data-consent-remember]")?.checked || !scope) {
|
|
308
|
+
this.grant(id);
|
|
309
|
+
} else {
|
|
310
|
+
this._activateWhere(scope, (category) => category === id);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
_onKeydown(event) {
|
|
314
|
+
if (!this._trapping) return;
|
|
315
|
+
if (event.key === "Escape") {
|
|
316
|
+
if (this.close()) event.preventDefault();
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
if (event.key !== "Tab") return;
|
|
320
|
+
const focusable = Array.from(this._element.querySelectorAll(FOCUSABLE)).filter((control) => !control.matches(":disabled") && control.getClientRects().length > 0);
|
|
321
|
+
if (!focusable.length) return;
|
|
322
|
+
const first = focusable[0];
|
|
323
|
+
const last = focusable[focusable.length - 1];
|
|
324
|
+
const active = this._document.activeElement;
|
|
325
|
+
if (event.shiftKey && (active === first || !this._element.contains(active) || active.id === `${this._element.id}-title`)) {
|
|
326
|
+
event.preventDefault();
|
|
327
|
+
last.focus();
|
|
328
|
+
} else if (!event.shiftKey && active === last) {
|
|
329
|
+
event.preventDefault();
|
|
330
|
+
first.focus();
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
function initConsent(root = document, options) {
|
|
335
|
+
const element = root.querySelector?.("[data-consent]");
|
|
336
|
+
return element ? ConsentManager.getOrCreateInstance(element, options) : null;
|
|
337
|
+
}
|
|
338
|
+
export {
|
|
339
|
+
consentRevision,
|
|
340
|
+
ConsentManager as default,
|
|
341
|
+
initConsent
|
|
342
|
+
};
|
package/dist/js/context-menu.js
CHANGED
package/dist/js/datatable.js
CHANGED
package/dist/js/datepicker.js
CHANGED
package/dist/js/image-gallery.js
CHANGED