@newswire/frames
Version:
A minimalistic take on responsive iframes in the spirit of Pym.js.
1 lines • 12.7 kB
Source Map (JSON)
{"version":3,"file":"frames.cjs","sources":["../src/constants.js","../src/framer.js","../src/auto.js","../src/frames.js"],"sourcesContent":["export const HEIGHT = 'height';\nexport const EMBED_SIZE = 'embed-size';\nexport const INITIAL_MESSAGE = 'frames-init';\nexport const AMP_SENTINEL = 'amp';\nexport const PYM_SENTINEL = 'pym';\nexport const PYM_REGEX = /xPYMx/;\nexport const FRAME_PREFIX = 'data-frame-';\nexport const FRAME_AUTO_INITIALIZED = `${FRAME_PREFIX}auto-initialized`;\nexport const FRAME_SRC = `${FRAME_PREFIX}src`;\nexport const FRAME_ATTRIBUTE_PREFIX = `${FRAME_PREFIX}attribute-`;\n","import {\n\tAMP_SENTINEL,\n\tEMBED_SIZE,\n\tHEIGHT,\n\tINITIAL_MESSAGE,\n\tPYM_REGEX,\n\tPYM_SENTINEL,\n} from './constants.js';\n\n/**\n * Adds an event listener to an existing iframe for receiving height change\n * messages. Also tells the iframe that we're listening and requests the\n * initial height. Returns an `unobserve()` function for later removing the\n * listener.\n *\n * @param {HTMLIFrameElement} iframe the iframe to observe\n * @returns {() => void}\n * @example\n *\n * // grab a reference to an existing iframe\n * const iframe = document.getElementById('my-embed');\n *\n * // returns a `unobserve()` function if you need to stop listening\n * const unobserve = observeIframe(iframe);\n *\n * // later, if you need to disconnect from the iframe\n * unobserve();\n */\nexport function observeIframe(iframe) {\n\t/**\n\t * @private\n\t * @param {MessageEvent} event\n\t * @returns {void}\n\t */\n\tfunction processMessage(event) {\n\t\t// this message isn't from our created frame, stop here\n\t\tif (event.source !== iframe.contentWindow) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst { data } = event;\n\n\t\t// if the sentinel and type matches, update our height\n\t\tif (data.sentinel === AMP_SENTINEL && data.type === EMBED_SIZE) {\n\t\t\tiframe.setAttribute(HEIGHT, data.height);\n\t\t} else if (typeof data === 'string' && data.slice(0, 3) === PYM_SENTINEL) {\n\t\t\tconst [, , type, height] = data.split(PYM_REGEX);\n\n\t\t\tif (type === HEIGHT) {\n\t\t\t\tiframe.setAttribute(HEIGHT, height);\n\t\t\t}\n\t\t}\n\t}\n\n\twindow.addEventListener('message', processMessage, false);\n\n\t// tell the iframe we've connected\n\tif (iframe.contentWindow) {\n\t\tiframe.contentWindow.postMessage(\n\t\t\t{ sentinel: AMP_SENTINEL, type: INITIAL_MESSAGE },\n\t\t\t'*',\n\t\t);\n\t}\n\n\treturn function unobserve() {\n\t\twindow.removeEventListener('message', processMessage, false);\n\t};\n}\n\n/**\n * @typedef {object} FramerOptions\n * @property {string | null} [src] the URL to set as the `src` of the iframe\n * @property {Record<string, string>} [attributes] any attributes to add to the iframe itself\n */\n\n/**\n * The Framer function to be called in the host page. A wrapper around\n * interactions with a created iframe. Returns a `remove()` function for\n * disconnecting the event listener and removing the iframe from the DOM.\n *\n * @param {Element} container the containing DOM element for the iframe\n * @param {FramerOptions} options\n * @returns {{remove: () => void}}\n */\nexport function Framer(container, { attributes, src } = {}) {\n\t// create the iframe\n\tconst iframe = document.createElement('iframe');\n\n\t// hook up our observer\n\tconst unobserve = observeIframe(iframe);\n\n\t// set its source if provided\n\tif (src) {\n\t\tiframe.setAttribute('src', src);\n\t}\n\n\t// set some smart default attributes\n\tiframe.setAttribute('width', '100%');\n\tiframe.setAttribute('scrolling', 'no');\n\tiframe.setAttribute('marginheight', '0');\n\tiframe.setAttribute('frameborder', '0');\n\n\tif (attributes) {\n\t\t// apply any supplied attributes\n\t\tfor (let key in attributes) {\n\t\t\tconst value = attributes[key];\n\n\t\t\tiframe.setAttribute(key, value);\n\t\t}\n\t}\n\n\t// append it to the container\n\tcontainer.appendChild(iframe);\n\n\treturn {\n\t\tremove() {\n\t\t\tunobserve();\n\t\t\tcontainer.removeChild(iframe);\n\t\t},\n\t};\n}\n","import { Framer } from './framer.js';\nimport {\n\tFRAME_ATTRIBUTE_PREFIX,\n\tFRAME_AUTO_INITIALIZED,\n\tFRAME_SRC,\n} from './constants.js';\n\n/**\n * @private\n * @type {number}\n */\nconst prefixLength = FRAME_ATTRIBUTE_PREFIX.length;\n\n/**\n * Searches an element's attributes and returns an Object of all the ones that\n * begin with our prefix. Each matching attribute name is returned\n * without the prefix.\n *\n * @private\n * @param {Element} element\n * @return {Record<string, string>}\n */\nfunction getMatchingAttributes(element) {\n\t// prepare the object to return\n\t/**\n\t * @private\n\t * @type {Record<string, string>}\n\t */\n\tconst attrs = {};\n\n\t// grab all the attributes off the element\n\tconst map = element.attributes;\n\n\t// get a count of the number of attributes\n\tconst length = map.length;\n\n\t// loop through the attributes\n\tfor (let i = 0; i < length; i++) {\n\t\t// get each attribute key\n\t\tconst key = map[i].name;\n\n\t\t// continue if the key begins with supplied prefix\n\t\tif (key.slice(0, prefixLength) === FRAME_ATTRIBUTE_PREFIX) {\n\t\t\t// slice off the prefix to get the bare field key\n\t\t\tconst field = key.slice(prefixLength);\n\n\t\t\t// grab the value associated with the key\n\t\t\tconst value = map[i].value;\n\n\t\t\t// add matching key to object\n\t\t\tattrs[field] = value;\n\t\t}\n\t}\n\n\treturn attrs;\n}\n\n/**\n * Automatically initializes any frames that have not already been\n * auto-activated.\n *\n * @example\n * // sets up all frames that have not been initialized yet\n * autoInitFrames();\n */\nexport function autoInitFrames() {\n\tconst elements = document.querySelectorAll(\n\t\t`[${FRAME_SRC}]:not([${FRAME_AUTO_INITIALIZED}])`,\n\t);\n\n\tfor (let i = 0; i < elements.length; i++) {\n\t\tconst container = elements[i];\n\n\t\tconst src = container.getAttribute(FRAME_SRC);\n\t\tconst attributes = getMatchingAttributes(container);\n\t\tcontainer.setAttribute(FRAME_AUTO_INITIALIZED, '');\n\n\t\tFramer(container, { attributes, src });\n\t}\n}\n","// internal\nimport { AMP_SENTINEL, EMBED_SIZE, INITIAL_MESSAGE } from './constants.js';\n\n/**\n * Gets the height of the current document's body. Uses offsetHeight to ensure\n * that margins are accounted for.\n *\n * @private\n * @returns {number}\n */\nfunction getDocumentHeight() {\n\treturn document.documentElement.offsetHeight;\n}\n\n/**\n * Sends the current document's height or provided value to the host window\n * using postMessage.\n *\n * @param {number} [height] The height to pass to the host page, is determined automatically if not passed\n * @returns {void}\n * @example\n *\n * // Uses the document's height to tell the host page\n * sendFrameHeight();\n *\n * // Pass a height you've determined in another way\n * sendFrameHeight(500);\n *\n */\nfunction sendFrameHeight(height = getDocumentHeight()) {\n\twindow.parent.postMessage(\n\t\t{ sentinel: AMP_SENTINEL, type: EMBED_SIZE, height },\n\t\t'*',\n\t);\n}\n\n/**\n * Sets up an event listener for the load event that sends the new frame\n * height to the host. Automatically removes itself once fired.\n *\n * @returns {void}\n * @example\n *\n * // once the frame's load event is fired, tell the host page its new height\n * sendHeightOnLoad();\n */\nfunction sendHeightOnLoad() {\n\twindow.addEventListener(\n\t\t'load',\n\t\tfunction load() {\n\t\t\tsendFrameHeight();\n\n\t\t\twindow.removeEventListener('load', load, false);\n\t\t},\n\t\tfalse,\n\t);\n}\n\n/**\n * Sets up an event listener for the resize event that sends the new frame\n * height to the host.\n *\n * @returns {void}\n * @example\n *\n * // every time the frame is resized, tell the host page what its new height is\n * sendHeightOnResize();\n */\nfunction sendHeightOnResize() {\n\twindow.addEventListener('resize', () => sendFrameHeight(), false);\n}\n\n/**\n * Sets up an event listener for a message from the parent window that it is\n * now listening for messages from this iframe, and tells it the iframe's height\n * at that time. This makes it possible to delay observing an iframe (e.g. when\n * lazy loading) but trust the parent will get the current height ASAP.\n *\n * @returns {void}\n * @example\n *\n * // as soon as a Framer connects, tell the host page what the current height is\n * sendHeightOnFramerInit();\n */\nfunction sendHeightOnFramerInit() {\n\twindow.addEventListener(\n\t\t'message',\n\t\tfunction onInit(event) {\n\t\t\tconst { data } = event;\n\n\t\t\t// if the sentinel and type matches, update our height\n\t\t\tif (data.sentinel === AMP_SENTINEL && data.type === INITIAL_MESSAGE) {\n\t\t\t\t// don't need it anymore\n\t\t\t\twindow.removeEventListener('message', onInit, false);\n\n\t\t\t\t// send the current frame height\n\t\t\t\tsendFrameHeight();\n\t\t\t}\n\t\t},\n\t\tfalse,\n\t);\n}\n\n/**\n * Sends height updates to the host page on an interval.\n *\n * @param {number} [delay] How long to set the interval\n * @returns {void}\n * @example\n *\n * // will call sendFrameHeight every 300ms\n * sendHeightOnPoll();\n *\n * // will call sendFrameHeight every 150ms\n * sendHeightOnPoll(150);\n */\nfunction sendHeightOnPoll(delay = 300) {\n\tsetInterval(sendFrameHeight, delay);\n}\n\n/**\n * A helper for running the standard functions for setting up a frame.\n *\n * Automatically calls an `sendFrameHeight`, `sendHeightOnLoad`, `sendHeightOnResize`\n * and `sendHeightOnFramerInit`.\n *\n * @returns {void}\n * @example\n *\n * initFrame();\n */\nfunction initFrame() {\n\tsendFrameHeight();\n\tsendHeightOnLoad();\n\tsendHeightOnResize();\n\tsendHeightOnFramerInit();\n}\n\n/**\n * Calls `initFrame` to setup a frame, then initializes a poller to continue to update on an interval.\n *\n * @param {number} [delay] An optional custom delay to pass to sendHeightOnPoll\n * @returns {void}\n * @example\n *\n * // calls initFrame, then calls sendHeightOnPoll\n * initFrameAndPoll();\n */\nfunction initFrameAndPoll(delay) {\n\tinitFrame();\n\tsendHeightOnPoll(delay);\n}\n\nexport {\n\tinitFrame,\n\tinitFrameAndPoll,\n\tsendFrameHeight,\n\tsendHeightOnLoad,\n\tsendHeightOnPoll,\n\tsendHeightOnResize,\n\tsendHeightOnFramerInit,\n};\n"],"names":["PYM_REGEX","observeIframe","iframe","processMessage","event","source","contentWindow","data","sentinel","type","setAttribute","height","slice","split","window","addEventListener","postMessage","removeEventListener","Framer","container","attributes","src","document","createElement","unobserve","key","appendChild","remove","removeChild","prefixLength","FRAME_PREFIX","length","getMatchingAttributes","element","attrs","map","i","name","value","sendFrameHeight","documentElement","offsetHeight","parent","sendHeightOnLoad","load","sendHeightOnResize","sendHeightOnFramerInit","onInit","sendHeightOnPoll","delay","setInterval","initFrame","elements","querySelectorAll","getAttribute"],"mappings":"IAKaA,EAAY,iBCuBTC,EAAcC,GAM7B,SAASC,EAAeC,GAEvB,GAAIA,EAAMC,SAAWH,EAAOI,cAA5B,CAIA,IAAQC,EAASH,EAATG,KAGR,GDxC0B,QCwCtBA,EAAKC,UD1Ce,eC0CcD,EAAKE,KAC1CP,EAAOQ,aD5CY,SC4CSH,EAAKI,gBACP,iBAATJ,GDzCQ,QCyCaA,EAAKK,MAAM,EAAG,GAAqB,CACzE,MAA2BL,EAAKM,MAAMb,GD9CnB,iBCiDlBE,EAAOQ,aDjDW,iBCgErB,OAVAI,OAAOC,iBAAiB,UAAWZ,GAAgB,GAG/CD,EAAOI,eACVJ,EAAOI,cAAcU,YACpB,CAAER,SDxDuB,MCwDCC,KDzDE,eC0D5B,gBAKDK,OAAOG,oBAAoB,UAAWd,GAAgB,aAmBxCe,EAAOC,sBAAiC,KAApBC,IAAAA,WAAYC,IAAAA,IAEzCnB,EAASoB,SAASC,cAAc,UAGhCC,EAAYvB,EAAcC,GAahC,GAVImB,GACHnB,EAAOQ,aAAa,MAAOW,GAI5BnB,EAAOQ,aAAa,QAAS,QAC7BR,EAAOQ,aAAa,YAAa,MACjCR,EAAOQ,aAAa,eAAgB,KACpCR,EAAOQ,aAAa,cAAe,KAE/BU,EAEH,IAAK,IAAIK,KAAOL,EAGflB,EAAOQ,aAAae,EAFNL,EAAWK,IAS3B,OAFAN,EAAUO,YAAYxB,GAEf,CACNyB,kBACCH,IACAL,EAAUS,YAAY1B,KC1GzB,IAAM2B,EFFmCC,wBEEGC,OAW5C,SAASC,EAAsBC,GAe9B,IATA,IAAMC,EAAQ,GAGRC,EAAMF,EAAQb,WAGdW,EAASI,EAAIJ,OAGVK,EAAI,EAAGA,EAAIL,EAAQK,IAAK,CAEhC,IAAMX,EAAMU,EAAIC,GAAGC,KF9BoBP,0BEiCnCL,EAAIb,MAAM,EAAGiB,KAQhBK,EANcT,EAAIb,MAAMiB,IAGVM,EAAIC,GAAGE,OAOvB,OAAOJ,ECzBR,SAASK,EAAgB5B,YAAAA,IAAAA,EAlBjBW,SAASkB,gBAAgBC,cAmBhC3B,OAAO4B,OAAO1B,YACb,CAAER,SH5BwB,MG4BAC,KH9BF,aG8BoBE,OAAAA,GAC5C,KAcF,SAASgC,IACR7B,OAAOC,iBACN,OACA,SAAS6B,IACRL,IAEAzB,OAAOG,oBAAoB,OAAQ2B,GAAM,KAE1C,GAcF,SAASC,IACR/B,OAAOC,iBAAiB,SAAU,kBAAMwB,MAAmB,GAe5D,SAASO,IACRhC,OAAOC,iBACN,UACA,SAASgC,EAAO3C,GACf,IAAQG,EAASH,EAATG,KHrFiB,QGwFrBA,EAAKC,UHzFmB,gBGyFUD,EAAKE,OAE1CK,OAAOG,oBAAoB,UAAW8B,GAAQ,GAG9CR,OAGF,GAiBF,SAASS,EAAiBC,YAAAA,IAAAA,EAAQ,KACjCC,YAAYX,EAAiBU,GAc9B,SAASE,IACRZ,IACAI,IACAE,IACAC,uDDjEA,IAJA,IAAMM,EAAW9B,SAAS+B,wEAIjBjB,EAAI,EAAGA,EAAIgB,EAASrB,OAAQK,IAAK,CACzC,IAAMjB,EAAYiC,EAAShB,GAErBf,EAAMF,EAAUmC,aFjEIxB,kBEkEpBV,EAAaY,EAAsBb,GACzCA,EAAUT,aFpE6BoB,8BEoEQ,IAE/CZ,EAAOC,EAAW,CAAEC,WAAAA,EAAYC,IAAAA,mDCuElC,SAA0B4B,GACzBE,IACAH,EAAiBC"}