@hubpav/hwio-web-runtime 0.4.0 → 0.6.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/consent.js +0 -9
- package/dist/forms.js +16 -0
- package/dist/inline.js +1 -1
- package/dist/nav.d.ts +5 -2
- package/dist/nav.js +53 -2
- package/dist/theme.d.ts +8 -2
- package/dist/theme.js +13 -29
- package/package.json +1 -1
package/dist/consent.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { hwioMount } from './mount.js';
|
|
2
2
|
import { HWIO_CONSENT_COOKIE, LEGACY_CONSENT_COOKIE, hwioBaseCategories, hwioConsentToGoogle, hwioCookieString, hwioDisallowedCookieNames, hwioExpireCookieString, hwioHasTrackingConsent, hwioMakeReceipt, hwioParseReceipt, hwioReadCookie, } from './consent-core.js';
|
|
3
3
|
const ATTRIBUTION_KEYS = ['hwio_attribution', 'hw_attribution']; // legacy-ok
|
|
4
|
-
const THEME_KEYS = ['hwio_theme', 'hwio_theme_consent_version', 'theme', 'hw_theme_consent_version']; // legacy-ok
|
|
5
4
|
/**
|
|
6
5
|
* Consent runtime. Markup contract (rendered by HWioCookieConsent):
|
|
7
6
|
* [data-hwio-consent data-version data-gtm? data-locale] banner root (hidden attr toggles it)
|
|
@@ -77,14 +76,6 @@ export function hwioConsentRuntime() {
|
|
|
77
76
|
try {
|
|
78
77
|
if (!categories.marketing)
|
|
79
78
|
ATTRIBUTION_KEYS.forEach((k) => sessionStorage.removeItem(k));
|
|
80
|
-
if (!categories.preferences)
|
|
81
|
-
THEME_KEYS.forEach((k) => localStorage.removeItem(k));
|
|
82
|
-
else {
|
|
83
|
-
if (localStorage.getItem('hwio_theme'))
|
|
84
|
-
localStorage.setItem('hwio_theme_consent_version', version);
|
|
85
|
-
if (localStorage.getItem('theme'))
|
|
86
|
-
localStorage.setItem('hw_theme_consent_version', version); // legacy-ok
|
|
87
|
-
}
|
|
88
79
|
}
|
|
89
80
|
catch {
|
|
90
81
|
// storage unavailable
|
package/dist/forms.js
CHANGED
|
@@ -101,6 +101,22 @@ export function hwioFormsRuntime() {
|
|
|
101
101
|
status.hidden = true;
|
|
102
102
|
}
|
|
103
103
|
await hwioEnsureTurnstileToken(form);
|
|
104
|
+
if (form.querySelector('.cf-turnstile') && !form.querySelector('input[name="cf-turnstile-response"]')?.value) {
|
|
105
|
+
// The widget produced no token (script blocked, unsupported browser, timeout): say so instead of
|
|
106
|
+
// sending a request the endpoint has to reject.
|
|
107
|
+
hwioResetTurnstile(form);
|
|
108
|
+
if (submitBtn) {
|
|
109
|
+
submitBtn.disabled = false;
|
|
110
|
+
submitBtn.textContent = original;
|
|
111
|
+
}
|
|
112
|
+
if (status) {
|
|
113
|
+
status.hidden = false;
|
|
114
|
+
status.textContent = form.dataset.hsTurnstileMsg || error;
|
|
115
|
+
status.classList.add('hs-status-error');
|
|
116
|
+
}
|
|
117
|
+
console.error('form submission blocked: no Turnstile token');
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
104
120
|
let fields = {};
|
|
105
121
|
for (const [name, value] of new FormData(form).entries())
|
|
106
122
|
if (typeof value === 'string')
|
package/dist/inline.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export const consentDefaults = { "code": "(function(){window.dataLayer=window.dataLayer||[];window.gtag=window.gtag||function(){window.dataLayer.push(arguments)};window.gtag('consent','default',{ad_storage:'denied',ad_user_data:'denied',ad_personalization:'denied',analytics_storage:'denied',functionality_storage:'denied',personalization_storage:'denied',security_storage:'granted'})})();", "hash": "sha256-8ypBBYlrPDUYAWyidrG8ZMBi91IYR2VYNNXABFu2aI8=" };
|
|
2
2
|
export const fontGate = { "code": "(function(){var h=document.documentElement,d=h.dataset,fam=d.hwioFontFamily;if(!fam||!('fonts' in document)){h.classList.remove('fonts-loading');h.classList.add('fonts-ready');return}var T=5000;function load(s){return new Promise(function(res,rej){var id=setTimeout(function(){rej(new Error('font timeout'))},T);document.fonts.load('400 1em \"'+fam+'\"',s).then(function(f){clearTimeout(id);res(f)},function(e){clearTimeout(id);rej(e)})})}if(!window.__hwioFontGate){window.__hwioFontGate={state:'loading',ready:Promise.all([load(d.hwioFontSample||'HARDWARIO'),load(d.hwioFontSampleExt||'Řešení Vývojáři Odwiedź')])};document.addEventListener('astro:before-swap',function(ev){var n=ev.newDocument.documentElement;n.classList.add('js');n.classList.remove('fonts-loading');n.classList.toggle('fonts-failed',window.__hwioFontGate.state==='failed');n.classList.add('fonts-ready');var cur=h.getAttribute('data-theme');if(cur)n.setAttribute('data-theme',cur);else n.removeAttribute('data-theme')})}window.__hwioFontGate.ready.then(function(){window.__hwioFontGate.state='ready';h.classList.remove('fonts-loading');h.classList.add('fonts-ready')},function(){window.__hwioFontGate.state='failed';h.classList.remove('fonts-loading');h.classList.add('fonts-failed','fonts-ready')})})();", "hash": "sha256-yZ5zwDTjvBj25m7DRmqzRrznrnc2PqP3T7FWx0ftKO8=" };
|
|
3
|
-
export const themeBootstrap = { "code": "(function(){var h=document.documentElement,d=h.dataset;h.classList.add('js');
|
|
3
|
+
export const themeBootstrap = { "code": "(function(){var h=document.documentElement,d=h.dataset;h.classList.add('js');var t=null;try{if(d.hwioThemeCookieDomain){var m=document.cookie.match(/(?:^|; )hwio_theme=(dark|light)/);if(m)t=m[1]}if(!t)t=localStorage.getItem('hwio_theme')||localStorage.getItem('theme')}catch(e){}if(!h.getAttribute('data-theme')&&d.hwioThemeLight){h.setAttribute('data-theme',t==='dark'&&d.hwioThemeDark?d.hwioThemeDark:d.hwioThemeLight)}})();", "hash": "sha256-N+PCgo10dW74h2jKsCZVedLAmTLJeWIaNkhmjfY8r1Q=" };
|
|
4
4
|
export const hwioInlineHashes = [consentDefaults.hash, fontGate.hash, themeBootstrap.hash];
|
package/dist/nav.d.ts
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Header behaviours: keeps aria-expanded on the DaisyUI drawer label in sync with its checkbox,
|
|
3
|
-
* closes the drawer when a link inside it is followed,
|
|
4
|
-
*
|
|
3
|
+
* closes the drawer when a link inside it is followed, opens HWioHeader's mega panels
|
|
4
|
+
* ([data-hwio-mega-trigger] buttons and their [data-hwio-mega-panel]; hover with intent on pointer
|
|
5
|
+
* devices, click, ArrowDown into the panel, Escape / outside click / focus leaving close; one at a
|
|
6
|
+
* time), and marks the nav link whose section is in view ([data-hwio-scrollspy] nav with same-page
|
|
7
|
+
* anchors) with aria-current="location".
|
|
5
8
|
*/
|
|
6
9
|
export declare function hwioNavRuntime(): import("./mount.js").HWioBinder;
|
package/dist/nav.js
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
import { hwioMount } from './mount.js';
|
|
2
2
|
/**
|
|
3
3
|
* Header behaviours: keeps aria-expanded on the DaisyUI drawer label in sync with its checkbox,
|
|
4
|
-
* closes the drawer when a link inside it is followed,
|
|
5
|
-
*
|
|
4
|
+
* closes the drawer when a link inside it is followed, opens HWioHeader's mega panels
|
|
5
|
+
* ([data-hwio-mega-trigger] buttons and their [data-hwio-mega-panel]; hover with intent on pointer
|
|
6
|
+
* devices, click, ArrowDown into the panel, Escape / outside click / focus leaving close; one at a
|
|
7
|
+
* time), and marks the nav link whose section is in view ([data-hwio-scrollspy] nav with same-page
|
|
8
|
+
* anchors) with aria-current="location".
|
|
6
9
|
*/
|
|
7
10
|
export function hwioNavRuntime() {
|
|
8
11
|
let controller = null;
|
|
@@ -29,6 +32,54 @@ export function hwioNavRuntime() {
|
|
|
29
32
|
sync();
|
|
30
33
|
} }, { signal });
|
|
31
34
|
});
|
|
35
|
+
const triggers = Array.from(document.querySelectorAll('[data-hwio-mega-trigger]'));
|
|
36
|
+
if (triggers.length) {
|
|
37
|
+
const panelOf = (trigger) => document.querySelector(`[data-hwio-mega-panel="${trigger.dataset.hwioMegaTrigger}"]`);
|
|
38
|
+
const pairs = triggers.map((trigger) => ({ trigger, panel: panelOf(trigger) })).filter((p) => !!p.panel);
|
|
39
|
+
const hoverable = window.matchMedia('(hover: hover)').matches;
|
|
40
|
+
let closeTimer = null;
|
|
41
|
+
const cancelClose = () => { if (closeTimer) {
|
|
42
|
+
clearTimeout(closeTimer);
|
|
43
|
+
closeTimer = null;
|
|
44
|
+
} };
|
|
45
|
+
const set = (pair, open) => { pair.trigger.setAttribute('aria-expanded', String(open)); pair.panel.hidden = !open; };
|
|
46
|
+
const closeAll = () => { cancelClose(); pairs.forEach((p) => set(p, false)); };
|
|
47
|
+
const openOne = (pair) => { cancelClose(); pairs.forEach((p) => set(p, p === pair)); };
|
|
48
|
+
const scheduleClose = () => { cancelClose(); closeTimer = setTimeout(closeAll, 180); };
|
|
49
|
+
const isOpen = (pair) => pair.trigger.getAttribute('aria-expanded') === 'true';
|
|
50
|
+
pairs.forEach((pair) => {
|
|
51
|
+
const { trigger, panel } = pair;
|
|
52
|
+
trigger.addEventListener('click', () => (isOpen(pair) ? closeAll() : openOne(pair)), { signal });
|
|
53
|
+
trigger.addEventListener('keydown', (e) => {
|
|
54
|
+
if (e.key !== 'ArrowDown')
|
|
55
|
+
return;
|
|
56
|
+
e.preventDefault();
|
|
57
|
+
openOne(pair);
|
|
58
|
+
panel.querySelector('a[href], button')?.focus();
|
|
59
|
+
}, { signal });
|
|
60
|
+
if (hoverable) {
|
|
61
|
+
trigger.addEventListener('mouseenter', () => openOne(pair), { signal });
|
|
62
|
+
trigger.addEventListener('mouseleave', scheduleClose, { signal });
|
|
63
|
+
panel.addEventListener('mouseenter', cancelClose, { signal });
|
|
64
|
+
panel.addEventListener('mouseleave', scheduleClose, { signal });
|
|
65
|
+
}
|
|
66
|
+
panel.addEventListener('keydown', (e) => { if (e.key === 'Escape') {
|
|
67
|
+
e.preventDefault();
|
|
68
|
+
closeAll();
|
|
69
|
+
trigger.focus();
|
|
70
|
+
} }, { signal });
|
|
71
|
+
panel.addEventListener('focusout', (e) => { const next = e.relatedTarget; if (next && !panel.contains(next) && next !== trigger)
|
|
72
|
+
closeAll(); }, { signal });
|
|
73
|
+
panel.querySelectorAll('a[href]').forEach((a) => a.addEventListener('click', closeAll, { signal }));
|
|
74
|
+
});
|
|
75
|
+
document.addEventListener('keydown', (e) => { if (e.key === 'Escape')
|
|
76
|
+
closeAll(); }, { signal });
|
|
77
|
+
document.addEventListener('click', (e) => {
|
|
78
|
+
const node = e.target;
|
|
79
|
+
if (!pairs.some((p) => p.trigger.contains(node) || p.panel.contains(node)))
|
|
80
|
+
closeAll();
|
|
81
|
+
}, { signal });
|
|
82
|
+
}
|
|
32
83
|
const nav = document.querySelector('[data-hwio-scrollspy]');
|
|
33
84
|
if (!nav || !('IntersectionObserver' in window))
|
|
34
85
|
return;
|
package/dist/theme.d.ts
CHANGED
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
export type HWioScheme = 'light' | 'dark';
|
|
2
|
-
export type HWioThemeChoice = HWioScheme
|
|
3
|
-
/** The scheme actually in effect:
|
|
2
|
+
export type HWioThemeChoice = HWioScheme;
|
|
3
|
+
/** The scheme actually in effect: the stamped data-theme ending in "-dark" means dark. */
|
|
4
4
|
export declare function hwioResolvedScheme(): HWioScheme;
|
|
5
|
+
/**
|
|
6
|
+
* Applies and remembers the visitor's choice. Light is the default (owner decision 2026-09-06); the
|
|
7
|
+
* choice is a user-requested display preference, stored without a consent gate: localStorage on every
|
|
8
|
+
* site and, when <html data-hwio-theme-cookie-domain> is set, a first-party cookie shared by that
|
|
9
|
+
* domain's sites (www, docs, stem on .hardwario.com).
|
|
10
|
+
*/
|
|
5
11
|
export declare function hwioApplyTheme(choice: HWioThemeChoice): void;
|
|
6
12
|
/**
|
|
7
13
|
* Binds [data-hwio-theme-toggle] buttons (two-state light/dark) and keeps the runtime-only
|
package/dist/theme.js
CHANGED
|
@@ -1,41 +1,35 @@
|
|
|
1
1
|
import { hwioMount } from './mount.js';
|
|
2
2
|
const THEME_KEY = 'hwio_theme';
|
|
3
|
-
|
|
4
|
-
/** The scheme actually in effect: explicit data-theme ending in "-dark", else the OS preference. */
|
|
3
|
+
/** The scheme actually in effect: the stamped data-theme ending in "-dark" means dark. */
|
|
5
4
|
export function hwioResolvedScheme() {
|
|
6
5
|
const theme = document.documentElement.getAttribute('data-theme');
|
|
7
6
|
if (theme)
|
|
8
7
|
return theme.endsWith('-dark') ? 'dark' : 'light';
|
|
9
8
|
if (document.documentElement.classList.contains('dark'))
|
|
10
9
|
return 'dark'; // legacy-ok: www's class toggle until P2
|
|
11
|
-
return
|
|
10
|
+
return 'light';
|
|
12
11
|
}
|
|
13
12
|
function themeNames() {
|
|
14
13
|
const d = document.documentElement.dataset;
|
|
15
14
|
return { light: d.hwioThemeLight ?? 'hwio', dark: d.hwioThemeDark ?? 'hwio-dark' };
|
|
16
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Applies and remembers the visitor's choice. Light is the default (owner decision 2026-09-06); the
|
|
18
|
+
* choice is a user-requested display preference, stored without a consent gate: localStorage on every
|
|
19
|
+
* site and, when <html data-hwio-theme-cookie-domain> is set, a first-party cookie shared by that
|
|
20
|
+
* domain's sites (www, docs, stem on .hardwario.com).
|
|
21
|
+
*/
|
|
17
22
|
export function hwioApplyTheme(choice) {
|
|
18
23
|
const html = document.documentElement;
|
|
19
24
|
const names = themeNames();
|
|
20
|
-
|
|
21
|
-
const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches;
|
|
22
|
-
const dark = choice === 'system' ? systemDark : choice === 'dark';
|
|
23
|
-
html.setAttribute('data-theme', dark ? names.dark : names.light);
|
|
25
|
+
html.setAttribute('data-theme', choice === 'dark' ? names.dark : names.light);
|
|
24
26
|
html.classList.add('hwio-theme-transition');
|
|
25
27
|
window.setTimeout(() => html.classList.remove('hwio-theme-transition'), 300);
|
|
26
28
|
try {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
}
|
|
32
|
-
else {
|
|
33
|
-
localStorage.setItem(THEME_KEY, choice);
|
|
34
|
-
const version = html.dataset.hwioConsentVersion;
|
|
35
|
-
if (version)
|
|
36
|
-
localStorage.setItem(THEME_VERSION_KEY, version);
|
|
37
|
-
}
|
|
38
|
-
}
|
|
29
|
+
localStorage.setItem(THEME_KEY, choice);
|
|
30
|
+
const domain = html.dataset.hwioThemeCookieDomain;
|
|
31
|
+
if (domain)
|
|
32
|
+
document.cookie = `${THEME_KEY}=${choice}; Domain=${domain}; Path=/; Max-Age=31536000; SameSite=Lax; Secure`;
|
|
39
33
|
}
|
|
40
34
|
catch {
|
|
41
35
|
// storage unavailable
|
|
@@ -48,16 +42,6 @@ export function hwioApplyTheme(choice) {
|
|
|
48
42
|
*/
|
|
49
43
|
export function hwioThemeRuntime() {
|
|
50
44
|
let controller = null;
|
|
51
|
-
// No stored choice: follow the OS scheme while the page is open.
|
|
52
|
-
try {
|
|
53
|
-
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', () => {
|
|
54
|
-
if (!localStorage.getItem(THEME_KEY))
|
|
55
|
-
hwioApplyTheme('system');
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
catch {
|
|
59
|
-
// matchMedia unavailable
|
|
60
|
-
}
|
|
61
45
|
let themeBeforeSwap = null;
|
|
62
46
|
document.addEventListener('astro:before-swap', () => { themeBeforeSwap = document.documentElement.getAttribute('data-theme'); });
|
|
63
47
|
document.addEventListener('astro:after-swap', () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hubpav/hwio-web-runtime",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.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",
|