Changeset 3334981
- Timestamp:
- 07/27/2025 07:33:25 PM (8 months ago)
- Location:
- emberly-popups/trunk
- Files:
-
- 5 edited
-
emberly-popups.php (modified) (1 diff)
-
lib/functions.php (modified) (1 diff)
-
lib/pop.js (modified) (1 diff)
-
lib/styles.css (modified) (2 diffs)
-
readme.txt (modified) (1 diff)
Legend:
- Unmodified
- Added
- Removed
-
emberly-popups/trunk/emberly-popups.php
r3290697 r3334981 4 4 Author URI: https://emberlydigital.com/ 5 5 description: Lightweight, accessible popups called via functions. By developers, for developers! 6 Version: 1. 26 Version: 1.3 7 7 Author: Emberly Digital 8 8 License: GPL2 -
emberly-popups/trunk/lib/functions.php
r3290697 r3334981 74 74 75 75 // Close the popup HTML structure 76 $popup_end .= '</div></div></div></div>';76 $popup_end = '</div></div></div></div>'; 77 77 78 78 // Return or echo -
emberly-popups/trunk/lib/pop.js
r3290697 r3334981 1 1 document.addEventListener('DOMContentLoaded', () => { 2 const focusableElements = 'input, button, [href], select, textarea, [tabindex]:not([tabindex="-1"])'; 3 let lastFocusedElement; // To store the last focused element before popup opens 4 5 const allAutoOpenPopups = document.querySelectorAll('.em-auto-open'); 6 7 function isDebugMode(popupOverlay) { 8 return popupOverlay?.getAttribute('data-em-popup-debug') === 'true'; 9 } 10 11 function forceLabelClicksInsidePopup(popupOverlay) { 12 if (!popupOverlay) return; 13 14 popupOverlay.addEventListener('click', function(e) { 15 const label = e.target.closest('label[for]'); 16 if (label) { 17 const inputId = label.getAttribute('for'); 18 const input = document.getElementById(inputId); 19 if (input) { 20 input.focus(); // Focus first 21 input.click(); // Then trigger click manually 22 23 // If debug mode is enabled, log the event. 24 if (isDebugMode(popupOverlay)) { 25 console.log(`[Emberly Popups] Label clicked: ${label.textContent}`); 26 console.log(`[Emberly Popups] Input focused: ${inputId}`); 27 } 28 } 29 } 30 }); 31 } 32 33 // Function to set a cookie with a default expiration of 24 hours 34 function setCookie(name, value, hours = 24) { 35 const date = new Date(); 36 date.setTime(date.getTime() + (hours * 60 * 60 * 1000)); // Convert hours to milliseconds 37 const expires = "expires=" + date.toUTCString(); 38 document.cookie = `${name}=${value}; ${expires}; path=/`; 39 40 // Log information about the current UTC, and when the cookie will expire. 41 console.log(`[Emberly Popups] UTC: ${date.toUTCString()}`); 42 console.log(`[Emberly Popups] Emberly Popup Cookie expires in ${hours} hours`); 43 44 // If debug mode is enabled, log the event. 45 const popupOverlay = document.querySelector('.em-popup-overlay.em-active'); 46 if (isDebugMode(popupOverlay)) { 47 console.log(`[Emberly Popups] Cookie set: ${name}=${value}`); 48 } 49 } 50 51 // Function to get a cookie 52 function getCookie(name) { 53 const cookies = document.cookie.split(';'); 54 for (let i = 0; i < cookies.length; i++) { 55 const cookie = cookies[i].trim(); 56 if (cookie.startsWith(name + '=')) { 57 return cookie.substring(name.length + 1); 58 } 59 } 60 return null; 61 } 62 63 // Function to keep focus inside the popup 64 function trapFocus(event) { 65 const dialog = document.querySelector('.em-popup-overlay.em-active > .em-popup'); 66 const isTabPressed = event.key === 'Tab'; 67 68 if (!isTabPressed) return; 69 70 const focusableContent = dialog.querySelectorAll(focusableElements); 71 const firstFocusableElement = focusableContent[0]; 72 const lastFocusableElement = focusableContent[focusableContent.length - 1]; 73 74 if (event.shiftKey) { 75 // If Shift + Tab 76 if (document.activeElement === firstFocusableElement) { 77 event.preventDefault(); 78 lastFocusableElement.focus(); 79 } 80 } else { 81 // If Tab 82 if (document.activeElement === lastFocusableElement) { 83 event.preventDefault(); 84 firstFocusableElement.focus(); 85 } 86 } 87 } 88 89 function unfocus(event) { 90 const dialog = document.querySelector('.em-popup-overlay.em-active > .em-popup'); 91 // Get the list of focusable elements 92 const focusableContent = Array.from(dialog.querySelectorAll(focusableElements)); 93 94 if(!focusableContent.includes(event.target)) { 95 event.preventDefault(); 96 // Focus the first focusable element inside the popup 97 const firstFocusableElement = dialog.querySelector(focusableElements); 98 if(firstFocusableElement) { 99 firstFocusableElement.focus(); 100 } 101 } 102 } 103 104 function lockScroll(popupOverlay) { 105 const scrollY = window.scrollY || document.documentElement.scrollTop; 106 document.body.style.position = 'fixed'; 107 document.body.style.top = `-${scrollY}px`; 108 document.body.style.left = '0'; 109 document.body.style.right = '0'; 110 document.body.style.width = '100%'; 111 document.body.dataset.scrollY = scrollY; 112 113 if (isDebugMode(popupOverlay)) { 114 console.log(`[Emberly Popups] Scroll locked at: ${scrollY}px`); 115 } 116 } 117 2 const focusableElements = 'input, button, [href], select, textarea, [tabindex]:not([tabindex="-1"])'; 3 let lastFocusedElement; // To store the last focused element before popup opens 4 5 const allAutoOpenPopups = document.querySelectorAll('.em-auto-open'); 6 7 function isDebugMode(popupOverlay) { 8 return popupOverlay?.getAttribute('data-em-popup-debug') === 'true'; 9 } 10 11 function forceLabelClicksInsidePopup(popupOverlay) { 12 if (!popupOverlay) return; 13 14 popupOverlay.addEventListener('click', function(e) { 15 const label = e.target.closest('label[for]'); 16 if (label) { 17 const inputId = label.getAttribute('for'); 18 const input = document.getElementById(inputId); 19 if (input) { 20 input.focus(); 21 input.click(); 22 23 if (isDebugMode(popupOverlay)) { 24 console.log(`[Emberly Popups] Label clicked: ${label.textContent}`); 25 console.log(`[Emberly Popups] Input focused: ${inputId}`); 26 } 27 } 28 } 29 }); 30 } 31 32 function setCookie(name, value, hours = 24) { 33 const date = new Date(); 34 date.setTime(date.getTime() + (hours * 60 * 60 * 1000)); 35 document.cookie = `${name}=${value}; expires=${date.toUTCString()}; path=/`; 36 37 console.log(`[Emberly Popups] UTC: ${date.toUTCString()}`); 38 console.log(`[Emberly Popups] Emberly Popup Cookie expires in ${hours} hours`); 39 40 const popupOverlay = document.querySelector('.em-popup-overlay.em-active'); 41 if (isDebugMode(popupOverlay)) { 42 console.log(`[Emberly Popups] Cookie set: ${name}=${value}`); 43 } 44 } 45 46 function getCookie(name) { 47 return document.cookie 48 .split(';') 49 .map(c => c.trim()) 50 .find(c => c.startsWith(name + '=')) 51 ?.substring(name.length + 1) || null; 52 } 53 54 function trapFocus(event) { 55 const dialog = document.querySelector('.em-popup-overlay.em-active > .em-popup'); 56 if (event.key !== 'Tab' || !dialog) return; 57 58 const focusable = dialog.querySelectorAll(focusableElements); 59 const first = focusable[0], last = focusable[focusable.length - 1]; 60 if (event.shiftKey && document.activeElement === first) { 61 event.preventDefault(), last.focus(); 62 } else if (!event.shiftKey && document.activeElement === last) { 63 event.preventDefault(), first.focus(); 64 } 65 } 66 67 function unfocus(event) { 68 const dialog = document.querySelector('.em-popup-overlay.em-active > .em-popup'); 69 if (!dialog) return; 70 const focusable = Array.from(dialog.querySelectorAll(focusableElements)); 71 if (!focusable.includes(event.target)) { 72 event.preventDefault(); 73 dialog.querySelector(focusableElements)?.focus(); 74 } 75 } 76 77 function lockScroll(popupOverlay) { 78 const scrollY = window.scrollY; 79 Object.assign(document.body.style, { 80 position: 'fixed', 81 top: `-${scrollY}px`, 82 left: '0', 83 right: '0', 84 width: '100%' 85 }); 86 document.body.dataset.scrollY = scrollY; 87 88 if (isDebugMode(popupOverlay)) { 89 console.log(`[Emberly Popups] Scroll locked at: ${scrollY}px`); 90 } 91 } 92 118 93 function unlockScroll(popupOverlay) { 119 94 const scrollY = parseInt(document.body.dataset.scrollY || '0', 10); 120 document.body.style.position = ''; 121 document.body.style.top = ''; 122 document.body.style.left = ''; 123 document.body.style.right = ''; 124 document.body.style.width = ''; 95 96 // Temporarily disable smooth scroll 97 const html = document.documentElement; 98 const prevScrollBehavior = html.style.scrollBehavior; 99 html.style.scrollBehavior = 'auto'; 100 101 // Restore scroll position instantly 102 Object.assign(document.body.style, { 103 position: '', 104 top: '', 105 left: '', 106 right: '', 107 width: '' 108 }); 125 109 delete document.body.dataset.scrollY; 110 126 111 window.scrollTo(0, scrollY); 127 112 113 // Re-enable smooth scroll after short delay 114 setTimeout(() => { 115 html.style.scrollBehavior = prevScrollBehavior || ''; 116 }, 50); 117 128 118 if (isDebugMode(popupOverlay)) { 129 119 console.log(`[Emberly Popups] Scroll unlocked, returned to: ${scrollY}px`); 130 120 } 131 }132 133 // Function to handle the popup open134 function openPopup(trigger) {135 lastFocusedElement = document.activeElement; // Save the last focused element136 const popupId = trigger.getAttribute('em-popup-trigger-id');137 138 const popupOverlay = document.querySelector('.em-popup-overlay[data-em-popup-id="' + popupId + '"]');139 handleOpenPopup(popupOverlay);140 141 // If debug mode is enabled, log the event.142 if (isDebugMode(popupOverlay)) {143 console.log(`[Emberly Popups] Popup opened: ${popupId}`);144 }145 121 } 146 122 147 function openPopupByID(popupId) { 148 const popupOverlay = document.querySelector('.em-popup-overlay[data-em-popup-id="' + popupId + '"]'); 149 handleOpenPopup(popupOverlay); 150 151 // If debug mode is enabled, log the event. 152 if (isDebugMode(popupOverlay)) { 153 console.log(`[Emberly Popups] Popup opened: ${popupId}`); 154 } 155 } 156 157 // Function to handle the popup open 158 function handleOpenPopup(popupOverlay) { 159 if (!popupOverlay) return; 160 161 // Check for a delay attribute 162 let delayTime = popupOverlay.getAttribute('data-em-delay'); 163 delayTime = delayTime ? parseInt(delayTime) : 0; 164 165 const delay = () => { 166 popupOverlay.style.display = 'flex'; 167 setTimeout(() => { 168 popupOverlay.classList.add('em-active'); 169 }, 10); // give browser a tick to apply display change 170 171 // Manual label-to-input binding fix 172 forceLabelClicksInsidePopup(popupOverlay); 173 174 // Turn on scroll lock. 175 lockScroll(popupOverlay); 176 177 // Make divs surrounding cross-origin iframes focusable 178 const iframeContainers = popupOverlay.querySelectorAll('div:has(> iframe)'); 179 iframeContainers.forEach((iframe) => { 180 iframe.setAttribute('tabindex', "0"); 181 }); 182 183 // Focus the first focusable element inside the popup 184 const firstFocusableElement = popupOverlay.querySelector(focusableElements); 185 if (firstFocusableElement) { 186 firstFocusableElement.focus(); 187 // Prevent being able to focus on the whole overlay 188 popupOverlay.addEventListener('click', unfocus); 189 } 190 191 // Add event listener to handle trap focus 192 document.addEventListener('keydown', trapFocus); 193 }; 194 195 // If there is a delay, wait before opening 196 if (delayTime > 0) { 197 setTimeout(delay, delayTime); 198 } else { 199 delay(); 200 } 201 202 // If debug mode is enabled, log the event. 203 if (isDebugMode(popupOverlay)) { 204 console.log(`[Emberly Popups] Popup opened with delay: ${delayTime}ms`); 205 } 206 } 207 208 // Function to handle the popup close and stop the video 209 function closePopup(popupId) { 210 console.log(popupId); 211 const popupOverlay = document.querySelector(`.em-popup-overlay[data-em-popup-id="${popupId}"]`); 212 if (popupOverlay) { 213 popupOverlay.classList.remove('em-active'); 214 setTimeout(() => { 215 popupOverlay.style.display = 'none'; 216 }, 400); // 400ms = match the CSS opacity transition 217 218 popupOverlay.removeEventListener('click', unfocus); 219 if (lastFocusedElement) { 220 lastFocusedElement.focus(); // Return focus to the last focused element 221 } 222 } 223 // Remove scroll lock from the body and html. 224 unlockScroll(popupOverlay); 225 226 // Remove event listener to handle trap focus 227 document.removeEventListener('keydown', trapFocus); 228 229 // If debug mode is enabled, log the event. 230 if (isDebugMode(popupOverlay)) { 231 console.log(`[Emberly Popups] Popup closed: ${popupId}`); 232 } 233 } 234 235 // Open the popup when the trigger is clicked. 236 const triggers = document.querySelectorAll('[em-popup-trigger-id]'); 237 triggers.forEach(trigger => { 238 trigger.addEventListener('click', (event) => { 239 event.preventDefault(); 240 241 const popupId = trigger.getAttribute('em-popup-trigger-id'); 242 const popupOverlay = document.querySelector(`.em-popup-overlay[data-em-popup-id="${popupId}"]`); 243 244 // Always log something for verification 245 console.log(`[Emberly Popup] Trigger clicked for popup ID: "${popupId}"`); 246 247 if (!popupOverlay) { 248 console.warn(`[Emberly Popup] No popup overlay found for ID: "${popupId}"`); 249 } else { 250 console.log(`[Emberly Popup] Found popup overlay for ID: "${popupId}"`); 251 } 252 253 if (popupOverlay?.getAttribute('data-em-popup-debug') === 'true') { 254 console.log('%c[Emberly Popup Debug]', 'color: orange; font-weight: bold;'); 255 console.log('Trigger element:', trigger); 256 console.log('Popup overlay element:', popupOverlay); 257 } 258 259 openPopup(trigger); 260 }); 261 }); 262 263 // Add event listeners to the close buttons 264 const closeButtons = document.querySelectorAll('.em-popup-close'); 265 closeButtons.forEach(closeButton => { 266 closeButton.addEventListener('click', () => { 267 const popupId = closeButton.closest('.em-popup').getAttribute('data-em-popup-id'); 268 closePopup(popupId); 269 }); 270 }); 271 272 // Close popup when clicking outside the popup content 273 const popups = document.querySelectorAll('.em-popup-overlay'); 274 popups.forEach(popup => { 275 popup.addEventListener('click', (e) => { 276 const popupContent = popup.querySelector('.em-popup'); 277 if (!popupContent.contains(e.target)) { 278 const popupId = popup.getAttribute('data-em-popup-id'); 279 closePopup(popupId); 280 } 281 }); 282 }); 283 284 // look for all elements with the class em-show-once 285 allAutoOpenPopups.forEach(element => { 286 const id = element.getAttribute('data-em-popup-id'); 287 const shouldOpenOnce = element.classList.contains('em-show-once'); 288 289 let displayOnceMethod = 'cookie'; // default 290 if (element.classList.contains('em-persistence-method-session')) { 291 displayOnceMethod = 'session'; 292 } 293 294 const displayEvery = element.getAttribute('data-em-show-interval-ms'); 295 const displayEveryMilliseconds = displayEvery ? parseInt(displayEvery) : 0; 296 297 let alreadyDisplayed = false; 298 const now = Date.now(); 299 300 if (shouldOpenOnce) { 301 if (displayOnceMethod === 'cookie') { 302 alreadyDisplayed = getCookie(id); 303 } else if (displayOnceMethod === 'session') { 304 alreadyDisplayed = sessionStorage.getItem(id); 305 } 306 } else if (displayEveryMilliseconds > 0) { 307 let lastShown = null; 308 if (displayOnceMethod === 'cookie') { 309 lastShown = getCookie(id + '_timestamp'); 310 } else if (displayOnceMethod === 'session') { 311 lastShown = sessionStorage.getItem(id + '_timestamp'); 312 } 313 if (lastShown) { 314 const lastShownTime = parseInt(lastShown, 10); 315 if (now - lastShownTime < displayEveryMilliseconds) { 316 alreadyDisplayed = true; // too soon, don't show 317 } 318 } 319 } 320 321 if (alreadyDisplayed) return; 322 323 // Open popup 324 openPopupByID(id); 325 326 // After opening, store either: 327 if (shouldOpenOnce) { 328 if (displayOnceMethod === 'cookie') { 329 setCookie(id, 'true', 365 * 24); // expires in 1 year (effectively forever) 330 } else { 331 sessionStorage.setItem(id, 'true'); 332 } 333 } else if (displayEveryMilliseconds > 0) { 334 if (displayOnceMethod === 'cookie') { 335 // Convert milliseconds to hours for cookie expiration 336 const hours = displayEveryMilliseconds / (1000 * 60 * 60); 337 setCookie(id + '_timestamp', now.toString(), hours); 338 } else { 339 sessionStorage.setItem(id + '_timestamp', now.toString()); 340 } 341 } 342 }); 343 344 // Log debug info when popups are initialized (on load) 345 document.querySelectorAll('.em-popup-overlay').forEach(popup => { 346 if (isDebugMode(popup)) { 347 const id = popup.getAttribute('data-em-popup-id'); 348 const delay = popup.getAttribute('data-em-delay') || '0'; 349 const interval = popup.getAttribute('data-em-show-interval-ms') || '0'; 350 const showOnce = popup.classList.contains('em-show-once'); 351 const persistence = popup.classList.contains('em-persistence-method-session') ? 'session' : 'cookie'; 352 353 console.groupCollapsed(`[Emberly Popups] Popup Loaded: "${id}"`); 354 console.table({ 355 'Popup ID': id, 356 'Delay (ms)': delay, 357 'Show Once': showOnce, 358 'Persistence': persistence, 359 'Interval (ms)': interval 360 }); 361 console.log('Popup Element:', popup); 362 console.groupEnd(); 363 } 364 }); 365 366 // Add escape key support to close the popup 367 document.addEventListener('keydown', (event) => { 368 if (event.key === 'Escape') { 369 const activePopup = document.querySelector('.em-popup-overlay.em-active'); 370 if (activePopup) { 371 const popupId = activePopup.getAttribute('id'); 372 closePopup(popupId); 373 } 374 } 375 }); 123 function handleOpenPopup(popupOverlay) { 124 if (!popupOverlay) return; 125 let delayTime = parseInt(popupOverlay.getAttribute('data-em-delay') || '0', 10); 126 127 const doOpen = () => { 128 popupOverlay.style.display = 'flex'; 129 setTimeout(() => popupOverlay.classList.add('em-active'), 10); 130 131 forceLabelClicksInsidePopup(popupOverlay); 132 lockScroll(popupOverlay); 133 134 popupOverlay.querySelectorAll('div:has(> iframe)').forEach(iframe => { 135 iframe.setAttribute('tabindex', '0'); 136 }); 137 138 const first = popupOverlay.querySelector(focusableElements); 139 if (first) { 140 first.focus(); 141 popupOverlay.addEventListener('click', unfocus); 142 } 143 document.addEventListener('keydown', trapFocus); 144 145 if (isDebugMode(popupOverlay)) { 146 console.log(`[Emberly Popups] Popup opened with delay: ${delayTime}ms`); 147 } 148 }; 149 150 delayTime > 0 ? setTimeout(doOpen, delayTime) : doOpen(); 151 152 if (isDebugMode(popupOverlay)) { 153 console.log(`[Emberly Popups] Popup opened: ${popupOverlay.dataset.emPopupId}`); 154 } 155 } 156 157 function openPopup(trigger) { 158 lastFocusedElement = document.activeElement; 159 const popupId = trigger.getAttribute('em-popup-trigger-id'); 160 const popupOverlay = document.querySelector(`.em-popup-overlay[data-em-popup-id="${popupId}"]`); 161 handleOpenPopup(popupOverlay); 162 } 163 164 function openPopupByID(popupId) { 165 const popupOverlay = document.querySelector(`.em-popup-overlay[data-em-popup-id="${popupId}"]`); 166 handleOpenPopup(popupOverlay); 167 } 168 169 function closePopup(popupId) { 170 const popupOverlay = document.querySelector(`.em-popup-overlay[data-em-popup-id="${popupId}"]`); 171 if (!popupOverlay) return; 172 173 popupOverlay.classList.remove('em-active'); 174 setTimeout(() => popupOverlay.style.display = 'none', 400); 175 popupOverlay.removeEventListener('click', unfocus); 176 unlockScroll(popupOverlay); 177 document.removeEventListener('keydown', trapFocus); 178 179 lastFocusedElement?.focus(); 180 181 if (isDebugMode(popupOverlay)) { 182 console.log(`[Emberly Popups] Popup closed: ${popupId}`); 183 } 184 } 185 186 // ——— Delegated click handler ————————————————————————— 187 document.addEventListener('click', event => { 188 // 1) Popup trigger? 189 const trigger = event.target.closest('[em-popup-trigger-id]'); 190 if (trigger) { 191 event.preventDefault(); 192 const id = trigger.getAttribute('em-popup-trigger-id'); 193 console.log(`[Emberly Popup] Trigger clicked for popup ID: "${id}"`); 194 openPopup(trigger); 195 return; 196 } 197 198 // 2) Close button inside popup? 199 const closeBtn = event.target.closest('.em-popup-close'); 200 if (closeBtn) { 201 event.preventDefault(); 202 const overlay = closeBtn.closest('.em-popup-overlay'); 203 if (overlay) closePopup(overlay.dataset.emPopupId); 204 return; 205 } 206 207 // 3) Click outside popup content? 208 const overlay = event.target.closest('.em-popup-overlay'); 209 if (overlay && !event.target.closest('.em-popup')) { 210 event.preventDefault(); 211 closePopup(overlay.dataset.emPopupId); 212 } 213 }); 214 215 // ——— Auto-open logic ————————————————————— 216 allAutoOpenPopups.forEach(el => { 217 const id = el.dataset.emPopupId; 218 const once = el.classList.contains('em-show-once'); 219 const method = el.classList.contains('em-persistence-method-session') ? 'session' : 'cookie'; 220 const intervalMs = parseInt(el.getAttribute('data-em-show-interval-ms') || '0', 10); 221 const now = Date.now(); 222 let shown = false; 223 224 if (once) { 225 shown = method === 'cookie' ? getCookie(id) : sessionStorage.getItem(id); 226 } else if (intervalMs > 0) { 227 const last = method === 'cookie' 228 ? getCookie(id + '_timestamp') 229 : sessionStorage.getItem(id + '_timestamp'); 230 if (last && now - parseInt(last, 10) < intervalMs) shown = true; 231 } 232 233 if (shown) return; 234 openPopupByID(id); 235 236 if (once) { 237 if (method === 'cookie') setCookie(id, 'true', 365 * 24); 238 else sessionStorage.setItem(id, 'true'); 239 } else if (intervalMs > 0) { 240 if (method === 'cookie') { 241 setCookie(id + '_timestamp', now.toString(), intervalMs / (1000*60*60)); 242 } else { 243 sessionStorage.setItem(id + '_timestamp', now.toString()); 244 } 245 } 246 }); 247 248 // ——— Debug info on init —————————————————————————— 249 document.querySelectorAll('.em-popup-overlay').forEach(popup => { 250 if (isDebugMode(popup)) { 251 const id = popup.dataset.emPopupId; 252 console.groupCollapsed(`[Emberly Popups] Popup Loaded: "${id}"`); 253 console.table({ 254 'Popup ID': id, 255 'Delay (ms)': popup.getAttribute('data-em-delay') || '0', 256 'Show Once': popup.classList.contains('em-show-once'), 257 'Persistence': popup.classList.contains('em-persistence-method-session') ? 'session' : 'cookie', 258 'Interval (ms)': popup.getAttribute('data-em-show-interval-ms') || '0', 259 }); 260 console.log('Popup Element:', popup); 261 console.groupEnd(); 262 } 263 }); 264 265 // ——— Escape-key support —————————————————————————— 266 document.addEventListener('keydown', event => { 267 if (event.key === 'Escape') { 268 const active = document.querySelector('.em-popup-overlay.em-active'); 269 if (active) closePopup(active.dataset.emPopupId); 270 } 271 }); 376 272 }); -
emberly-popups/trunk/lib/styles.css
r3290697 r3334981 49 49 50 50 .em-popup .em-popup-close { 51 width: 1 rem;52 height: 1 rem;51 width: 1.5rem; 52 height: 1.5rem; 53 53 display: flex; 54 54 position: absolute; … … 58 58 transition: all 0.4s ease; 59 59 opacity: 0.5; 60 padding: 0; 60 padding: 0.25rem; 61 border: 0; 62 } 63 64 .em-popup .em-popup-close, .em-popup .em-popup-close:focus { 61 65 background: transparent; 62 border: 0;66 color: #222; 63 67 } 64 68 -
emberly-popups/trunk/readme.txt
r3290697 r3334981 1 1 === Emberly Popups === 2 2 Contributors: emberlydigital 3 Tags: popup, modal, overlay, wordpress popup3 Tags: popup, modal, overlay, accessible, developer 4 4 Requires at least: 5.0 5 5 Tested up to: 6.8 6 Stable tag: 1. 26 Stable tag: 1.3 7 7 License: GPLv2 or later 8 8 License URI: https://www.gnu.org/licenses/gpl-2.0.html 9 Lightweight, accessible popups called via functions. By developers, for developers! 9 10 Lightweight, accessible popups for WordPress—made by developers, for developers. 10 11 11 12 == Description == 12 13 13 Emberly Popups is a lightweight WordPress function that creates accessible, customizable popup modals. 14 **Emberly Popups** is a simple PHP function that generates ARIA‑compliant modal popups. Great for newsletters, surveys, announcements, or any call‑to‑action: 14 15 15 Features include: 16 - Open automatically after a delay 17 - Show only once per user with cookie- or session-based control 18 - Control how often the popup appears using an interval 19 - Support for shortcodes inside popup content 20 - Adjustable popup width and padding 21 - Accessible HTML structure with ARIA roles and attributes 16 * Auto‑open after a configurable delay 17 * Cookie‑ or session‑based “show once” control 18 * Repeat interval (milliseconds) between popups 19 * Shortcode support inside popup content 20 * Adjustable width and padding 21 * Accessible markup with proper ARIA roles 22 22 23 23 == Installation == 24 24 25 1. Include the `emberly_popup()` function in your theme or plugin. 26 2. Call `emberly_popup()` where you want the popup to appear. 27 3. Make sure `assets/icons/close.svg` exists for the close button. 25 1. Copy the **emberly_popup()** function into your theme’s `functions.php` or a custom plugin. 26 2. Enqueue or include the popup’s CSS and JS assets. 27 3. Ensure `assets/icons/close.svg` exists (used for the close button). 28 4. Call `emberly_popup()` anywhere in your PHP templates. 29 30 == Usage == 31 32 === PHP 8+ (named arguments) === 33 ```php 34 emberly_popup( 35 title : 'Welcome!', 36 content : '<p>Thanks for visiting our site. Don’t miss our latest offers!</p>', 37 id : 'welcome-popup', 38 width : '50rem', 39 padding : '3rem', 40 echo : true, 41 output_shortcodes: true, 42 auto_open : true, 43 delay : 2000, 44 show_once : false, 45 persistence_method: 'session', 46 show_interval_ms : 1800000 // 30 minutes 47 ); 48 ``` 49 50 === PHP 7 & earlier (ordered arguments) === 51 ```php 52 emberly_popup( 53 '', // $title 54 '<p>Thanks for visiting our site. Don't miss our latest offers!</p>', 55 'category-survey-popup', // $id 56 '50rem', // $width 57 '3rem', // $padding 58 true, // $echo 59 true, // $output_shortcodes 60 true, // $auto_open 61 2000, // $delay 62 false, // $show_once 63 'session', // $persistence_method 64 1800000 // $show_interval_ms 65 ); 66 ``` 67 68 === Manual triggers === 69 Anywhere in your markup, add: 70 ```html 71 <a href="#" em-popup-trigger-id="welcome-popup">Open Welcome Popup</a> 72 ``` 73 The `em-popup-trigger-id` value must match the `$id` you set in `emberly_popup()`. 74 75 == Parameters == 76 77 | Parameter | Type | Default | Description | 78 |-----------------------|---------|-----------|-----------------------------------------------------------------------------| 79 | title | string | '' | Popup heading text. | 80 | content | string | '' | HTML for the popup’s body. | 81 | id | string | '' | Unique popup identifier (required for cookies/sessions & triggers). | 82 | width | string | '60rem' | CSS max-width for the popup container. | 83 | padding | string | '3rem' | Inner padding inside the popup. | 84 | echo | bool | true | Echo the markup immediately (false to return as string). | 85 | output_shortcodes | bool | false | Process WordPress shortcodes in `content`. | 86 | auto_open | bool | false | Automatically open after `delay` ms. | 87 | delay | int | 0 | Milliseconds to wait before auto-open. | 88 | show_once | bool | false | If true, shows only once per session/cookie period. | 89 | persistence_method | string | 'cookie' | Where to store “shown” flag: 'cookie' or 'session'. | 90 | show_interval_ms | int | 0 | Minimum interval (ms) before showing again. 0 disables repeats. | 91 | debug | bool | false | Log internal events to browser console (load, open, cookies, scroll lock). | 28 92 29 93 == Debugging == 30 94 31 Want to know exactly when a popup loads, opens, sets cookies, or locks the scroll? 32 33 Use the `debug` parameter to enable verbose logging for a specific popup. This outputs helpful information to the browser console for troubleshooting purposes. 34 35 To enable: 95 Add `debug: true` (PHP 8+) or append `true` as the 13th argument (PHP 7) to enable console logging: 36 96 37 97 ```php 98 // PHP 8+ example 38 99 emberly_popup( 39 title : 'Example',100 title : 'Debug Popup', 40 101 content: '<p>Debugging is on!</p>', 41 id: 'debug-popup', 42 debug: true // 👈 enable debug mode 43 ); 44 45 == Usage == 46 47 Calling a popup: 48 49 Popups can only be called via PHP functions. The most ideal scenario is to call them using PHP 8+, which allows you to be specific about which attributes you're utilizing. 50 51 Example using **PHP 8+ named arguments**: 52 53 ```php 54 emberly_popup( 55 title: '', 56 content: '<p>Thanks for visiting our site. Don\'t miss our latest offers!</p>', 57 id: 'welcome-popup', 58 width: '50rem', 59 padding: '3rem', 60 echo: true, 61 output_shortcodes: true, 62 auto_open: true, 63 delay: 2000, 64 show_once: false, 65 persistence_method: 'session', 66 show_interval_ms: 1800000 // 30 minutes in milliseconds 102 id : 'debug-popup', 103 debug : true 67 104 ); 68 105 ``` 69 106 70 Example for **PHP 7 and earlier** (ordered arguments):71 72 ```php73 emberly_popup(74 '', // $title75 '<p>Thanks for visiting our site. Don\'t miss our latest offers!</p>',76 'category-survey-popup', // $id77 '50rem', // $width78 '3rem', // $padding79 true, // $echo80 true, // $output_shortcodes81 true, // $auto_open82 2000, // $delay83 false, // $show_once84 'session', // $persistence_method85 1800000 // $show_interval_ms86 );87 ```88 89 == Triggering Popups ==90 91 You can open popups automatically by using the auto_open parameter.92 93 Want to open with a click? Popups can be opened manually by adding a trigger element anywhere on your page with the `em-popup-trigger-id` attribute. The value should match the ID you assigned to the popup via the `$id` parameter in your `emberly_popup()` call.94 95 ### Example96 97 ```html98 <a href="#" em-popup-trigger-id="welcome-popup">Open Welcome Popup</a>99 100 Parameters:101 102 | Parameter | Type | Default | Description |103 |:--|:--|:--|:--|104 | $title | string | '' | Title text of the popup. |105 | $content | string | '' | Content HTML inside the popup. |106 | $id | string | '' | Unique ID for the popup. |107 | $width | string | '60rem' | Maximum width of the popup. |108 | $padding | string | '3rem' | Padding inside the popup. |109 | $echo | bool | true | Echo the popup or return it as a string. |110 | $output_shortcodes | bool | false | Run shortcodes inside the content. |111 | $auto_open | bool | false | Open automatically after a delay. |112 | $delay | int | 0 | Auto-open delay in milliseconds. |113 | $show_once | bool | false | Only show once per session or cookie, depending on persistence. |114 | $persistence_method | string | 'cookie' | Where to store whether the popup was shown. Options: 'cookie' or 'session'. |115 | $show_interval_ms | int | 0 | How often (in milliseconds) the popup should show again. 0 disables repeat showing. |116 117 107 == Frequently Asked Questions == 118 108 119 = What happens if no ID is set? = 109 = What if I don’t set an ID? = 110 An ID is required for cookies/sessions and manual triggers; omit at your own risk. 120 111 121 Each popup should have a unique ID for proper functioning, especially when using cookies, sessions, or auto-open behavior. 122 123 = Can I use shortcodes inside the popup content? = 124 125 Yes, just set `$output_shortcodes` to `true`. 112 = Can I use shortcodes in my popup? = 113 Yes—set `output_shortcodes` to `true`. 126 114 127 115 == Changelog == 128 116 117 = 1.3 = 118 * Fixed minor CSS selector issue in nested themes. 119 * Added `debug` parameter to PHP 7 ordered args example. 120 * Improved README formatting for WP.org. 121 * Fixed trigger logging order. Now logs triggers before content by moving the trigger handler up to the body. 122 * Added compatibility to override smooth scrolling when closing a popup. 123 124 = 1.2 = 125 * Added `debug` logging support. 126 * Support for PHP 8 named arguments. 127 129 128 = 1.0 = 130 * Initial release.129 * Initial public release. 131 130 132 131 == Upgrade Notice == 133 132 134 = 1. 0=135 First public release.133 = 1.3 = 134 Make sure to rebuild assets if you override CSS/JS; no breaking changes. 136 135 137 136 == Screenshots == 138 139 No screenshots available. 137 No screenshots yet. Feel free to submit one via the support forum!
Note: See TracChangeset
for help on using the changeset viewer.