lisn.js
Version:
Simply handle user gestures and actions. Includes widgets.
1 lines • 69.9 kB
Source Map (JSON)
{"version":3,"file":"same-height.cjs","names":["MC","_interopRequireWildcard","require","MH","_settings","_cssAlter","_domQuery","_log","_math","_text","_validation","_sizeWatcher","_widget","_debug","_interopRequireDefault","e","__esModule","default","t","WeakMap","r","n","o","i","f","__proto__","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","_defineProperty","_toPropertyKey","value","enumerable","configurable","writable","_toPrimitive","Symbol","toPrimitive","TypeError","String","Number","SameHeight","Widget","containerElement","instance","DUMMY_ID","isInstanceOf","register","registerWidget","WIDGET_NAME","element","config","isHTMLElement","logError","usageError","configValidator","constructor","_SameHeight$get","destroyPromise","destroy","id","items","getItemsFrom","sizeOf","item","keys","parentOf","fetchConfig","then","fullConfig","promiseResolve","isDestroyed","init","toColumn","setData","PREFIX_ORIENTATION","S_VERTICAL","toRow","delData","getItems","getItemConfigs","newMap","entries","exports","PREFIXED_NAME","prefixName","PREFIX_ROOT","PREFIX_ITEM","PREFIX_ITEM__FOR_SELECT","S_TEXT","S_IMAGE","MIN_CHARS_FOR_TEXT","diffTolerance","validateNumber","resizeThreshold","S_DEBOUNCE_WINDOW","minGap","maxFreeR","maxWidthR","isText","getData","lengthOf","innerText","areImagesLoaded","img","querySelectorAll","naturalWidth","width","naturalHeight","height","userConfig","_userConfig$minGap","_userConfig$maxFreeR","_userConfig$maxWidthR","_userConfig$diffToler","_userConfig$resizeThr","_userConfig$debounceW","colGapStr","getComputedStyleProp","getNumValue","strReplace","settings","sameHeightMinGap","_minGap","toNumWithBounds","min","_maxFreeR","sameHeightMaxFreeR","max","_maxWidthR","sameHeightMaxWidthR","_diffTolerance","sameHeightDiffTolerance","_resizeThreshold","sameHeightResizeThreshold","_debounceWindow","debounceWindow","sameHeightDebounceWindow","strValue","defaultValue","num","parseFloat","NaN","isNaN","findItems","getDefaultWidgetSelector","push","getVisibleContentChildren","inputItems","itemMap","addItem","itemType","isArray","Map","widget","logger","debug","Logger","name","formatAsString","sizeWatcher","SizeWatcher","reuse","allItems","callCounter","isFirstTime","lastOptimalHeight","hasScheduledReset","counterTimeout","resizeHandler","sizeData","debug7","setTimer","clearTimer","measurements","calculateMeasurements","getOptimalHeight","abs","setWidths","properties","bugError","_width","border","S_WIDTH","content","_height","S_HEIGHT","observeAll","onResize","target","unobserveAll","offResize","getWidthAtH","debug9","setNumericStyleJsVars","sameHeightW","_units","onDisable","onEnable","onDestroy","removeClasses","clear","getProperties","_type","_aspectR","_area","_extraH","_components","addClasses","getTextComponents","child","components","debug8","tArea","tExtraH","imgAR","flexW","nItems","thisTxtArea","thisTxtExtraH","component","cmpProps","thisAspectR","_tArea","_tExtraH","_imgAR","_flexW","_nItems","targetHeight","h0","sqrt","h2","h1","quadraticRoots","hR0","hR1","hR2","hF2","hF1","hConstr1","filter","v","isValidNum","hConstr2","tw0","iw0","freeSpace0","debug1"],"sources":["../../../src/ts/widgets/same-height.ts"],"sourcesContent":["/**\n * @module Widgets\n */\n\n// This widget finds optimal widths of flexbox children so that their heights\n// are equal or as close as possible to each other. It takes into account\n// whether they contain text (and possibly other elements, but not images) or\n// images.\n//\n// NOTE:\n// - We assume that a given flexbox child is either a \"text container\" and\n// contains only text and other non-image elements (such as buttons), or is\n// an \"image container\" and contains only images.\n// - We also assume that all the text inside a text container is the same\n// font size as the font size of the text container.\n//\n// ~~~~~~ BACKGROUND: analysis for one text container and one image container ~~~~~~\n//\n// A text box has a fixed area, its height decreasing as width increases.\n// Whereas an image has a fixed aspect ratio, its height increasing as width\n// increases.\n//\n// We want to find an optimal configuration at which the text container (which\n// can include other elements apart from text) and image heights are equal, or\n// if not possible, at which they are as close as possible to each other while\n// satisfying as best as possible these \"guidelines\" (constraints that are not\n// enforced), based on visual appeal:\n// - minGap, minimum gap between each item\n// - maxWidthR, maximum ratio between the width of the widest child and the\n// narrowest child\n// - maxFreeR, maximum free space in the container as a percentage of its\n// total width\n//\n// Then we set flex-basis as the optimal width (making sure this is disabled\n// when the flex direction is column). This allows for fluid width if the user\n// to configure shrink or wrap on the flexbox using CSS.\n//\n// ~~~~~~ FORMULAE: text and image width as a function of their height ~~~~~~\n//\n// For a given height, h, the widths of the text and image are:\n//\n// txtArea\n// txtW(h) = —————————————\n// h - txtExtraH\n//\n// imgW(h) = imgAspectR * h\n//\n// where txtExtraH comes from buttons and other non-text elements inside the\n// text container, whose height is treated as fixed (not changing with width).\n//\n// ~~~~~~ PLOT: total width as a function of height ~~~~~~\n//\n// The sum of the widths of image and text varies with their height, h, as:\n//\n// w(h) = txtW(h) + imgW(h)\n//\n// txtArea\n// = ————————————— + imgAspectR * h\n// h - txtExtraH\n//\n//\n// w(h)\n// ^\n// | | .\n// | . .\n// | . .\n// flexW + . .\n// | . .\n// | . .\n// | -\n// |\n// |———|———|—————|———————————> h\n// h1 h0 h2\n//\n//\n// ~~~~~~ FORMULAE: height at which total width is minimum ~~~~~~\n//\n// The minimum of the function w(h) is at h = h0\n//\n// ⌈ txtArea ⌉\n// h0 = sqrt| —————————— | + txtExtraH\n// ⌊ imgAspectR ⌋\n//\n// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n//\n// The widths of image and text container at height = h0 are:\n//\n// txtW(h0) = sqrt( txtArea * imgAspectR )\n//\n// imgW(h0) = sqrt( txtArea * imgAspectR ) + imgAspectR * txtExtraH\n// = txtW(h0) + imgAspectR * txtExtraH\n//\n// - NOTE: at if txtExtraH is 0 (i.e. the container has only text), then\n// their widths are equal at h0; otherwise the image is wider\n//\n// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n//\n// There are zero, one or two values of h at which w(h) equals the flexbox\n// width, flexW. Labelled h1 and h2 above.\n//\n// ~~~~~~ FORMULAE: height at which total width is equal to flexbox width ~~~~~~\n//\n// The heights at which the sum of the widths, w(h) equals exactly flexW are:\n//\n// -b ± sqrt( b^2 - 4ac )\n// h2/1 = ——————————————————————\n// 2a\n//\n// where:\n// a = imgAspectR\n// b = - ( (imgAspectR * txtExtraH) + flexW )\n// c = txtArea + (txtExtraH * flexW)\n//\n// If h1 and h2 are real, then h1 <= h0 <= h2, as shown in plot above.\n//\n// ~~~~~~ SCENARIOS: free space or overflow in the flexbox ~~~~~~\n//\n// Whether there is a solution to the above equation, i.e. whether h1 and h2\n// are real, depends on which scenario we have:\n//\n// 1. If flexW = w(h0), then h1 = h2 = h0\n// 2. If flexW < w(h0), then there is no exact solution, i.e. it's impossible\n// to fit the text and image inside the flexbox and have them equal heights;\n// there is overflow even at h0\n// 3. If flexW > w(h0) (as in the graph above), then at h0 there is free space\n// in the flexbox and we can choose any height between h1 and h2\n//\n// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n//\n// The widths h0, h1 and h2 represent the following visual configuration:\n// - h0: intermediate height, maximum free space in the container;\n// - h1: minimum height (i.e. wide text and small image), no free space in\n// the container;\n// - h2: maximum height (i.e. narrow text and large image), no free space in\n// the container;\n//\n// ~~~~~~ THEREFORE: approach ~~~~~~\n//\n// 1. If flexW = w(h0), i.e. h1 = h2 = h0:\n// => we choose h0 as the height\n// 2. If flexW < w(h0), i.e. it's impossible to fit the text and image inside\n// the flexbox and have them equal heights:\n// => we still choose h0 as the height as that gives the least amount of\n// overflow; user-defined CSS can control whether the items will be\n// shrunk, the flexbox will wrap or overflow\n// 3. If flexW > w(h0), i.e. at h0 there is free space in the flexbox:\n// => choose a height between h1 and h2 that best fits with the guidelines\n// maxWidthR and maxFreeR\n//\n// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n//\n// In scenario 3 we can look at the guidelines, maxWidthR and maxFreeR.\n//\n// ~~~~~~ GUIDELINE: maxWidthR ~~~~~~\n//\n// ~~~~~~ FORMULAE: height at which text and image width are equal ~~~~~~\n//\n// The width of the text and image container are equal at height hR0:\n//\n// txtExtraH + sqrt( txtExtraH^2 + 4 * (h0 - txtExtraH)^2 )\n// hR0 = ——————————————————————————————————————————————————————————\n// 2\n//\n// ~~~~~~ FORMULAE: height at which text to image width is maxWidthR ~~~~~~\n//\n// For heights < hR0, i.e. text becomes wider than the image, at some point the\n// ratio of text width to image width becomes maxWidthR. This happens at hR1.\n//\n// ⌈ 4 * (h0 - txtExtraH)^2 ⌉\n// txtExtraH + sqrt| txtExtraH^2 + —————————————————————— |\n// ⌊ maxWidthR ⌋\n// hR1 = ——————————————————————————————————————————————————————————\n// 2\n//\n// ~~~~~~ FORMULAE: height at which image to text width is maxWidthR ~~~~~~\n//\n// For heights > hR0, i.e. text becomes narrower than the image, at some point\n// the ratio of image width to text width becomes maxWidthR. This happens at hR2.\n//\n// txtExtraH + sqrt( txtExtraH^2 + 4 * maxWidthR * (h0 - txtExtraH)^2 )\n// hR2 = ——————————————————————————————————————————————————————————————————————\n// 2\n//\n// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n//\n// NOTE:\n// - hR1 <= hR0 <= hR2 && hR0 <= h0\n// - hR0, hR1 and hR2 are the first (larger) roots of the quadratic equation\n// with coefficients:\n// a = imgAspectR * R\n// b = - imgAspectR * txtExtraH * R\n// c = - textArea\n// where R = 1 gives hR0, R = maxWidthR gives hR1 and R = 1 / maxWidthR gives hR2\n// - The smaller roots of the equation should be negative, so we ignore them\n//\n// ~~~~~~ GUIDELINE: maxFreeR ~~~~~~\n//\n// ~~~~~~ FORMULAE: free space in flexbox relative to its width ~~~~~~\n//\n// The percentage of free space in the container is:\n//\n// flexW - w(h)\n// freeR = ————————————\n// flexW\n//\n//\n// txtArea\n// flexW - ————————————— - imgAspectR * h\n// h - txtExtraH\n// = —————————————————————————————————————————\n// flexW\n//\n// ~~~~~~ FORMULAE: height at which relative free space is maxFreeR ~~~~~~\n//\n// This would be equal to maxFreeR at hF1 and hF2:\n//\n// -b ± sqrt( b^2 - 4ac )\n// hF2/1 = ——————————————————————\n// 2a\n//\n// where:\n// a = imgAspectR\n// b = - ( (imgAspectR * txtExtraH) + ( flexW * (1 - maxFreeR) ) )\n// c = txtArea + ( txtExtraH * flexW * (1 - maxFreeR) )\n//\n// If hF1 and hF2 are real, then h1 < hF1 <= h0 <= hF2 < h2.\n//\n// ~~~~~~ THEREFORE: choosing a height in scenario 3 ~~~~~~\n//\n// So in scenario 3 we can choose any height h between\n//\n// max(h1, hR1, hF1) and min(h2, hR2, hF2)\n//\n// Note, it's possible that max(h1, hR1, hF1) is greater than min(h2, hR2, hF2),\n// e.g. if hF1 > hR2 or hR1 > hF2.\n//\n// This will make the text and image equal height, fitting in the flexbox, and\n// if possible, satisfying both maxFreeR and maxWidthR.\n//\n// Here we choose the smallest height possible, which would result in the\n// larger ratio between text width and image width, but it will satisfy the\n// constraints maxFreeR and maxWidthR, so that is ok.\n//\n// ~~~~~~ GENERALISING: for more than one text and/or image container ~~~~~~\n//\n// We can generalise the above in order to find an approximate solution for the\n// case of multiple text or image containers (an exact solution would require\n// solving a polynomial of degree equal to the number of elements).\n//\n// If we imaging putting all text in one container and all images in another\n// container we are back at the above exact solutions for a single text and\n// image container.\n//\n// We can solve for the following parameters:\n// - txtArea: total text area\n// = sum_i(txtArea_i)\n//\n// - txtExtraH: weighted average extra height\n// = sum_i(txtExtraH_i * txtArea_i) / txtArea\n//\n// - imgAspectR: total image aspect ratio (for horizontally laid out image\n// containers)\n// = sum_i(imgAspectR_i)\n//\n// ~~~~~~ CASE 1: only images containers ~~~~~~\n// If we have only image containers, we solve for the optimal height as follows:\n//\n// flexW = imgAspectR * h\n//\n// flexW\n// => hIdeal = ——————————\n// imgAspectR\n//\n// ~~~~~~ CASE 2: only text containers ~~~~~~\n// If we have only text containers, we solve for the optimal height as follows:\n//\n// txtArea\n// flexW = ——————————————————\n// hIdeal - txtExtraH\n//\n// txtArea\n// => hIdeal = ——————— + txtExtraH\n// flexW\n//\n// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n// Once we've found the optimal height h, we calculate the individual widths of\n// the flexbox children as:\n//\n// txtArea_i\n// txtW_i(h) = —————————————\n// h - txtExtraH_i\n//\n// imgW_i(h) = imgAspectR_i * h\n//\n// ~~~~~~ IMPLEMENTATION ~~~~~~\n//\n// We go through the flexbox children and determine whether a child is a \"text\n// container\" or an \"image container\".\n//\n// For image containers, we measure the width and height and calculate the\n// aspect ratio using these.\n//\n// For text containers, we measure their width and height. We calculate the\n// text area by measuring the size of all children of the text container that\n// are deemed to contain only text (or if the entire text container is deemed\n// to contain only text, then we take its size). Then we sum the areas of\n// all such text-only boxes.\n//\n// To determine the extra height in the text container, we take the total\n// height of all text-only boxes inside it, and we subtract that from its\n// measured height.\n//\n// NOTE:\n// - This does not work if the flexbox children are set to align stretch,\n// because in such cases there would be free vertical space in the container\n// that shouldn't be counted.\n// - If the flexbox children or any of their descendants have paddings and\n// margins, then this calculation would only work if the paddings/margins\n// inside text containers are absolute and only on top and bottom, and\n// paddings/margins inside image containers are in percentages and only on\n// descendants of the image container. Otherwise the image aspect ratio and the\n// extra text height would not be constant, and there may be extra width in\n// the text container. It is very tricky to take all of this into account. So\n// we ignore such cases and assume constant image aspect ratio and constant\n// text area and text container extra height.\n//\n// We use resize observers to get the size of relevant elements and\n// re-calculate as needed.\n\nimport * as MC from \"@lisn/globals/minification-constants\";\nimport * as MH from \"@lisn/globals/minification-helpers\";\n\nimport { settings } from \"@lisn/globals/settings\";\n\nimport {\n addClasses,\n removeClasses,\n getData,\n setData,\n delData,\n setNumericStyleJsVars,\n getComputedStyleProp,\n} from \"@lisn/utils/css-alter\";\nimport { getVisibleContentChildren } from \"@lisn/utils/dom-query\";\nimport { logError } from \"@lisn/utils/log\";\nimport { isValidNum, toNumWithBounds, quadraticRoots } from \"@lisn/utils/math\";\nimport { formatAsString } from \"@lisn/utils/text\";\nimport { validateNumber } from \"@lisn/utils/validation\";\n\nimport { SizeWatcher, SizeData } from \"@lisn/watchers/size-watcher\";\n\nimport {\n Widget,\n WidgetConfigValidatorObject,\n registerWidget,\n getDefaultWidgetSelector,\n} from \"@lisn/widgets/widget\";\n\nimport { LoggerInterface } from \"@lisn/debug/types\";\n\nimport debug from \"@lisn/debug/debug\";\n\n/**\n * Configures the given element as a {@link SameHeight} widget.\n *\n * The SameHeight widget sets up the given element as a flexbox and sets the\n * flex basis of its components so that their heights are as close as possible\n * to each other. It tracks their size (see {@link SizeWatcher}) and\n * continually updates the basis as needed.\n *\n * When calculating the best flex basis that would result in equal heights,\n * SameHeight determines whether a flex child is mostly text or mostly images\n * since the height of these scales in opposite manner with their width.\n * Therefore, the components of the widget should contain either mostly text or\n * mostly images.\n *\n * The widget should have more than one item and the items must be immediate\n * children of the container element.\n *\n * SameHeight tries to automatically determine if an item is mostly text or\n * mostly images based on the total display text content, but you can override\n * this in two ways:\n * 1. By passing a map of elements as {@link SameHeightConfig.items | items}\n * instead of an array, and setting the value for each to either `\"text\"` or\n * `\"image\"`\n * 2. By setting the `data-lisn-same-height-item` attribute to `\"text\"` or\n * `\"image\"` on the children. **NOTE** however that when auto-discovering the\n * items (i.e. when you have not explicitly passed a list/map of items), if\n * you set the `data-lisn-same-height-item` attribute on _any_ child you must\n * also add this attribute to all other children that are to be used by the\n * widget. Other children (that don't have this attribute) will be ignored\n * and assumed to be either zero-size or position absolute/fixed.\n *\n * **IMPORTANT:** You should not instantiate more than one {@link SameHeight}\n * widget on a given element. Use {@link SameHeight.get} to get an existing\n * instance if any. If there is already a widget instance, it will be destroyed!\n *\n * **IMPORTANT:** The element you pass will be set to `display: flex` and its\n * children will get `box-sizing: border-box` and continually updated\n * `flex-basis` style. You can add additional CSS to the element or its\n * children if you wish. For example you may wish to set `flex-wrap: wrap` on\n * the element and a `min-width` on the children.\n *\n * -----\n *\n * To use with auto-widgets (HTML API) (see {@link settings.autoWidgets}), the\n * following CSS classes or data attributes are recognized:\n * - `lisn-same-height` class or `data-lisn-same-height` attribute set on the\n * container element that constitutes the widget.\n *\n * When using auto-widgets, the elements that will be used as items are\n * discovered in the following way:\n * 1. The immediate children of the top-level element that constitutes the\n * widget that have the `lisn-same-height-item` class or\n * `data-lisn-same-height-item` attribute are taken.\n * 2. If none of the root's children have this class or attribute, then all of\n * the immediate children of the widget element except any `script` or\n * `style` elements are taken as the items.\n *\n * See below examples for what values you can use set for the data attribute\n * in order to modify the configuration of the automatically created widget.\n *\n * @example\n * This defines a simple SameHeight widget with one text and one image child.\n *\n * ```html\n * <div class=\"lisn-same-height\">\n * <div>\n * <p>\n * Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do\n * eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad\n * minim veniam, quis nostrud exercitation ullamco laboris nisi ut\n * aliquip ex ea commodo consequat. Duis aute irure dolor in\n * reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla\n * pariatur. Excepteur sint occaecat cupidatat non proident, sunt in\n * culpa qui officia deserunt mollit anim id est laborum.\n * </p>\n * </div>\n *\n * <div>\n * <img\n * src=\"https://www.wikipedia.org/portal/wikipedia.org/assets/img/Wikipedia-logo-v2@1.5x.png\"\n * />\n * </div>\n * </div>\n * ```\n *\n * @example\n * This defines a SameHeight widget with the flexbox children specified\n * explicitly (and one ignored), as well as having all custom settings.\n *\n * ```html\n * <div data-lisn-same-height=\"diff-tolerance=20\n * | resize-threshold=10\n * | debounce-window=50\n * | min-gap=50\n * | max-free-r=0.2\n * | max-width-r=3.2\">\n * <div>Example ignored child</div>\n *\n * <div data-lisn-same-height-item><!-- Will be detected as text anyway -->\n * <p>\n * Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do\n * eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad\n * minim veniam, quis nostrud exercitation ullamco laboris nisi ut\n * aliquip ex ea commodo consequat. Duis aute irure dolor in\n * reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla\n * pariatur. Excepteur sint occaecat cupidatat non proident, sunt in\n * culpa qui officia deserunt mollit anim id est laborum.\n * </p>\n * </div>\n *\n * <!-- Explicitly set to image type, though it will be detected as such -->\n * <div data-lisn-same-height-item=\"image\">\n * <img\n * src=\"https://www.wikipedia.org/portal/wikipedia.org/assets/img/Wikipedia-logo-v2@1.5x.png\"\n * />\n * </div>\n *\n * <!-- Explicitly set to text type, because it will NOT be detected as such (text too short). -->\n * <div data-lisn-same-height-item=\"text\">\n * <p>\n * Lorem ipsum dolor sit amet, consectetur adipiscing elit.\n * </p>\n * </div>\n * </div>\n * ```\n */\nexport class SameHeight extends Widget {\n /**\n * Switches the flexbox to vertical (column) mode.\n *\n * You can alternatively do this by setting the\n * `data-lisn-orientation=\"vertical\"` attribute on the element at any time.\n *\n * You can do this for example as part of a trigger:\n *\n * @example\n * ```html\n * <div class=\"lisn-same-height\"\n * data-lisn-on-layout=\"max-mobile-wide:set-attribute=data-lisn-orientation#vertical\">\n * <!-- ... children -->\n * </div>\n * ```\n */\n readonly toColumn: () => Promise<void>;\n\n /**\n * Switches the flexbox back to horizontal (row) mode, which is the default.\n *\n * You can alternatively do this by deleting the\n * `data-lisn-orientation` attribute on the element, or setting it to\n * `\"horizontal\"` at any time.\n */\n readonly toRow: () => Promise<void>;\n\n /**\n * Returns the elements used as the flex children.\n */\n readonly getItems: () => Element[];\n\n /**\n * Returns a map of the elements used as the flex children with their type.\n */\n readonly getItemConfigs: () => Map<Element, \"text\" | \"image\">;\n\n /**\n * If the element is already configured as a SameHeight widget, the widget\n * instance is returned. Otherwise null.\n */\n static get(containerElement: Element): SameHeight | null {\n const instance = super.get(containerElement, DUMMY_ID);\n if (MH.isInstanceOf(instance, SameHeight)) {\n return instance;\n }\n return null;\n }\n\n static register() {\n registerWidget(\n WIDGET_NAME,\n (element, config) => {\n if (MH.isHTMLElement(element)) {\n if (!SameHeight.get(element)) {\n return new SameHeight(element, config);\n }\n } else {\n logError(\n MH.usageError(\n \"Only HTMLElement is supported for SameHeight widget\",\n ),\n );\n }\n return null;\n },\n configValidator,\n );\n }\n\n constructor(containerElement: HTMLElement, config?: SameHeightConfig) {\n const destroyPromise = SameHeight.get(containerElement)?.destroy();\n super(containerElement, { id: DUMMY_ID });\n\n const items = getItemsFrom(containerElement, config?.items);\n\n if (MH.sizeOf(items) < 2) {\n throw MH.usageError(\"SameHeight must have more than 1 item\");\n }\n\n for (const item of items.keys()) {\n if (MH.parentOf(item) !== containerElement) {\n throw MH.usageError(\n \"SameHeight's items must be its immediate children\",\n );\n }\n }\n\n fetchConfig(containerElement, config).then((fullConfig) => {\n (destroyPromise || MH.promiseResolve()).then(() => {\n if (this.isDestroyed()) {\n return;\n }\n\n init(this, containerElement, items, fullConfig);\n });\n });\n\n this.toColumn = () =>\n setData(containerElement, MC.PREFIX_ORIENTATION, MC.S_VERTICAL);\n\n this.toRow = () => delData(containerElement, MC.PREFIX_ORIENTATION);\n\n this.getItems = () => [...items.keys()];\n this.getItemConfigs = () => MH.newMap([...items.entries()]);\n }\n}\n\n/**\n * @interface\n */\nexport type SameHeightConfig = {\n /**\n * The elements that will make up the items. They **MUST** be immediate\n * children of the container element.\n *\n * The widget should have more than one item.\n *\n * If this is not specified, then\n * 1. The immediate children of the top-level element that constitutes the\n * widget that have the `lisn-same-height-item` class or\n * `data-lisn-same-height-item` attribute are taken.\n * 2. If none of the root's children have this class or attribute, then all of\n * the immediate children of the widget element except any `script` or\n * `style` elements are taken as the items.\n */\n items?: Element[] | Map<Element, \"image\" | \"text\">;\n\n /**\n * After setting the flex basis of the children and their size updates, in\n * case the resultant height differs from the predicted calculated one by\n * `diffTolerance` in pixels, then the calculations are re-run using the new\n * sizes. Calculations are re-run at most once only.\n *\n * Differences between the predicted and resultant height would happen if the\n * children contain a mixture of text and images or if there are margins or\n * paddings that don't scale in the same way as the content.\n *\n * @defaultValue {@link settings.sameHeightDiffTolerance}\n */\n diffTolerance?: number;\n\n /**\n * The `resizeThreshold` to pass to the {@link SizeWatcher}.\n *\n * @defaultValue {@link settings.sameHeightResizeThreshold}\n */\n resizeThreshold?: number;\n\n /**\n * The `debounceWindow` to pass to the {@link SizeWatcher}.\n *\n * @defaultValue {@link settings.sameHeightDebounceWindow}\n */\n debounceWindow?: number;\n\n /**\n * Minimum gap between the flex items. Note that setting this to 0 while at\n * the same time setting `flex-wrap` to `wrap` (or `wrap-reverse`) on the\n * element may lead to premature/unnecessary wrapping.\n *\n * Note that this is not strictly enforced, and is only used in finding\n * optimal height based on other constraints. If you want to enforce this gap,\n * set it as a `column-gap` CSS rule.\n *\n * @defaultValue The effective `column-gap` on the container element style or\n * if none, {@link settings.sameHeightMinGap}\n */\n minGap?: number;\n\n /**\n * Maximum ratio between the free space in the flex container and its total\n * width. You can set this to a negative number to disable this restriction.\n *\n * It has to be < 1. Otherwise it is invalid and disables this restriction.\n *\n * Note that this is not strictly enforced, and is only used in finding\n * optimal height based on other constraints.\n *\n * @defaultValue {@link settings.sameHeightMaxFreeR}\n */\n maxFreeR?: number;\n\n /**\n * Maximum ratio between the width of the widest item and the narrowest item.\n * You can set this to 0 or a negative number to disable this restriction.\n *\n * It has to be >= 1. Otherwise it is invalid and disables this restriction.\n *\n * Note that this is not strictly enforced, and is only used in finding\n * optimal height based on other constraints.\n *\n * @defaultValue {@link settings.sameHeightMaxWidthR}\n */\n maxWidthR?: number;\n};\n\n// ------------------------------\n\ntype SameHeightConfigInternal = {\n _minGap: number;\n _diffTolerance: number;\n _resizeThreshold: number;\n _debounceWindow: number;\n _maxFreeR: number;\n _maxWidthR: number;\n};\n\ntype ItemProperties = {\n _type: \"\" | \"image\" | \"text\";\n _width: number;\n _height: number;\n _aspectR: number;\n _area: number;\n _extraH: number;\n _components: Element[];\n};\n\ntype AverageMeasurements = {\n _tArea: number;\n _tExtraH: number;\n _imgAR: number;\n _flexW: number;\n _nItems: number;\n};\n\nconst WIDGET_NAME = \"same-height\";\nconst PREFIXED_NAME = MH.prefixName(WIDGET_NAME);\nconst PREFIX_ROOT = `${PREFIXED_NAME}__root`;\n\n// Use different classes for styling items to the one used for auto-discovering\n// them, so that re-creating existing widgets can correctly find the items to\n// be used by the new widget synchronously before the current one is destroyed.\nconst PREFIX_ITEM = `${PREFIXED_NAME}__item`;\nconst PREFIX_ITEM__FOR_SELECT = `${PREFIXED_NAME}-item`;\n\nconst S_TEXT = \"text\";\nconst S_IMAGE = \"image\";\n\n// Only one SameHeight widget per element is allowed, but Widget requires a\n// non-blank ID.\nconst DUMMY_ID = PREFIXED_NAME;\n\n// We consider elements that have text content of at least <MIN_CHARS_FOR_TEXT>\n// characters to be text.\nconst MIN_CHARS_FOR_TEXT = 100;\n\nconst configValidator: WidgetConfigValidatorObject<SameHeightConfig> = {\n diffTolerance: validateNumber,\n resizeThreshold: validateNumber,\n [MC.S_DEBOUNCE_WINDOW]: validateNumber,\n minGap: validateNumber,\n maxFreeR: validateNumber,\n maxWidthR: validateNumber,\n};\n\nconst isText = (element: Element) =>\n getData(element, PREFIX_ITEM__FOR_SELECT) === S_TEXT ||\n (getData(element, PREFIX_ITEM__FOR_SELECT) !== S_IMAGE &&\n MH.isHTMLElement(element) &&\n MH.lengthOf(element.innerText) >= MIN_CHARS_FOR_TEXT);\n\nconst areImagesLoaded = (element: Element) => {\n for (const img of element.querySelectorAll(\"img\")) {\n // Don't rely on img.complete since sometimes this returns false even\n // though the image is loaded and has a size. Just check the size.\n if (\n img.naturalWidth === 0 ||\n img.width === 0 ||\n img.naturalHeight === 0 ||\n img.height === 0\n ) {\n return false;\n }\n }\n\n return true;\n};\n\nconst fetchConfig = async (\n containerElement: HTMLElement,\n userConfig: SameHeightConfig | undefined,\n): Promise<SameHeightConfigInternal> => {\n const colGapStr = await getComputedStyleProp(containerElement, \"column-gap\");\n const minGap = getNumValue(\n MH.strReplace(colGapStr, /px$/, \"\"),\n settings.sameHeightMinGap,\n );\n\n return {\n _minGap: toNumWithBounds(userConfig?.minGap ?? minGap, { min: 0 }, 10),\n _maxFreeR: toNumWithBounds(\n userConfig?.maxFreeR ?? settings.sameHeightMaxFreeR,\n { min: 0, max: 0.9 },\n -1,\n ),\n _maxWidthR: toNumWithBounds(\n userConfig?.maxWidthR ?? settings.sameHeightMaxWidthR,\n { min: 1 },\n -1,\n ),\n _diffTolerance:\n userConfig?.diffTolerance ?? settings.sameHeightDiffTolerance,\n _resizeThreshold:\n userConfig?.resizeThreshold ?? settings.sameHeightResizeThreshold,\n _debounceWindow:\n userConfig?.debounceWindow ?? settings.sameHeightDebounceWindow,\n };\n};\n\nconst getNumValue = (strValue: string | null, defaultValue: number): number => {\n const num = strValue ? MH.parseFloat(strValue) : NaN;\n return MH.isNaN(num) ? defaultValue : num;\n};\n\nconst findItems = (containerElement: HTMLElement) => {\n const items = [\n ...MH.querySelectorAll(\n containerElement,\n getDefaultWidgetSelector(PREFIX_ITEM__FOR_SELECT),\n ),\n ];\n\n if (!MH.lengthOf(items)) {\n items.push(...getVisibleContentChildren(containerElement));\n }\n\n return items;\n};\n\nconst getItemsFrom = (\n containerElement: HTMLElement,\n inputItems: Element[] | Map<Element, \"image\" | \"text\"> | undefined,\n) => {\n const itemMap = MH.newMap<Element, \"image\" | \"text\">();\n\n inputItems = inputItems || findItems(containerElement);\n\n const addItem = (item: Element, itemType?: \"text\" | \"image\") => {\n itemType = itemType || (isText(item) ? S_TEXT : S_IMAGE);\n itemMap.set(item, itemType);\n };\n\n if (MH.isArray(inputItems)) {\n for (const item of inputItems) {\n addItem(item);\n }\n } else if (MH.isInstanceOf(inputItems, Map)) {\n for (const [item, itemType] of inputItems.entries()) {\n addItem(item, itemType);\n }\n }\n\n return itemMap;\n};\n\nconst init = (\n widget: SameHeight,\n containerElement: HTMLElement,\n items: Map<Element, \"image\" | \"text\">,\n config: SameHeightConfigInternal,\n) => {\n const logger = debug\n ? new debug.Logger({\n name: `SameHeight-${formatAsString(containerElement)}`,\n })\n : null;\n\n const diffTolerance = config._diffTolerance;\n const debounceWindow = config._debounceWindow;\n\n const sizeWatcher = SizeWatcher.reuse({\n [MC.S_DEBOUNCE_WINDOW]: debounceWindow,\n resizeThreshold: config._resizeThreshold,\n });\n\n const allItems = MH.newMap<Element, ItemProperties>();\n\n let callCounter = 0;\n let isFirstTime = true;\n let lastOptimalHeight = 0;\n let hasScheduledReset = false;\n let counterTimeout: ReturnType<typeof setTimeout> | null = null;\n\n // ----------\n\n const resizeHandler = (element: Element, sizeData: SizeData) => {\n // Since the SizeWatcher calls us once for every element, we batch the\n // re-calculations so they are done once in every cycle.\n // Allow the queue of ResizeObserverEntry in the SizeWatcher to be\n // emptied, and therefore to ensure we have the latest size for all\n // elements.\n if (!hasScheduledReset) {\n debug: logger?.debug7(\"Scheduling calculations\", callCounter);\n hasScheduledReset = true;\n\n MH.setTimer(() => {\n hasScheduledReset = false;\n\n if (callCounter > 1) {\n debug: logger?.debug7(\"Already re-calculated once, skipping\");\n callCounter = 0;\n return;\n }\n\n callCounter++;\n if (counterTimeout) {\n MH.clearTimer(counterTimeout);\n }\n\n const measurements = calculateMeasurements(\n containerElement,\n allItems,\n isFirstTime,\n logger,\n );\n\n const height = measurements\n ? getOptimalHeight(measurements, config, logger)\n : null;\n\n if (height && MH.abs(lastOptimalHeight - height) > diffTolerance) {\n // Re-set widths again. We may be called again in the next cycle if\n // the change in size exceeds the resizeThreshold.\n lastOptimalHeight = height;\n isFirstTime = false;\n setWidths(height); // no need to await\n\n // If we are _not_ called again in the next cycle (just after\n // debounceWindow), then reset the counter. It means the resultant\n // change in size did not exceed the SizeWatcher threshold.\n counterTimeout = MH.setTimer(() => {\n callCounter = 0;\n }, debounceWindow + 50);\n } else {\n // Done, until the next time elements are resized\n callCounter = 0;\n }\n }, 0);\n }\n\n // Save the size of the item\n const properties = allItems.get(element);\n if (!properties) {\n logError(MH.bugError(\"Got SizeWatcher call for unknown element\"));\n return;\n }\n\n properties._width =\n sizeData.border[MC.S_WIDTH] || sizeData.content[MC.S_WIDTH];\n properties._height =\n sizeData.border[MC.S_HEIGHT] || sizeData.content[MC.S_HEIGHT];\n\n debug: logger?.debug7(\"Got size\", element, properties);\n };\n\n // ----------\n\n const observeAll = () => {\n isFirstTime = true;\n\n for (const element of allItems.keys()) {\n sizeWatcher.onResize(resizeHandler, { target: element });\n }\n };\n\n // ----------\n\n const unobserveAll = () => {\n for (const element of allItems.keys()) {\n sizeWatcher.offResize(resizeHandler, element);\n }\n };\n\n // ----------\n\n const setWidths = (height: number) => {\n for (const [element, properties] of allItems.entries()) {\n if (MH.parentOf(element) === containerElement) {\n const width = getWidthAtH(element, properties, height);\n debug: logger?.debug9(\n \"Setting width property\",\n element,\n properties,\n width,\n );\n setNumericStyleJsVars(\n element,\n { sameHeightW: width },\n { _units: \"px\" },\n );\n }\n }\n };\n\n // SETUP ------------------------------\n\n widget.onDisable(unobserveAll);\n widget.onEnable(observeAll);\n\n widget.onDestroy(async () => {\n for (const element of allItems.keys()) {\n if (MH.parentOf(element) === containerElement) {\n // delete the property and attribute\n await setNumericStyleJsVars(element, { sameHeightW: NaN });\n await removeClasses(element, PREFIX_ITEM);\n }\n }\n\n allItems.clear();\n\n await removeClasses(containerElement, PREFIX_ROOT);\n });\n\n // Find all relevant items: the container, its direct children and the\n // top-level text only elements.\n const getProperties = (itemType: \"\" | \"image\" | \"text\"): ItemProperties => {\n return {\n _type: itemType,\n _width: NaN,\n _height: NaN,\n _aspectR: NaN,\n _area: NaN,\n _extraH: NaN,\n _components: [],\n };\n };\n\n allItems.set(containerElement, getProperties(\"\"));\n\n for (const [item, itemType] of items.entries()) {\n addClasses(item, PREFIX_ITEM);\n\n const properties: ItemProperties = getProperties(itemType);\n allItems.set(item, properties);\n\n if (itemType === S_TEXT) {\n properties._components = getTextComponents(item);\n for (const child of properties._components) {\n allItems.set(child, getProperties(\"\"));\n }\n }\n }\n\n addClasses(containerElement, PREFIX_ROOT);\n observeAll();\n};\n\n/**\n * Find the top-level text-only elements that are descendants of the given one.\n */\nconst getTextComponents = (element: Element): Element[] => {\n const components: Element[] = [];\n for (const child of getVisibleContentChildren(element)) {\n if (isText(child)) {\n components.push(child);\n } else {\n components.push(...getTextComponents(child));\n }\n }\n\n return components;\n};\n\nconst calculateMeasurements = (\n containerElement: HTMLElement,\n allItems: Map<Element, ItemProperties>,\n isFirstTime: boolean,\n logger: LoggerInterface | null,\n): AverageMeasurements | null => {\n if (getData(containerElement, MC.PREFIX_ORIENTATION) === MC.S_VERTICAL) {\n debug: logger?.debug8(\"In vertical mode\");\n return null;\n }\n\n debug: logger?.debug7(\"Calculating measurements\");\n // initial values\n let tArea = NaN,\n tExtraH = 0,\n imgAR = NaN,\n flexW = NaN,\n nItems = 0;\n\n for (const [element, properties] of allItems.entries()) {\n const width = properties._width;\n const height = properties._height;\n\n if (element === containerElement) {\n flexW = width;\n nItems = MH.lengthOf(getVisibleContentChildren(element));\n\n //\n } else if (properties._type === S_TEXT) {\n let thisTxtArea = 0,\n thisTxtExtraH = 0;\n const components = properties._components;\n\n if (MH.lengthOf(components)) {\n for (const component of properties._components) {\n const cmpProps = allItems.get(component);\n if (cmpProps) {\n thisTxtArea += cmpProps._width * cmpProps._height;\n } else {\n logError(MH.bugError(\"Text component not observed\"));\n }\n }\n thisTxtExtraH = height - thisTxtArea / width;\n } else {\n thisTxtArea = width * height;\n }\n\n properties._area = thisTxtArea;\n properties._extraH = thisTxtExtraH;\n\n tArea = (tArea || 0) + thisTxtArea;\n tExtraH += thisTxtExtraH;\n\n //\n } else if (properties._type === S_IMAGE) {\n if (isFirstTime && !areImagesLoaded(element)) {\n debug: logger?.debug8(\"Images not loaded\");\n return null;\n }\n\n const thisAspectR = width / height;\n imgAR = (imgAR || 0) + thisAspectR;\n properties._aspectR = thisAspectR;\n\n //\n } else {\n // skip grandchildren (text components), here\n continue;\n }\n\n debug: logger?.debug8(\"Examined\", properties, {\n tArea,\n tExtraH,\n imgAR,\n flexW,\n });\n }\n\n return {\n _tArea: tArea,\n _tExtraH: tExtraH,\n _imgAR: imgAR,\n _flexW: flexW,\n _nItems: nItems,\n };\n};\n\nconst getWidthAtH = (\n element: Element,\n properties: ItemProperties,\n targetHeight: number,\n): number =>\n properties._type === S_TEXT\n ? properties._area / (targetHeight - (properties._extraH || 0))\n : properties._aspectR * targetHeight;\n\nconst getOptimalHeight = (\n measurements: AverageMeasurements,\n config: SameHeightConfigInternal,\n logger: LoggerInterface | null,\n) => {\n const tArea = measurements._tArea;\n const tExtraH = measurements._tExtraH;\n const imgAR = measurements._imgAR;\n const flexW =\n measurements._flexW - (measurements._nItems - 1) * config._minGap;\n const maxFreeR = config._maxFreeR;\n const maxWidthR = config._maxWidthR;\n\n debug: logger?.debug8(\"Getting optimal height\", measurements, config);\n\n // CASE 1: No text items\n if (MH.isNaN(tArea)) {\n debug: logger?.debug8(\"No text items\");\n if (!imgAR) {\n debug: logger?.debug8(\"Images not loaded\");\n return NaN;\n }\n\n return flexW / imgAR;\n }\n\n // CASE 2: No images\n if (MH.isNaN(imgAR)) {\n debug: logger?.debug8(\"No images\");\n return tArea / flexW + tExtraH;\n }\n\n if (!imgAR || !tArea) {\n debug: logger?.debug8(\n \"Expected both images and text, but no imgAR or tArea\",\n );\n return NaN;\n }\n\n const h0 = MH.sqrt(tArea / imgAR) + tExtraH;\n\n // heights satisfying w(h) === flexW\n const [h2, h1] = quadraticRoots(\n imgAR,\n -(imgAR * tExtraH + flexW),\n tArea + tExtraH * flexW,\n );\n\n // heights satisfying maxWidthR\n let hR0 = NaN,\n hR1 = NaN,\n hR2 = NaN;\n if (maxWidthR > 0) {\n hR0 = quadraticRoots(imgAR, -imgAR * tExtraH, -tArea)[0];\n\n hR1 = quadraticRoots(\n imgAR * maxWidthR,\n\n -imgAR * tExtraH * maxWidthR,\n -tArea,\n )[0];\n\n hR2 = quadraticRoots(\n imgAR / maxWidthR,\n (-imgAR * tExtraH) / maxWidthR,\n -tArea,\n )[0];\n }\n\n // heights satisfying maxFreeR\n let hF2 = NaN,\n hF1 = NaN;\n if (maxFreeR >= 0) {\n [hF2, hF1] = quadraticRoots(\n imgAR,\n -(imgAR * tExtraH + flexW * (1 - maxFreeR)),\n tArea + tExtraH * flexW * (1 - maxFreeR),\n );\n }\n\n // limits on constraints\n const hConstr1 = MH.max(...MH.filter([h1, hR1, hF1], (v) => isValidNum(v)));\n const hConstr2 = MH.min(...MH.filter([h2, hR2, hF2], (v) => isValidNum(v)));\n\n // text and image widths at h0\n const tw0 = tArea / (h0 - tExtraH);\n const iw0 = h0 * imgAR;\n\n // free space at h0\n const freeSpace0 = flexW - tw0 - iw0;\n\n debug: logger?.debug8(\"Optimal height calculations\", config, measurements, {\n h0,\n h1,\n h2,\n hR0,\n hR1,\n hR2,\n hF1,\n hF2,\n hConstr1,\n hConstr2,\n tw0,\n iw0,\n freeSpace0,\n });\n\n // ~~~~ Some sanity checks\n // If any of then is NaN, the comparison would be false, so we don't need to\n // check.\n // Also, we round the difference to 0.1 pixels to account for rounding\n // errors during calculations.\n if (!h0 || h0 <= 0) {\n debug: logger?.debug1(\"Invalid calculation: Invalid h0\");\n } else if (isValidNum(h1) !== isValidNum(h2)) {\n debug: logger?.debug1(\n \"Invalid calculation: One and only one of h1 or h2 is real\",\n );\n } else if (isValidNum(hR1) !== isValidNum(hR2)) {\n debug: logger?.debug1(\n \"Invalid calculation: One and only one of hR1 or hR2 is real\",\n );\n } else if (isValidNum(hF1) !== isValidNum(hF2)) {\n debug: logger?.debug1(\n \"Invalid calculation: One and only one of hF1 or hF2 is real\",\n );\n } else if (h1 - h0 > 0.1) {\n debug: logger?.debug1(\"Invalid calculation: h1 > h0\");\n } else if (h0 - h2 > 0.1) {\n debug: logger?.debug1(\"Invalid calculation: h0 > h2\");\n } else if (hR0 - h0 > 0.1) {\n debug: logger?.debug1(\"Invalid calculation: hR0 > h0\");\n } else if (hR1 - hR0 > 0.1) {\n debug: logger?.debug1(\"Invalid calculation: hR1 > hR0\");\n } else if (hR0 - hR2 > 0.1) {\n debug: logger?.debug1(\"Invalid calculation: hR0 > hR2\");\n } else if (hF1 - hF2 > 0.1) {\n debug: logger?.debug1(\"Invalid calculation: hF1 > hF2\");\n } else if (h1 - hF1 > 0.1) {\n debug: logger?.debug1(\"Invalid calculation: h1 > hF1\");\n } else if (hF2 - h2 > 0.1) {\n debug: logger?.debug1(\"Invalid calculation: hF2 > h2\");\n } else {\n // Choose a height\n if (freeSpace0 <= 0) {\n // scenario 1 or 2\n return h0;\n } else {\n // scenario 3\n return MH.min(hConstr1, hConstr2);\n }\n }\n\n logError(\n MH.bugError(\"Invalid SameHeight calculations\"),\n measurements,\n config,\n );\n return NaN; // sanity checks failed\n};\n"],"mappings":";;;;;;AAyUA,IAAAA,EAAA,GAAAC,uBAAA,CAAAC,OAAA;AACA,IAAAC,EAAA,GAAAF,uBAAA,CAAAC,OAAA;AAEA,IAAAE,SAAA,GAAAF,OAAA;AAEA,IAAAG,SAAA,GAAAH,OAAA;AASA,IAAAI,SAAA,GAAAJ,OAAA;AACA,IAAAK,IAAA,GAAAL,OAAA;AACA,IAAAM,KAAA,GAAAN,OAAA;AACA,IAAAO,KAAA,GAAAP,OAAA;AACA,IAAAQ,WAAA,GAAAR,OAAA;AAEA,IAAAS,YAAA,GAAAT,OAAA;AAEA,IAAAU,OAAA,GAAAV,OAAA;AASA,IAAAW,MAAA,GAAAC,sBAAA,CAAAZ,OAAA;AAAsC,SAAAY,uBAAAC,CAAA,WAAAA,CAAA,IAAAA,CAAA,CAAAC,UAAA,GAAAD,CAAA,KAAAE,OAAA,EAAAF,CAAA;AAAA,SAAAd,wBAAAc,CAAA,EAAAG,CAAA,6BAAAC,OAAA,MAAAC,CAAA,OAAAD,OAAA,IAAAE,CAAA,OAAAF,OAAA,YAAAlB,uBAAA,YAAAA,CAAAc,CAAA,EAAAG,CAAA,SAAAA,CAAA,IAAAH,CAAA,IAAAA,CAAA,CAAAC,UAAA,SAAAD,CAAA,MAAAO,CAAA,EAAAC,CAAA,EAAAC,CAAA,KAAAC,SAAA,QAAAR,OAAA,EAAAF,CAAA,iBAAAA,CAAA,uBAAAA,CAAA,yBAAAA,CAAA,SAAAS,CAAA,MAAAF,CAAA,GAAAJ,CAAA,GAAAG,CAAA,GAAAD,CAAA,QAAAE,CAAA,CAAAI,GAAA,CAAAX,CAAA,UAAAO,CAAA,CAAAK,GAAA,CAAAZ,CAAA,GAAAO,CAAA,CAAAM,GAAA,CAAAb,CAAA,EAAAS,CAAA,gBAAAN,CAAA,IAAAH,CAAA,gBAAAG,CAAA,OAAAW,cAAA,CAAAC,IAAA,CAAAf,CAAA,EAAAG,CAAA,OAAAK,CAAA,IAAAD,CAAA,GAAAS,MAAA,CAAAC,cAAA,KAAAD,MAAA,CAAAE,wBAAA,CAAAlB,CAAA,EAAAG,CAAA,OAAAK,CAAA,CAAAI,GAAA,IAAAJ,CAAA,CAAAK,GAAA,IAAAN,CAAA,CAAAE,CAAA,EAAAN,CAAA,EAAAK,CAAA,IAAAC,CAAA,CAAAN,CAAA,IAAAH,CAAA,CAAAG,CAAA,WAAAM,CAAA,KAAAT,CAAA,EAAAG,CAAA;AAAA,SAAAgB,gBAAAnB,CAAA,EAAAK,CAAA,EAAAF,CAAA,YAAAE,CAAA,GAAAe,cAAA,CAAAf,CAAA,MAAAL,CAAA,GAAAgB,MAAA,CAAAC,cAAA,CAAAjB,CAAA,EAAAK,CAAA,IAAAgB,KAAA,EAAAlB,CAAA,EAAAmB,UAAA,MAAAC,YAAA,MAAAC,QAAA,UAAAxB,CAAA,CAAAK,CAAA,IAAAF,CAAA,EAAAH,CAAA;AAAA,SAAAoB,eAAAjB,CAAA,QAAAK,CAAA,GAAAiB,YAAA,CAAAtB,CAAA,uCAAAK,CAAA,GAAAA,CAAA,GAAAA,CAAA;AAAA,SAAAiB,aAAAtB,CAAA,EAAAE,CAAA,2BAAAF,CAAA,KAAAA,CAAA,SAAAA,CAAA,MAAAH,CAAA,GAAAG,CAAA,CAAAuB,MAAA,CAAAC,WAAA,kBAAA3B,CAAA,QAAAQ,CAAA,GAAAR,CAAA,CAAAe,IAAA,CAAAZ,CAAA,EAAAE,CAAA,uCAAAG,CAAA,SAAAA,CAAA,YAAAoB,SAAA,yEAAAvB,CAAA,GAAAwB,MAAA,GAAAC,MAAA,EAAA3B,CAAA,KAxWtC;AACA;AACA,GAFA,CAIA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA