Diff -- /var/www/vhosts/3dshawn.com/site1/assets/js/ds_help.js
Diff
/var/www/vhosts/3dshawn.com/site1/assets/js/ds_help.js
added on local at 2026-07-12 00:35:23
Added
+132
lines
Removed
-0
lines
Context
0
unchanged
Blobs
from
to 5d9af64ff731
to 5d9af64ff731
Unified diff
Naive LCS-based line diff. Additions in emerald, deletions in rose. Both sides decompressed live from BLOB_STORE.| 1 | /* | |
| 2 | * DriftSense -- help-tooltip runtime. | |
| 3 | * | |
| 4 | * Scans for <span class="ds-help" data-help-title data-help-body | |
| 5 | * [data-help-link] [data-help-link-label]>... | |
| 6 | * elements and wires up a single reusable floating popover that | |
| 7 | * fades in on hover/focus. | |
| 8 | * | |
| 9 | * One popover element in the DOM at any time. Position math flips | |
| 10 | * below-chip when the chip is too close to the top of the viewport. | |
| 11 | */ | |
| 12 | (function () { | |
| 13 | 'use strict'; | |
| 14 | ||
| 15 | var pop = null; | |
| 16 | var currentAnchor = null; | |
| 17 | var hideTimer = null; | |
| 18 | ||
| 19 | function ensurePop() { | |
| 20 | if (pop) return pop; | |
| 21 | pop = document.createElement('div'); | |
| 22 | pop.className = 'ds-help-popover'; | |
| 23 | pop.setAttribute('role', 'tooltip'); | |
| 24 | pop.innerHTML = '' | |
| 25 | + '<span class="dsh-title"></span>' | |
| 26 | + '<span class="dsh-body"></span>' | |
| 27 | + '<a class="dsh-link" href="#" style="display:none"></a>'; | |
| 28 | document.body.appendChild(pop); | |
| 29 | // Keep the popover alive when the pointer moves onto it so the | |
| 30 | // user can click links inside. | |
| 31 | pop.addEventListener('mouseenter', function () { | |
| 32 | if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; } | |
| 33 | }); | |
| 34 | pop.addEventListener('mouseleave', schedHide); | |
| 35 | return pop; | |
| 36 | } | |
| 37 | ||
| 38 | function show(anchor) { | |
| 39 | var el = ensurePop(); | |
| 40 | var title = anchor.getAttribute('data-help-title') || ''; | |
| 41 | var body = anchor.getAttribute('data-help-body') || ''; | |
| 42 | var link = anchor.getAttribute('data-help-link') || ''; | |
| 43 | var lbl = anchor.getAttribute('data-help-link-label') || 'Learn more'; | |
| 44 | ||
| 45 | el.querySelector('.dsh-title').innerHTML = title; | |
| 46 | el.querySelector('.dsh-body').innerHTML = body; | |
| 47 | var a = el.querySelector('.dsh-link'); | |
| 48 | if (link) { | |
| 49 | a.setAttribute('href', link); | |
| 50 | a.innerHTML = lbl + ' <svg viewBox="0 0 24 24" width="12" height="12" fill="none" stroke="currentColor" stroke-width="2"><polyline points="9 18 15 12 9 6"/></svg>'; | |
| 51 | a.style.display = ''; | |
| 52 | } else { | |
| 53 | a.style.display = 'none'; | |
| 54 | } | |
| 55 | ||
| 56 | // Position: center of anchor, then measure + flip if needed | |
| 57 | var rect = anchor.getBoundingClientRect(); | |
| 58 | var vh = window.innerHeight || document.documentElement.clientHeight; | |
| 59 | var vw = window.innerWidth || document.documentElement.clientWidth; | |
| 60 | ||
| 61 | // Temporarily reveal to measure | |
| 62 | el.style.left = '-9999px'; | |
| 63 | el.style.top = '-9999px'; | |
| 64 | el.classList.remove('below'); | |
| 65 | el.classList.add('on'); | |
| 66 | var ph = el.offsetHeight; | |
| 67 | var pw = el.offsetWidth; | |
| 68 | el.classList.remove('on'); | |
| 69 | ||
| 70 | var above = rect.top >= ph + 18; | |
| 71 | var cx = rect.left + rect.width / 2; | |
| 72 | // Clamp horizontally so popover stays on-screen | |
| 73 | var pad = 12; | |
| 74 | if (cx - pw / 2 < pad) cx = pw / 2 + pad; | |
| 75 | if (cx + pw / 2 > vw - pad) cx = vw - pw / 2 - pad; | |
| 76 | ||
| 77 | if (above) { | |
| 78 | el.classList.remove('below'); | |
| 79 | el.style.left = cx + 'px'; | |
| 80 | el.style.top = (rect.top - 10) + 'px'; | |
| 81 | el.style.transformOrigin = 'center bottom'; | |
| 82 | el.style.transform = 'translate(-50%, calc(-100% - 0px))'; | |
| 83 | } else { | |
| 84 | el.classList.add('below'); | |
| 85 | el.style.left = cx + 'px'; | |
| 86 | el.style.top = (rect.bottom + 10) + 'px'; | |
| 87 | el.style.transformOrigin = 'center top'; | |
| 88 | el.style.transform = 'translate(-50%, 0)'; | |
| 89 | } | |
| 90 | requestAnimationFrame(function () { | |
| 91 | el.classList.add('on'); | |
| 92 | }); | |
| 93 | } | |
| 94 | ||
| 95 | function hide() { | |
| 96 | if (pop) pop.classList.remove('on'); | |
| 97 | currentAnchor = null; | |
| 98 | } | |
| 99 | function schedHide() { | |
| 100 | if (hideTimer) clearTimeout(hideTimer); | |
| 101 | hideTimer = setTimeout(hide, 120); | |
| 102 | } | |
| 103 | ||
| 104 | function attach(el) { | |
| 105 | if (el.__dsHelpAttached) return; el.__dsHelpAttached = true; | |
| 106 | el.setAttribute('tabindex', el.getAttribute('tabindex') || '0'); | |
| 107 | el.setAttribute('aria-label', 'Help'); | |
| 108 | el.addEventListener('mouseenter', function () { | |
| 109 | if (hideTimer) { clearTimeout(hideTimer); hideTimer = null; } | |
| 110 | currentAnchor = el; | |
| 111 | show(el); | |
| 112 | }); | |
| 113 | el.addEventListener('mouseleave', schedHide); | |
| 114 | el.addEventListener('focus', function () { currentAnchor = el; show(el); }); | |
| 115 | el.addEventListener('blur', hide); | |
| 116 | el.addEventListener('keydown', function (e) { | |
| 117 | if (e.key === 'Escape') hide(); | |
| 118 | }); | |
| 119 | } | |
| 120 | ||
| 121 | function init(root) { | |
| 122 | (root || document).querySelectorAll('.ds-help').forEach(attach); | |
| 123 | } | |
| 124 | if (document.readyState === 'loading') { | |
| 125 | document.addEventListener('DOMContentLoaded', function () { init(); }); | |
| 126 | } else { | |
| 127 | init(); | |
| 128 | } | |
| 129 | window.dsHelpInit = init; | |
| 130 | window.addEventListener('resize', hide); | |
| 131 | window.addEventListener('scroll', hide, true); | |
| 132 | })(); |