1 line
No EOL
18 KiB
Text
1 line
No EOL
18 KiB
Text
{"version":3,"file":"fitty.min.js","sources":["../../src/util/fitty.js"],"sourcesContent":["/*eslint no-console: \"off\"*/\n/*\nCopyright (c) 2017-2021 Rik Schennink - All rights reserved.\n\nPermission is hereby granted, free of charge, to any person obtaining a copy of this software\nand associated documentation files (the \"Software\"), to deal in the Software without restriction,\nincluding without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,\nand/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,\nsubject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all copies\nor substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,\nINCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR\nPURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE\nFOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,\nARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.\n*/\nexport default ((w) => {\n // no window, early exit\n if (!w) { return; }\n\n // node list to array helper method\n const toArray = (nl) => [].slice.call(nl);\n\n // states\n const DrawState = {\n IDLE: 0,\n DIRTY_CONTENT: 1,\n DIRTY_LAYOUT: 2,\n DIRTY: 3,\n };\n\n // all active fitty elements\n let fitties = [];\n\n // group all redraw calls till next frame, we cancel each frame request when a new one comes in.\n // If no support for request animation frame, this is an empty function and supports for fitty stops.\n let redrawFrame = null;\n const requestRedraw =\n 'requestAnimationFrame' in w\n ? (options = { sync: false }) => {\n w.cancelAnimationFrame(redrawFrame);\n\n const redrawFn = () => redraw(fitties.filter((f) => f.dirty && f.active));\n\n if (options.sync) { return redrawFn(); }\n\n redrawFrame = w.requestAnimationFrame(redrawFn);\n }\n : () => {};\n\n // sets all fitties to dirty so they are redrawn on the next redraw loop, then calls redraw\n const redrawAll = (type) => (options) => {\n fitties.forEach((f) => (f.dirty = type));\n requestRedraw(options);\n };\n\n // redraws fitties so they nicely fit their parent container\n const redraw = (fitties) => {\n // getting info from the DOM at this point should not trigger a reflow,\n // let's gather as much intel as possible before triggering a reflow\n\n // check if styles of all fitties have been computed\n fitties\n .filter((f) => !f.styleComputed)\n .forEach((f) => {\n f.styleComputed = computeStyle(f);\n });\n\n // restyle elements that require pre-styling, this triggers a reflow, please try to prevent by adding CSS rules (see docs)\n fitties.filter(shouldPreStyle).forEach(applyStyle);\n\n // we now determine which fitties should be redrawn\n const fittiesToRedraw = fitties.filter(shouldRedraw);\n\n // we calculate final styles for these fitties\n fittiesToRedraw.forEach(calculateStyles);\n\n // now we apply the calculated styles from our previous loop\n fittiesToRedraw.forEach((f) => {\n applyStyle(f);\n markAsClean(f);\n });\n\n // now we dispatch events for all restyled fitties\n fittiesToRedraw.forEach(dispatchFitEvent);\n };\n\n const markAsClean = (f) => (f.dirty = DrawState.IDLE);\n\n const calculateStyles = (f) => {\n if (f.vertical) {\n // get available width from parent node\n f.availableHeight = f.element.parentNode.clientHeight;\n\n // the space our target element uses\n f.currentHeight = f.element.scrollHeight;\n\n // remember current font size\n f.previousFontSize = f.currentFontSize;\n\n // let's calculate the new font size\n f.currentFontSize = Math.min(\n Math.max(f.minSize, (f.availableHeight / f.currentHeight) * f.previousFontSize),\n f.maxSize\n );\n\n } else {\n // get available width from parent node\n f.availableWidth = f.element.parentNode.clientWidth;\n\n // the space our target element uses\n f.currentWidth = f.element.scrollWidth;\n\n // remember current font size\n f.previousFontSize = f.currentFontSize;\n\n // let's calculate the new font size\n f.currentFontSize = Math.min(\n Math.max(f.minSize, (f.availableWidth / f.currentWidth) * f.previousFontSize),\n f.maxSize\n );\n }\n // if allows wrapping, only wrap when at minimum font size (otherwise would break container)\n f.whiteSpace = f.multiLine && f.currentFontSize === f.minSize ? 'normal' : 'nowrap';\n };\n\n // should always redraw if is not dirty layout, if is dirty layout, only redraw if size has changed\n const shouldRedraw = (f) => {\n if (f.vertical) {\n return f.dirty !== DrawState.DIRTY_LAYOUT ||\n (f.dirty === DrawState.DIRTY_LAYOUT &&\n f.element.parentNode.clientHeight !== f.availableHeight);\n } else {\n return f.dirty !== DrawState.DIRTY_LAYOUT ||\n (f.dirty === DrawState.DIRTY_LAYOUT &&\n f.element.parentNode.clientWidth !== f.availableWidth);\n }\n };\n // every fitty element is tested for invalid styles\n const computeStyle = (f) => {\n // get style properties\n const style = w.getComputedStyle(f.element, null);\n\n // get current font size in pixels (if we already calculated it, use the calculated version)\n f.currentFontSize = parseFloat(style.getPropertyValue('font-size'));\n\n // get display type and wrap mode\n f.display = style.getPropertyValue('display');\n f.whiteSpace = style.getPropertyValue('white-space');\n\n // styles computed\n return true;\n };\n\n // determines if this fitty requires initial styling, can be prevented by applying correct styles through CSS\n const shouldPreStyle = (f) => {\n let preStyle = false;\n\n // if we already tested for prestyling we don't have to do it again\n if (f.preStyleTestCompleted) { return false; }\n\n // should have an inline style, if not, apply\n if (!/inline-/.test(f.display)) {\n preStyle = true;\n f.display = 'inline-block';\n }\n\n // to correctly calculate dimensions the element should have whiteSpace set to nowrap\n if (f.whiteSpace !== 'nowrap') {\n preStyle = true;\n f.whiteSpace = 'nowrap';\n }\n\n // we don't have to do this twice\n f.preStyleTestCompleted = true;\n\n return preStyle;\n };\n\n // apply styles to single fitty\n const applyStyle = (f) => {\n f.element.style.whiteSpace = f.whiteSpace;\n f.element.style.display = f.display;\n f.element.style.fontSize = f.currentFontSize + 'px';\n };\n\n // dispatch a fit event on a fitty\n const dispatchFitEvent = (f) => {\n f.element.dispatchEvent(\n new CustomEvent('fit', {\n detail: {\n oldValue: f.previousFontSize,\n newValue: f.currentFontSize,\n scaleFactor: f.currentFontSize / f.previousFontSize,\n },\n })\n );\n };\n\n // fit method, marks the fitty as dirty and requests a redraw (this will also redraw any other fitty marked as dirty)\n const fit = (f, type) => (options) => {\n f.dirty = type;\n if (!f.active) { return; }\n requestRedraw(options);\n };\n\n const init = (f) => {\n // save some of the original CSS properties before we change them\n f.originalStyle = {\n whiteSpace: f.element.style.whiteSpace,\n display: f.element.style.display,\n fontSize: f.element.style.fontSize,\n };\n\n // should we observe DOM mutations\n observeMutations(f);\n\n // this is a new fitty so we need to validate if it's styles are in order\n f.newbie = true;\n\n // because it's a new fitty it should also be dirty, we want it to redraw on the first loop\n f.dirty = true;\n\n // we want to be able to update this fitty\n fitties.push(f);\n };\n\n const destroy = (f) => () => {\n // remove from fitties array\n fitties = fitties.filter((_) => _.element !== f.element);\n\n // stop observing DOM\n if (f.observeMutations) { f.observer.disconnect(); }\n\n // reset the CSS properties we changes\n f.element.style.whiteSpace = f.originalStyle.whiteSpace;\n f.element.style.display = f.originalStyle.display;\n f.element.style.fontSize = f.originalStyle.fontSize;\n };\n\n // add a new fitty, does not redraw said fitty\n const subscribe = (f) => () => {\n if (f.active) { return; }\n f.active = true;\n requestRedraw();\n };\n\n // remove an existing fitty\n const unsubscribe = (f) => () => (f.active = false);\n\n const observeMutations = (f) => {\n // no observing?\n if (!f.observeMutations) { return; }\n\n // start observing mutations\n f.observer = new MutationObserver(fit(f, DrawState.DIRTY_CONTENT));\n\n // start observing\n f.observer.observe(f.element, f.observeMutations);\n };\n\n // default mutation observer settings\n const mutationObserverDefaultSetting = {\n subtree: true,\n childList: true,\n characterData: true,\n };\n\n // default fitty options\n const defaultOptions = {\n minSize: 16,\n maxSize: 512,\n multiLine: true,\n vertical: false,\n observeMutations: 'MutationObserver' in w ? mutationObserverDefaultSetting : false,\n };\n\n /**\n * array of elements in, fitty instances out\n * @param {Array} elements\n * @param {object} options\n */\n function fittyCreate(elements, options) {\n // set options object\n const fittyOptions = Object.assign(\n {},\n\n // expand default options\n defaultOptions,\n\n // override with custom options\n options\n );\n\n // create fitties\n const publicFitties = elements.map((element) => {\n // create fitty instance\n const f = Object.assign({}, fittyOptions, {\n // internal options for this fitty\n element,\n active: true,\n });\n\n // initialise this fitty\n init(f);\n\n // expose API\n return {\n element,\n fit: fit(f, DrawState.DIRTY),\n unfreeze: subscribe(f),\n freeze: unsubscribe(f),\n unsubscribe: destroy(f),\n };\n });\n\n // call redraw on newly initiated fitties\n requestRedraw();\n\n // expose fitties\n return publicFitties;\n }\n\n /**\n * fitty creation function\n * @param {*} target\n * @param {*} options\n * @returns\n */\n function fitty(target, options = {}) {\n // if target is a string\n return typeof target === 'string'\n ? // treat it as a querySelector\n fittyCreate(toArray(document.querySelectorAll(target)), options)\n : // create single fitty\n fittyCreate([target], options)[0];\n }\n\n // handles viewport changes, redraws all fitties, but only does so after a timeout\n let resizeDebounce = null;\n const onWindowResized = () => {\n w.clearTimeout(resizeDebounce);\n resizeDebounce = w.setTimeout(redrawAll(DrawState.DIRTY_LAYOUT), fitty.observeWindowDelay);\n };\n\n // define observe window property, so when we set it to true or false events are automatically added and removed\n const events = ['resize', 'orientationchange'];\n Object.defineProperty(fitty, 'observeWindow', {\n set: (enabled) => {\n const method = `${enabled ? 'add' : 'remove'}EventListener`;\n events.forEach((e) => {\n w[method](e, onWindowResized);\n });\n },\n });\n\n // fitty global properties (by setting observeWindow to true the events above get added)\n fitty.observeWindow = true;\n fitty.observeWindowDelay = 100;\n\n // public fit all method, will force redraw no matter what\n fitty.fitAll = redrawAll(DrawState.DIRTY);\n\n // export our fitty function, we don't want to keep it to our selves\n return fitty;\n})(typeof window === 'undefined' ? null : window);\n"],"names":["w","DrawState","IDLE","DIRTY_CONTENT","DIRTY_LAYOUT","DIRTY","fitties","redrawFrame","requestRedraw","options","sync","cancelAnimationFrame","redrawFn","redraw","filter","f","dirty","active","requestAnimationFrame","redrawAll","type","forEach","styleComputed","computeStyle","shouldPreStyle","applyStyle","fittiesToRedraw","shouldRedraw","calculateStyles","markAsClean","dispatchFitEvent","vertical","availableHeight","element","parentNode","clientHeight","currentHeight","scrollHeight","previousFontSize","currentFontSize","Math","min","max","minSize","maxSize","availableWidth","clientWidth","currentWidth","scrollWidth","whiteSpace","multiLine","style","getComputedStyle","parseFloat","getPropertyValue","display","preStyle","preStyleTestCompleted","test","fontSize","dispatchEvent","CustomEvent","detail","oldValue","newValue","scaleFactor","fit","init","originalStyle","observeMutations","newbie","push","destroy","_","observer","disconnect","subscribe","unsubscribe","MutationObserver","observe","defaultOptions","subtree","childList","characterData","fittyCreate","elements","fittyOptions","Object","assign","publicFitties","map","unfreeze","freeze","fitty","target","nl","document","querySelectorAll","slice","call","resizeDebounce","onWindowResized","clearTimeout","setTimeout","observeWindowDelay","events","defineProperty","set","enabled","method","e","observeWindow","fitAll","window"],"mappings":"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"} |