@hubpav/hwio-web-runtime 0.9.0 → 0.10.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/dist/dropdown.d.ts +1 -1
- package/dist/dropdown.js +1 -1
- package/dist/nav.d.ts +6 -5
- package/dist/nav.js +86 -38
- package/dist/popup.d.ts +1 -1
- package/dist/popup.js +1 -1
- package/dist/turnstile.d.ts +4 -0
- package/dist/turnstile.js +39 -7
- package/package.json +1 -1
package/dist/dropdown.d.ts
CHANGED
|
@@ -7,6 +7,6 @@
|
|
|
7
7
|
* - Escape, with focus back on its summary when focus was inside;
|
|
8
8
|
* - focus leaving it (Tab past the last item);
|
|
9
9
|
* - another dropdown opening, or another runtime announcing a popup (HWioHeader's mega panels and
|
|
10
|
-
*
|
|
10
|
+
* small-screen menu, through `hwio:popupopen`). Opening a dropdown announces it, so an open mega panel closes.
|
|
11
11
|
*/
|
|
12
12
|
export declare function hwioDropdownRuntime(): import("./mount.js").HWioBinder;
|
package/dist/dropdown.js
CHANGED
|
@@ -9,7 +9,7 @@ import { hwioAnnouncePopup, hwioOnPopup } from './popup.js';
|
|
|
9
9
|
* - Escape, with focus back on its summary when focus was inside;
|
|
10
10
|
* - focus leaving it (Tab past the last item);
|
|
11
11
|
* - another dropdown opening, or another runtime announcing a popup (HWioHeader's mega panels and
|
|
12
|
-
*
|
|
12
|
+
* small-screen menu, through `hwio:popupopen`). Opening a dropdown announces it, so an open mega panel closes.
|
|
13
13
|
*/
|
|
14
14
|
export function hwioDropdownRuntime() {
|
|
15
15
|
let controller = null;
|
package/dist/nav.d.ts
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Header behaviours:
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
2
|
+
* Header behaviours: opens HWioHeader's small-screen menu from its [data-hwio-menu-toggle] button into its
|
|
3
|
+
* [data-hwio-menu-panel] sheet (aria-expanded and the button's label follow; the page is locked without
|
|
4
|
+
* moving, its scrollbar's width kept as padding, and made inert behind the sheet; focus moves to the first
|
|
5
|
+
* item without scrolling and back to the toggle on close; the toggle, Escape, a followed link, another
|
|
6
|
+
* popup and a resize to the desktop layout close it), opens HWioHeader's mega panels
|
|
6
7
|
* ([data-hwio-mega-trigger] buttons and their [data-hwio-mega-panel]; hover with intent on pointer
|
|
7
8
|
* devices, click, ArrowDown into the panel, Escape / outside click / focus leaving close; one at a
|
|
8
9
|
* time; the click that follows a hover-open keeps the panel open, the next one closes it), and marks the nav link whose section is in view ([data-hwio-scrollspy] nav with same-page
|
|
9
|
-
* anchors) with aria-current="location". Opening a mega panel or the
|
|
10
|
+
* anchors) with aria-current="location". Opening a mega panel or the small-screen menu announces it
|
|
10
11
|
* (`hwio:popupopen`, popup.ts), so an open dropdown (HWioLanguageSwitcher) closes; a dropdown that
|
|
11
12
|
* opens closes the mega panels the same way.
|
|
12
13
|
*/
|
package/dist/nav.js
CHANGED
|
@@ -1,21 +1,26 @@
|
|
|
1
1
|
import { hwioMount } from './mount.js';
|
|
2
2
|
import { hwioAnnouncePopup, hwioOnPopup } from './popup.js';
|
|
3
3
|
/**
|
|
4
|
-
* Header behaviours:
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Header behaviours: opens HWioHeader's small-screen menu from its [data-hwio-menu-toggle] button into its
|
|
5
|
+
* [data-hwio-menu-panel] sheet (aria-expanded and the button's label follow; the page is locked without
|
|
6
|
+
* moving, its scrollbar's width kept as padding, and made inert behind the sheet; focus moves to the first
|
|
7
|
+
* item without scrolling and back to the toggle on close; the toggle, Escape, a followed link, another
|
|
8
|
+
* popup and a resize to the desktop layout close it), opens HWioHeader's mega panels
|
|
8
9
|
* ([data-hwio-mega-trigger] buttons and their [data-hwio-mega-panel]; hover with intent on pointer
|
|
9
10
|
* devices, click, ArrowDown into the panel, Escape / outside click / focus leaving close; one at a
|
|
10
11
|
* time; the click that follows a hover-open keeps the panel open, the next one closes it), and marks the nav link whose section is in view ([data-hwio-scrollspy] nav with same-page
|
|
11
|
-
* anchors) with aria-current="location". Opening a mega panel or the
|
|
12
|
+
* anchors) with aria-current="location". Opening a mega panel or the small-screen menu announces it
|
|
12
13
|
* (`hwio:popupopen`, popup.ts), so an open dropdown (HWioLanguageSwitcher) closes; a dropdown that
|
|
13
14
|
* opens closes the mega panels the same way.
|
|
14
15
|
*/
|
|
15
16
|
export function hwioNavRuntime() {
|
|
16
17
|
let controller = null;
|
|
17
18
|
let observer = null;
|
|
19
|
+
let closeMenus = [];
|
|
18
20
|
function cleanup() {
|
|
21
|
+
// A page swap with the menu open must not leave the next page locked or inert.
|
|
22
|
+
closeMenus.forEach((close) => close());
|
|
23
|
+
closeMenus = [];
|
|
19
24
|
controller?.abort();
|
|
20
25
|
controller = null;
|
|
21
26
|
observer?.disconnect();
|
|
@@ -25,46 +30,89 @@ export function hwioNavRuntime() {
|
|
|
25
30
|
cleanup();
|
|
26
31
|
controller = new AbortController();
|
|
27
32
|
const { signal } = controller;
|
|
28
|
-
document.querySelectorAll('
|
|
29
|
-
const
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
33
|
+
document.querySelectorAll('[data-hwio-menu-toggle]').forEach((toggle) => {
|
|
34
|
+
const panel = document.querySelector(`[data-hwio-menu-panel="${toggle.dataset.hwioMenuToggle}"]`);
|
|
35
|
+
if (!panel)
|
|
36
|
+
return;
|
|
37
|
+
const header = toggle.closest('header');
|
|
38
|
+
const root = document.documentElement;
|
|
39
|
+
const reduced = window.matchMedia('(prefers-reduced-motion: reduce)');
|
|
40
|
+
const desktop = toggle.dataset.hwioMenuUntil ? window.matchMedia(`(min-width: ${toggle.dataset.hwioMenuUntil})`) : null;
|
|
41
|
+
let inerted = [];
|
|
42
|
+
let hideTimer = null;
|
|
43
|
+
const isOpen = () => toggle.getAttribute('aria-expanded') === 'true';
|
|
44
|
+
const label = (open) => {
|
|
45
|
+
const text = open ? toggle.dataset.hwioLabelClose : toggle.dataset.hwioLabelOpen;
|
|
46
|
+
if (text)
|
|
47
|
+
toggle.setAttribute('aria-label', text);
|
|
40
48
|
};
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
+
// Lock the page where it is: overflow hidden on the root, its scrollbar's width kept as padding (so
|
|
50
|
+
// the layout keeps its width), and everything outside the header inert, walking up from the header.
|
|
51
|
+
const lock = (on) => {
|
|
52
|
+
if (on) {
|
|
53
|
+
root.style.setProperty('--hwio-scrollbar', `${Math.max(0, window.innerWidth - root.clientWidth)}px`);
|
|
54
|
+
root.classList.add('hwio-scroll-lock');
|
|
55
|
+
for (let node = header; node && node.parentElement && node !== document.body; node = node.parentElement) {
|
|
56
|
+
for (const sibling of Array.from(node.parentElement.children)) {
|
|
57
|
+
if (sibling !== node && sibling instanceof HTMLElement && !sibling.inert && sibling.tagName !== 'SCRIPT') {
|
|
58
|
+
sibling.inert = true;
|
|
59
|
+
inerted.push(sibling);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
49
63
|
}
|
|
50
|
-
else
|
|
51
|
-
|
|
64
|
+
else {
|
|
65
|
+
root.classList.remove('hwio-scroll-lock');
|
|
66
|
+
root.style.removeProperty('--hwio-scrollbar');
|
|
67
|
+
inerted.forEach((el) => { el.inert = false; });
|
|
68
|
+
inerted = [];
|
|
52
69
|
}
|
|
53
70
|
};
|
|
54
71
|
const setOpen = (open, restoreFocus = true) => {
|
|
55
|
-
if (
|
|
72
|
+
if (isOpen() === open)
|
|
56
73
|
return;
|
|
57
|
-
|
|
58
|
-
|
|
74
|
+
toggle.setAttribute('aria-expanded', String(open));
|
|
75
|
+
label(open);
|
|
76
|
+
if (hideTimer) {
|
|
77
|
+
clearTimeout(hideTimer);
|
|
78
|
+
hideTimer = null;
|
|
79
|
+
}
|
|
80
|
+
if (open) {
|
|
81
|
+
panel.hidden = false;
|
|
82
|
+
lock(true);
|
|
83
|
+
// One frame with the closed state rendered, so the sheet fades in instead of appearing.
|
|
84
|
+
requestAnimationFrame(() => { if (isOpen())
|
|
85
|
+
panel.dataset.state = 'open'; });
|
|
86
|
+
hwioAnnouncePopup(panel);
|
|
87
|
+
panel.querySelector('a[href], button, summary, input:not([type="hidden"])')?.focus({ preventScroll: true });
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
panel.dataset.state = 'closed';
|
|
91
|
+
lock(false);
|
|
92
|
+
const hide = () => { if (!isOpen())
|
|
93
|
+
panel.hidden = true; };
|
|
94
|
+
if (reduced.matches)
|
|
95
|
+
hide();
|
|
96
|
+
else
|
|
97
|
+
hideTimer = setTimeout(hide, 200);
|
|
98
|
+
if (restoreFocus)
|
|
99
|
+
toggle.focus({ preventScroll: true });
|
|
100
|
+
}
|
|
59
101
|
};
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
102
|
+
closeMenus.push(() => { if (isOpen()) {
|
|
103
|
+
setOpen(false, false);
|
|
104
|
+
panel.hidden = true;
|
|
105
|
+
} });
|
|
106
|
+
toggle.addEventListener('click', () => setOpen(!isOpen()), { signal });
|
|
107
|
+
// A followed link closes the menu without stealing focus from the navigation target (same-page anchors).
|
|
108
|
+
panel.querySelectorAll('a[href]').forEach((a) => a.addEventListener('click', () => setOpen(false, false), { signal }));
|
|
109
|
+
document.addEventListener('keydown', (e) => { if (e.key === 'Escape' && isOpen())
|
|
67
110
|
setOpen(false); }, { signal });
|
|
111
|
+
desktop?.addEventListener('change', (e) => { if (e.matches)
|
|
112
|
+
setOpen(false, false); }, { signal });
|
|
113
|
+
// Another popup (the language dropdown in the bar) opened: close the menu unless it sits in the sheet.
|
|
114
|
+
hwioOnPopup((owner) => { if (owner !== panel && !panel.contains(owner))
|
|
115
|
+
setOpen(false, false); }, signal);
|
|
68
116
|
});
|
|
69
117
|
const triggers = Array.from(document.querySelectorAll('[data-hwio-mega-trigger]'));
|
|
70
118
|
if (triggers.length) {
|
|
@@ -135,7 +183,7 @@ export function hwioNavRuntime() {
|
|
|
135
183
|
if (!pairs.some((p) => p.trigger.contains(node) || p.panel.contains(node)))
|
|
136
184
|
closeAll();
|
|
137
185
|
}, { signal });
|
|
138
|
-
// Another popup (a language dropdown, the
|
|
186
|
+
// Another popup (a language dropdown, the small-screen menu) opened: close the panels unless it sits in one.
|
|
139
187
|
hwioOnPopup((owner) => {
|
|
140
188
|
if (!pairs.some((p) => p.panel === owner || p.panel.contains(owner)))
|
|
141
189
|
closeAll();
|
package/dist/popup.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* One popup at a time across runtimes. A runtime that opens something which floats over the page
|
|
3
|
-
* (HWioHeader's mega panel or
|
|
3
|
+
* (HWioHeader's mega panel or small-screen menu, a `details[data-hwio-dropdown]`) announces the element it
|
|
4
4
|
* opened with `hwio:popupopen`; every other runtime closes what it has open unless that element is
|
|
5
5
|
* the announced one or contains it. The runtimes stay independent: neither imports the other.
|
|
6
6
|
*/
|
package/dist/popup.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* One popup at a time across runtimes. A runtime that opens something which floats over the page
|
|
3
|
-
* (HWioHeader's mega panel or
|
|
3
|
+
* (HWioHeader's mega panel or small-screen menu, a `details[data-hwio-dropdown]`) announces the element it
|
|
4
4
|
* opened with `hwio:popupopen`; every other runtime closes what it has open unless that element is
|
|
5
5
|
* the announced one or contains it. The runtimes stay independent: neither imports the other.
|
|
6
6
|
*/
|
package/dist/turnstile.d.ts
CHANGED
|
@@ -24,6 +24,10 @@ declare global {
|
|
|
24
24
|
* Lazy explicit Turnstile: the third-party script loads on first form interaction (focus or
|
|
25
25
|
* pointer-enter) and the widget id is stored on its form so the submit handler resets exactly
|
|
26
26
|
* the token it consumed. Markup: <div class="cf-turnstile" data-sitekey="..."> inside a <form>.
|
|
27
|
+
*
|
|
28
|
+
* The widget takes the page's scheme when it renders, and Turnstile cannot change the theme of a
|
|
29
|
+
* rendered widget, so a switch of the site theme (hwio:themechange) redraws every widget in the
|
|
30
|
+
* new scheme; the redrawn widget issues a fresh token.
|
|
27
31
|
*/
|
|
28
32
|
export declare function hwioTurnstileRuntime(): import("./mount.js").HWioBinder;
|
|
29
33
|
/** Waits (bounded) for the lazily rendered widget to produce a token before the form is snapshotted. */
|
package/dist/turnstile.js
CHANGED
|
@@ -35,22 +35,51 @@ function loadApi() {
|
|
|
35
35
|
});
|
|
36
36
|
return window.__hwioTurnstileLoader;
|
|
37
37
|
}
|
|
38
|
+
function draw(api, container) {
|
|
39
|
+
return api.render(container, { sitekey: container.dataset.sitekey ?? '', theme: hwioResolvedScheme(), size: widgetSize(container) });
|
|
40
|
+
}
|
|
41
|
+
function storeId(form, id) {
|
|
42
|
+
form.hwioTurnstileWidgetId = id;
|
|
43
|
+
form.hwTurnstileWidgetId = id; // legacy-ok
|
|
44
|
+
}
|
|
38
45
|
/**
|
|
39
46
|
* Lazy explicit Turnstile: the third-party script loads on first form interaction (focus or
|
|
40
47
|
* pointer-enter) and the widget id is stored on its form so the submit handler resets exactly
|
|
41
48
|
* the token it consumed. Markup: <div class="cf-turnstile" data-sitekey="..."> inside a <form>.
|
|
49
|
+
*
|
|
50
|
+
* The widget takes the page's scheme when it renders, and Turnstile cannot change the theme of a
|
|
51
|
+
* rendered widget, so a switch of the site theme (hwio:themechange) redraws every widget in the
|
|
52
|
+
* new scheme; the redrawn widget issues a fresh token.
|
|
42
53
|
*/
|
|
43
54
|
export function hwioTurnstileRuntime() {
|
|
44
55
|
let controller = null;
|
|
45
|
-
let
|
|
56
|
+
let widgets = [];
|
|
57
|
+
let themeBound = false;
|
|
46
58
|
function cleanup() {
|
|
47
59
|
controller?.abort();
|
|
48
60
|
controller = null;
|
|
49
|
-
|
|
61
|
+
themeBound = false;
|
|
62
|
+
widgets.forEach(({ id }) => { try {
|
|
50
63
|
window.turnstile?.remove(id);
|
|
51
64
|
}
|
|
52
65
|
catch { /* gone */ } });
|
|
53
|
-
|
|
66
|
+
widgets = [];
|
|
67
|
+
}
|
|
68
|
+
function redraw() {
|
|
69
|
+
const api = window.turnstile;
|
|
70
|
+
if (!api)
|
|
71
|
+
return;
|
|
72
|
+
widgets = widgets.filter((w) => w.container.isConnected).map((w) => {
|
|
73
|
+
try {
|
|
74
|
+
api.remove(w.id);
|
|
75
|
+
}
|
|
76
|
+
catch { /* gone */ }
|
|
77
|
+
const id = draw(api, w.container);
|
|
78
|
+
if (id === undefined)
|
|
79
|
+
return w;
|
|
80
|
+
storeId(w.form, id);
|
|
81
|
+
return { ...w, id };
|
|
82
|
+
});
|
|
54
83
|
}
|
|
55
84
|
function bind() {
|
|
56
85
|
const containers = Array.from(document.querySelectorAll('.cf-turnstile:not([data-hwio-turnstile-bound])'));
|
|
@@ -58,6 +87,10 @@ export function hwioTurnstileRuntime() {
|
|
|
58
87
|
return;
|
|
59
88
|
controller ??= new AbortController();
|
|
60
89
|
const { signal } = controller;
|
|
90
|
+
if (!themeBound) {
|
|
91
|
+
themeBound = true;
|
|
92
|
+
window.addEventListener('hwio:themechange', redraw, { signal });
|
|
93
|
+
}
|
|
61
94
|
containers.forEach((container) => {
|
|
62
95
|
const form = container.closest('form');
|
|
63
96
|
if (!form)
|
|
@@ -68,11 +101,10 @@ export function hwioTurnstileRuntime() {
|
|
|
68
101
|
if (signal.aborted || !container.isConnected || state === 'rendered')
|
|
69
102
|
return;
|
|
70
103
|
state = 'rendered';
|
|
71
|
-
const id = api
|
|
104
|
+
const id = draw(api, container);
|
|
72
105
|
if (id !== undefined) {
|
|
73
|
-
|
|
74
|
-
form
|
|
75
|
-
form.hwTurnstileWidgetId = id; // legacy-ok
|
|
106
|
+
widgets.push({ container, form, id });
|
|
107
|
+
storeId(form, id);
|
|
76
108
|
}
|
|
77
109
|
};
|
|
78
110
|
const load = () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubpav/hwio-web-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.0",
|
|
4
4
|
"description": "Framework-free browser runtime for the HARDWARIO websites: consent (Consent Mode v2 + GTM), HubSpot forms with Turnstile, attribution, theme, CTA tracking and small behaviours. View-transition safe.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|