/* Shared UI primitives for the demo forms: icons, escaping, the "?" help tooltip and the toast.
Form-specific state stays in each form module. */
export const $ = (selector, scope = document) => scope.querySelector(selector);
export const $$ = (selector, scope = document) => [...scope.querySelectorAll(selector)];
export const escape = value => String(value ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
export const clone = value => JSON.parse(JSON.stringify(value));
export const icons = {
floor:'',
sliders:'',
palette:'',
card:'',
sun:'',
close:'',
check:'',
layers:'',
info:'',
help:'',
image:'',
draw:'',
grid:'',
upload:'',
download:'',
folder:'',
borders:'',
type:'',
zigbee:'',
sofa:'',
door:'',
temperature:'',
humidity:'',
bulb:'',
eyeOff:'',
eye:'',
more:'',
copy:'',
trash:'',
compass:'',
reset:'',
chevron:'',
link:'',
radar:'',
tooltip:'',
wall:'',
broom:'',
wifi:'',
refresh:'',
};
export const icon = name => ``;
export const hydrateIcons = (scope = document) => $$('[data-icon]', scope).forEach(el => { el.innerHTML = icon(el.dataset.icon); });
/* Toast: one shared status line at the bottom of the page. */
let toastTimer;
export function showToast(message) {
const toast = $('#toast');
clearTimeout(toastTimer); toast.textContent = message; toast.hidden = false;
toastTimer = setTimeout(() => { toast.hidden = true; }, 4200);
}
/* Help: "?" trigger with a floating tooltip, mirrors the product's hp-help
(hover, click or focus opens; Escape, outside click or leaving closes). One surface serves every form. */
const helpPopover = document.createElement('div');
helpPopover.id = 'help-popover'; helpPopover.className = 'help-popover'; helpPopover.setAttribute('role', 'tooltip'); helpPopover.hidden = true;
document.body.append(helpPopover);
let helpTrigger = null, helpPinned = false, helpText = () => '';
function positionHelp() {
if (!helpTrigger) return;
const margin = 8, gap = 6, a = helpTrigger.getBoundingClientRect();
helpPopover.style.maxWidth = `${Math.max(0, Math.min(320, innerWidth - margin * 2))}px`;
const r = helpPopover.getBoundingClientRect();
const left = Math.min(Math.max(margin, a.left), innerWidth - margin - r.width);
let top = a.bottom + gap, side = 'bottom';
if (top + r.height > innerHeight - margin && a.top - gap - r.height >= margin) { top = a.top - gap - r.height; side = 'top'; }
helpPopover.style.left = `${Math.round(left)}px`; helpPopover.style.top = `${Math.round(top)}px`; helpPopover.dataset.side = side;
}
function openHelp(button, pinned) {
if (helpTrigger && helpTrigger !== button) helpTrigger.setAttribute('aria-expanded', 'false');
helpTrigger = button; helpPinned = pinned || (helpPinned && helpTrigger === button);
helpPopover.textContent = helpText(button.dataset.help);
helpPopover.hidden = false; button.setAttribute('aria-expanded', 'true');
positionHelp();
}
export function closeHelp() {
if (!helpTrigger) return;
helpTrigger.setAttribute('aria-expanded', 'false'); helpTrigger = null; helpPinned = false; helpPopover.hidden = true;
}
export const helpOpen = () => !!helpTrigger;
document.addEventListener('pointerdown', e => { if (helpTrigger && !e.target.closest('button[data-help]') && !helpPopover.contains(e.target)) closeHelp(); }, true);
addEventListener('resize', () => { if (helpTrigger) positionHelp(); });
/* Bind the triggers inside `scope` to a dictionary { key: [title, text] }; `scroller` keeps the tooltip anchored while the form scrolls. */
export function attachHelp(scope, scroller, copy) {
const text = key => copy[key][1];
scope.addEventListener('click', e => {
const button = e.target.closest('button[data-help]'); if (!button) return;
helpText = text;
if (helpTrigger === button && helpPinned) closeHelp(); else openHelp(button, true);
});
scope.addEventListener('pointerover', e => {
const button = e.target.closest('button[data-help]');
if (!button || e.pointerType === 'touch' || helpPinned) return;
helpText = text; openHelp(button, false);
});
scope.addEventListener('pointerout', e => {
const button = e.target.closest('button[data-help]');
if (button && helpTrigger === button && !helpPinned && !button.contains(e.relatedTarget)) closeHelp();
});
scope.addEventListener('focusin', e => { const b = e.target.closest('button[data-help]'); if (b && !helpPinned) { helpText = text; openHelp(b, false); } });
scope.addEventListener('focusout', e => { const b = e.target.closest('button[data-help]'); if (b && helpTrigger === b && !helpPinned) closeHelp(); });
scroller.addEventListener('scroll', () => { if (helpTrigger) positionHelp(); }, { passive: true });
return { button: key => `` };
}