UNPKG

@ulu/frontend

Version:

A versatile SCSS and JavaScript component library offering configurable, accessible components and flexible integration into any project, with SCSS modules suitable for modern JS frameworks.

1,491 lines 786 kB
var __defProp = Object.defineProperty; var __defNormalProp = (obj, key2, value) => key2 in obj ? __defProp(obj, key2, { enumerable: true, configurable: true, writable: true, value }) : obj[key2] = value; var __publicField = (obj, key2, value) => __defNormalProp(obj, typeof key2 !== "symbol" ? key2 + "" : key2, value); const scriptRel = "modulepreload"; const assetsURL = function(dep, importerUrl) { return new URL(dep, importerUrl).href; }; const seen = {}; const __vitePreload = function preload(baseModule, deps, importerUrl) { let promise = Promise.resolve(); if (deps && deps.length > 0) { const links = document.getElementsByTagName("link"); const cspNonceMeta = document.querySelector( "meta[property=csp-nonce]" ); const cspNonce = (cspNonceMeta == null ? void 0 : cspNonceMeta.nonce) || (cspNonceMeta == null ? void 0 : cspNonceMeta.getAttribute("nonce")); promise = Promise.allSettled( deps.map((dep) => { dep = assetsURL(dep, importerUrl); if (dep in seen) return; seen[dep] = true; const isCss = dep.endsWith(".css"); const cssSelector = isCss ? '[rel="stylesheet"]' : ""; const isBaseRelative = !!importerUrl; if (isBaseRelative) { for (let i = links.length - 1; i >= 0; i--) { const link2 = links[i]; if (link2.href === dep && (!isCss || link2.rel === "stylesheet")) { return; } } } else if (document.querySelector(`link[href="${dep}"]${cssSelector}`)) { return; } const link = document.createElement("link"); link.rel = isCss ? "stylesheet" : scriptRel; if (!isCss) { link.as = "script"; } link.crossOrigin = ""; link.href = dep; if (cspNonce) { link.setAttribute("nonce", cspNonce); } document.head.appendChild(link); if (isCss) { return new Promise((res, rej) => { link.addEventListener("load", res); link.addEventListener( "error", () => rej(new Error(`Unable to preload CSS for ${dep}`)) ); }); } }) ); } function handlePreloadError(err) { const e = new Event("vite:preloadError", { cancelable: true }); e.payload = err; window.dispatchEvent(e); if (!e.defaultPrevented) { throw err; } } return promise.then((res) => { for (const item of res || []) { if (item.status !== "rejected") continue; handlePreloadError(item.reason); } return baseModule().catch(handlePreloadError); }); }; const defaults$b = { iconClassClose: "css-icon css-icon--close", iconClassDragX: "css-icon css-icon--drag-x", iconClassPrevious: "css-icon css-icon--angle-left", iconClassNext: "css-icon css-icon--angle-right" }; let currentSettings = { ...defaults$b }; function getDefaultSettings() { return { ...defaults$b }; } function updateSettings(changes) { Object.assign(currentSettings, changes); } function getSettings() { return { ...currentSettings }; } function getSetting(key2) { if (!currentSettings.hasOwnProperty(key2)) { console.warn(`Attempted to access non-existent setting: ${key2}`); return void 0; } return currentSettings[key2]; } function updateSetting(key2, value) { currentSettings[key2] = value; } function wrapSettingString(key2) { return { toString() { return getSetting(key2); } }; } const settings = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, getDefaultSettings, getSetting, getSettings, updateSetting, updateSettings, wrapSettingString }, Symbol.toStringTag, { value: "Module" })); function debounce(callback, wait, immediate, valueThis) { var timeout; return function executedFunction() { var context = this; var args = arguments; var later = function() { timeout = null; callback.apply(context, args); }; clearTimeout(timeout); timeout = setTimeout(later, wait); }; } function isBrowser() { return typeof window !== "undefined" && typeof window.document !== "undefined"; } function createElementFromHtml(markup) { const doc = new DOMParser().parseFromString(markup, "text/html"); return doc.body.firstElementChild; } if (isBrowser()) { initResize(); initPrint(); } const events = { /** * Event is dispatched when DOM in the page has changed, triggers updates from * all modules listening for the change (init instances, etc) * - Is triggered by modules that were responsible for modifying the page */ pageModified(context) { context.dispatchEvent(new CustomEvent(getName$1("pageModified"), { bubbles: true })); }, /** * Event called when page is resized */ pageResized(context) { context.dispatchEvent(new CustomEvent(getName$1("pageResized"), { bubbles: true })); }, /** * Event dispatched before page print begins (teardown/restructure/hide things) */ beforePrint(context) { context.dispatchEvent(new CustomEvent(getName$1("beforePrint"), { bubbles: true })); }, /** * Event dispatched after page print (cleanup) */ afterPrint(context) { context.dispatchEvent(new CustomEvent(getName$1("afterPrint"), { bubbles: true })); } }; function dispatch(type, context) { if (events[type]) { events[type](context); } else { console.warn(`Unable to dispatch site event: ${type} in context:`, context); } } function getName$1(type) { return "ulu:" + type; } function initResize() { window.addEventListener("resize", debounce(() => dispatch("pageResized", document), 250)); } function initPrint() { window.addEventListener("beforeprint", () => { dispatch("beforePrint", document); }); window.addEventListener("afterprint", () => { dispatch("afterPrint", document); }); } const index$2 = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, dispatch, getName: getName$1 }, Symbol.toStringTag, { value: "Module" })); const regexJsonString = /^[{\[][\s\S]*[}\]]$/; function getDatasetJson(element, key2) { const passed = element.dataset[key2]; try { return JSON.parse(passed); } catch (error) { console.error(`Error getting JSON from dataset (${key2}) -- "${passed}" `, element, error); return {}; } } function getDatasetOptionalJson(element, key2) { const passed = element.dataset[key2]; if (passed && regexJsonString.test(passed.trim())) { return getDatasetJson(element, key2); } else { return passed; } } function wasClickOutside(element, event) { const rect = element.getBoundingClientRect(); return event.clientY < rect.top || // above event.clientY > rect.top + rect.height || // below event.clientX < rect.left || // left side event.clientX > rect.left + rect.width; } function setPositionClasses(parent, classes = { columnFirst: "position-column-first", columnLast: "position-column-last", rowFirst: "position-row-first", rowLast: "position-row-last" }) { const children = [...parent.children]; const rows = []; let lastY; children.forEach((child) => { const y = child.getBoundingClientRect().y; if (lastY !== y) rows.push([]); rows[rows.length - 1].push(child); lastY = y; child.classList.remove(...Object.values(classes)); }); rows.forEach((row, index2) => { if (index2 === 0) row.forEach((child) => child.classList.add(classes.rowFirst)); if (index2 == rows.length - 1) row.forEach((child) => child.classList.add(classes.rowLast)); row.forEach((child, childIndex) => { if (childIndex === 0) child.classList.add(classes.columnFirst); if (childIndex == row.length - 1) child.classList.add(classes.columnLast); }); }); } function getElement(target, context = document) { if (typeof target === "string") { return context.querySelector(target); } else if (target instanceof Element) { return target; } else { console.warn("getElement: Invalid target type (expected String/Node)", target); return null; } } function getElements(target, context = document) { if (typeof target === "string") { return [...context.querySelectorAll(target)]; } else if (target instanceof Element) { return [target]; } else if (Array.isArray(target) || target instanceof NodeList) { return [...target]; } else { console.warn("getElement: Invalid target type (expected String/Node/Array/Node List)", target); return null; } } function resolveClasses(classes) { if (typeof classes === "string") { return classes.split(" ").filter((c) => c !== ""); } else if (Array.isArray(classes)) { return classes; } else if (!classes) { return []; } else { console.warn("resolveClassArray: Invalid class input type.", classes); return []; } } function addScrollbarProperty(element = document.body, container2 = window, propName = "--ulu-scrollbar-width") { const scrollbarWidth = container2.innerWidth - element.clientWidth; element.style.setProperty(propName, `${scrollbarWidth}px`); } const dom = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, addScrollbarProperty, getDatasetJson, getDatasetOptionalJson, getElement, getElements, regexJsonString, resolveClasses, setPositionClasses, wasClickOutside }, Symbol.toStringTag, { value: "Module" })); function init$h() { addScrollbarProperty(); } const page = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, init: init$h }, Symbol.toStringTag, { value: "Module" })); function removeArrayElement(array, element) { var index2 = array.indexOf(element); if (index2 > -1) { array.splice(index2, 1); } } const config$1 = { debug: false, warningsAlways: true, errorsAlways: true, outputContext: false }; const hasConsole = "console" in window; function allow(context) { return hasConsole && config$1.debug && ((context == null ? void 0 : context.debug) || context == null); } function getName(context) { var _a; return typeof context === "object" && ((_a = context == null ? void 0 : context.constructor) == null ? void 0 : _a.name); } function output(method, context, messages) { const label = getName(context) || "Logger"; console[method](label, ...messages); if (config$1.outputContext) { console.log("Context:\n", context); } } function set(changes) { Object.assign(config$1, changes); } function log(context, ...messages) { if (allow(context)) { output("log", context, messages); } } function logWarning(context, ...messages) { if (config$1.warningsAlways || allow(context)) { output("warn", context, messages); } } function logError$1(context, ...messages) { if (config$1.errorsAlways || allow(context)) { output("error", context, messages); } } const classLogger = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, log, logError: logError$1, logWarning, set }, Symbol.toStringTag, { value: "Module" })); window.addEventListener(getName$1("pageResized"), () => { BreakpointManager.instances.forEach((i) => i.update()); }); const _BreakpointManager = class _BreakpointManager { /** * @param {Object} config Configruation object * @param {Array} config.order Array of strings that correspond to the breakpoints setup in the styles, Breakpoints from smallest to largest, defaults to [small, medium, large] * @param {Array} config.customProperty Property to grab breakpoint from (default is --breakpoint) * @param {Array} config.valueFromPsuedo Use the legacy method of grabbing breakpoint from psuedo element, default uses custom property * @param {Node} config.element The element to retrieve active breakpoint from stylesheet. (default is html) For using the old psuedo method, adjust this to document.body * @param {String} config.psuedoSelector Change psuedo selector used to get the breakpoint from the psuedo's content property */ constructor(config2) { Object.assign(this, _BreakpointManager.defaults, config2); this.active = null; this.previous = null; this.activeIndex = null; this.resizeDirection = null; this.previousIndex = null; this.breakpoints = {}; this.onChangeCallbacks = []; this.order.forEach((n) => this.breakpoints[n] = new Breakpoint(n, this)); log(this, this); this.update(); _BreakpointManager.instances.push(this); } /** * Add a callback for everytime a breakpoint changes * - Not recommended, possibly use to watch for changes, etc * - For more control use intance.at(name) with breakpoint methods * @param {Function} callback Function to call, passed one argument current instance which can be used to get information about breakpoints */ onChange(callback) { this.onChangeCallbacks.push(callback); } /** * Remove change callback * @param {Function} callback Function to remove */ removeOnChange(callback) { removeArrayElement(this.onChangeCallbacks, callback); } /** * Get breakpoint from a psuedo element */ getBreakpointInPsuedo() { return window.getComputedStyle(this.element, this.psuedoSelector).content.replace(/^"|"$/g, ""); } /** * Get breakpoint from a custom property */ getBreakpointInProperty() { return getComputedStyle(this.element).getPropertyValue(this.customProperty).trim(); } /** * Get breakpoint from element (design note: user could override prototype) */ getBreakpoint() { if (this.valueFromPsuedo) { return this.getBreakpointInPsuedo(); } else { return this.getBreakpointInProperty(); } } /** * Updates the active breakpoint by checking the element and executes handlers on change */ update() { const name = this.getBreakpoint(); if (!name) { logError$1(this, "Unable to get current breakpoint, maybe order is incorrect? Breakpoint check skipped!"); return; } if (name === this.active) return; this.previous = this.active; this.previousIndex = this.activeIndex; const index2 = this.order.indexOf(name); this.active = name; this.activeIndex = index2; const activeBreakpoint = this.at(this.active); const mapBreakpoints = (n) => this.at(n); const max2 = this.order.slice(index2).map(mapBreakpoints); const notMax = this.order.slice(0, index2).map(mapBreakpoints); const min2 = this.order.slice(0, index2 + 1).map(mapBreakpoints); const notMin = this.order.slice(index2 + 1).map(mapBreakpoints); const notOnly = this.order.slice().map(mapBreakpoints); notOnly.splice(index2, 1); log(this, "max:", max2.map((b) => b.name).join()); log(this, "min:", min2.map((b) => b.name).join()); max2.forEach((b) => b._setDirection("max", true)); min2.forEach((b) => b._setDirection("min", true)); activeBreakpoint._setDirection("only", true); notMax.forEach((b) => b._setDirection("max", false)); notMin.forEach((b) => b._setDirection("min", false)); notOnly.forEach((b) => b._setDirection("only", false)); if (this.previousIndex !== null) { this.resizeDirection = this.previousIndex < index2 ? "up" : "down"; } this.onChangeCallbacks.forEach((cb) => cb(this)); } /** * Get a breakpoint by key * @param {String} name The name of the breakpoint to get * @return {Breakpoint} Breakpoint to act on (see Breakpoint class) */ at(name) { const bp = this.breakpoints[name]; if (!name) { logError$1(this, "Unable to find breakpoint for:", bp); } return bp; } }; __publicField(_BreakpointManager, "instances", []); __publicField(_BreakpointManager, "defaults", { element: document == null ? void 0 : document.documentElement, valueFromPsuedo: false, customProperty: "--breakpoint", psuedoSelector: ":before", order: ["none", "small", "medium", "large"], debug: false }); let BreakpointManager = _BreakpointManager; class BreakpointDirection { constructor(direction, breakpoint) { this.direction = direction; this.active = false; this.on = []; this.off = []; this.breakpoint = breakpoint; } /** * Change the state of the direction */ change(to) { if (this.active !== to) { if (to) this._call(true); else if (this.active) this._call(false); this.active = to; } } /** * Calls all functions in handlers or */ _call(forActive) { const handlers = forActive ? this.on : this.off; handlers.forEach((handler) => handler()); log(this.breakpoint._manager, `Handlers called (${forActive ? "on" : "off"}): ${this.direction}`); } /** * Returns handlers in normalized object format on/off */ getHandlers(handler) { return typeof handler !== "object" ? { on: handler } : handler; } /** * Adds a handler for the direction, optionally use object to add off state handler * @param {Function|Object} handler Function to be executed when direction is active, read object description for on/off * @param {Function|Object} handler.on Function to be executed when direction is active * @param {Function|Object} handler.off Function to be executed when direction was active and is now changed to inactive */ add(handler) { const handlers = this.getHandlers(handler); if (handlers.on) { this.on.push(handlers.on); } if (handlers.off) { this.off.push(handlers.off); } if (this.active && handlers.on) { handlers.on(); log(this.breakpoint._manager, `Handler called immediately: ${this.direction}`, handlers.on); } } /** * Removes a handler */ remove(handler) { const handlers = this.getHandlers(handler); if (handlers.on) { removeArrayElement(this.on, handlers.on); } if (handlers.off) { removeArrayElement(this.off, handlers.off); } } } class Breakpoint { constructor(name, manager) { this.directions = { max: new BreakpointDirection("max", this), min: new BreakpointDirection("min", this), only: new BreakpointDirection("only", this) }; this._manager = manager; this.name = name; } /** * Private method used inrternally for managing direction activation * - Each direction manages it's own state and handlers * @param {String} direction The directional key * @param {Boolean} active State of that direction to set */ _setDirection(direction, active) { this.directions[direction].change(active); } /** * Attach handler to be executed from the breakpoint and to all breakpoints below. * - If the browser resizes from a breakpoint below this breakpoint, * and above the breakpoint name specified, this handler will fire * @param {Function} handler Handler to be executed */ max(handler) { this.directions.max.add(handler); } /** * Attach handler to be executed from the breakpoint and to all breakpoints below. * - If the browser resizes from a breakpoint above this breakpoint, * and below the breakpoint name specified, this handler will fire * @param {Function} handler Handler to be executed */ min(handler) { this.directions.min.add(handler); } /** * Attach a handler to fire when the breakpoint is within the key * @param {Function} handler Handler to be executed */ only(handler) { this.directions.only.add(handler); } /** * Remove handler * @param {Function} handler Handler to be removed, extended on/off object style can be used * @param {String} direction Remove handler only from specified direction, else search all directions */ remove(handler, direction) { const directions = direction ? [direction] : ["max", "min", "only"]; directions.forEach((d) => d.remove(handler)); } log(...msg) { msg.unshift(`Breakpoint (${this.name}):`); this._manager.log.apply(this._manager, msg); } } const breakpoints = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, BreakpointManager }, Symbol.toStringTag, { value: "Module" })); let idCount = 0; function newId() { return `ulu-uid-${++idCount}`; } function ensureId(element) { if (!element.id) { element.id = newId(); } } const id = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, ensureId, newId }, Symbol.toStringTag, { value: "Module" })); const _Collapsible = class _Collapsible { /** * @param {Object} elements Elements object * @param {Node} elements.trigger Trigger button/element that opens/closes collapsible * @param {Node} elements.content The content element that the trigger reveals * @param {Object} config Configuration options (see defaults) * @returns {Object} Collapsible instance */ constructor(elements, config2) { const { trigger, content } = elements; if (!trigger || !content) { logError$1(this, "missing required elements (trigger or content)"); return; } const options = Object.assign({}, _Collapsible.defaults, config2); this.elements = elements; this.options = options; this.isOpen = false; this.handlers = {}; ensureId(trigger); ensureId(content); this.debugLog(this, this); if (!options.selfManaged) { this.attachHandlers(); } this.setup(); } attachHandlers() { const { trigger, content } = this.elements; const { focusoutCloses } = this.options; this.clickHandler = (event) => { this.onClick(event); }; this.focusoutHandler = (event) => { if (focusoutCloses) { this.close(event); } }; trigger.addEventListener("click", this.clickHandler); content.addEventListener("focusout", this.focusoutHandler); } removeHandlers() { const { trigger, content } = this.elements; trigger.removeEventListener("click", this.clickHandler); content.removeEventListener("focusout", this.focusoutHandler); } onClick(event) { this.toggle(event); } destroy() { this.removeHandlers(); this.destroyTemporaryHandlers(); } debugLog(...msgs) { if (this.options.debug) { log(this, ...msgs); } } setup() { const { trigger, content } = this.elements; const { startOpen } = this.options; trigger.setAttribute("role", "button"); trigger.setAttribute("aria-controls", content.id); content.setAttribute("aria-labelledby", trigger.id); this.setState(startOpen); } createEvent(name, detail) { return new CustomEvent(getName$1("collapsible:" + name), { detail }); } setState(isOpen, event) { const ctx = { collapsible: this, isOpen, event }; this.debugLog(this, "Set state", ctx); const { trigger, content } = this.elements; const { openClass } = this.options; const setClass = (el) => el.classList[isOpen ? "add" : "remove"](openClass); trigger.setAttribute("aria-expanded", isOpen ? "true" : "false"); setClass(trigger); setClass(content); this.isOpen = isOpen; this.options.onChange(ctx); trigger.dispatchEvent(this.createEvent("change", ctx)); if (isOpen) { this.setupTemporaryHandlers(); } else { this.destroyTemporaryHandlers(); } } /** * Setup handlers needed for closing once open */ setupTemporaryHandlers() { const { content, trigger } = this.elements; const { clickOutsideCloses, escapeCloses } = this.options; const onDocumentClick = (event) => { const { target } = event; const inTrigger = trigger.contains(target); const inContent = content.contains(target); if (clickOutsideCloses && !inTrigger && !inContent) { this.close(event); } }; const onDocumentKeydown = (event) => { if (escapeCloses && event.key === "Escape") { this.close(event); } }; document.addEventListener("click", onDocumentClick); document.addEventListener("keydown", onDocumentKeydown); this.handlers.onDocumentClick = onDocumentClick; this.handlers.onDocumentKeydown = onDocumentKeydown; } /** * Destroy handlers attached for closing once open */ destroyTemporaryHandlers() { const { onDocumentClick, onDocumentKeydown } = this.handlers; if (onDocumentClick) { document.removeEventListener("click", onDocumentClick); } if (onDocumentClick) { document.removeEventListener("keydown", onDocumentKeydown); } } open(event) { this.setState(true, event); } close(event) { this.setState(false, event); } toggle(event) { this.setState(!this.isOpen, event); } // This is removed because I think it's not useful, users should keep references // Static Methods for managing instances of this class // static instances = []; // /** // * Get collapsible instance by trigger element // * @param {Node|String} trigger Trigger node or trigger ID // */ // static getInstance(trigger) { // return Collapsible.instances.find(c => typeof trigger === "string" ? // c.elements.trigger.id === trigger : // c.elements.trigger === trigger // ); // } // static removeInstance(instance) { // const index = Collapsible.instances.findIndex(c => c === instance); // if (index > -1) { // Collapsible.instances.splice(index, 1); // } // } }; __publicField(_Collapsible, "defaults", { clickOutsideCloses: false, // oneOpenPerContext: false, // This should be another module that manages instances within a context (accordions) // clickWithinCloses: false, // Not sure how this was used but seems like it should be separate focusoutCloses: false, escapeCloses: false, /** * The module won't attach the handlers (you need to do it yourself) */ selfManaged: false, /** * This collapsible starts in open state */ startOpen: false, /** * Open/active state class */ openClass: "is-active", /** * Output debug info */ debug: true, onChange(_ctx) { } }); let Collapsible = _Collapsible; const collapsible = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, Collapsible }, Symbol.toStringTag, { value: "Module" })); const _Resizer = class _Resizer { /** * * @param {Node} container Container to be resize * @param {Node} control Resize handle element * @param {Object} options Defualt can be changed on class * @param {Boolean} options.debug Enable non-essential debugging logs * @param {Boolean} options.overrideMaxWidth When script is activated by handle remove the elements max-width and allow the width of the resize to exceed the max (default false) * @param {Boolean} options.fromLeft The script should assume the handle is on the left side of the element */ constructor(container2, control, options) { if (!control || !container2) { logError$1(this, "Missing required elements 'control' or 'container'"); } this.options = Object.assign({}, _Resizer.defaults, options); this.container = container2; this.control = control; this.handlerMousedown = this.onMousedown.bind(this); this.control.addEventListener("mousedown", this.handlerMousedown); } destroy() { this.control.removeEventListener("mousedown", this.handlerMousedown); } onMousedown(e) { const { overrideMaxWidth, fromLeft } = this.options; const doc = document.documentElement; const win = document.defaultView; const x = e.clientX; const width = parseInt(win.getComputedStyle(this.container).width, 10); if (overrideMaxWidth) { this.container.style.maxWidth = "none"; } const mousemove = (event) => { const polarity = fromLeft ? -1 : 1; this.container.style.width = `${width + (event.clientX - x) * polarity}px`; }; const cleanup = () => { doc.removeEventListener("mousemove", mousemove, false); }; doc.addEventListener("mousemove", mousemove, false); doc.addEventListener("mouseup", cleanup, { capture: true, once: true }); } }; __publicField(_Resizer, "defaults", { debug: false, overrideMaxWidth: false, fromLeft: false }); let Resizer = _Resizer; const resizer = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, Resizer }, Symbol.toStringTag, { value: "Module" })); const selectors = [ ".youtube-embedded-video", 'iframe[title*="YouTube video player"]', 'iframe[src*="youtube.com/embed"]' ]; function pauseVideos$1(context = document) { const videos = getVideos(context); videos.forEach((video) => { try { video.contentWindow.postMessage('{"event":"command","func":"stopVideo","args":""}', "*"); } catch (error) { console.error(error); } }); } function prepVideos$1(context = document) { const videos = getVideos(context); videos.forEach((video) => { const { src } = video; if (src) { video.src = src.split("?")[0] + "?rel=0&enablejsapi=1"; } }); } function getVideos(context) { return context.querySelectorAll(selectors.join(", ")); } const pauseYoutubeVideo = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, pauseVideos: pauseVideos$1, prepVideos: prepVideos$1 }, Symbol.toStringTag, { value: "Module" })); const attrs$d = { init: "data-ulu-dialog-init", dialog: "data-ulu-dialog", trigger: "data-ulu-dialog-trigger", close: "data-ulu-dialog-close" }; const attrSelector$d = (key2) => `[${attrs$d[key2]}]`; const attrSelectorInitial$9 = (key2) => `${attrSelector$d(key2)}:not([${attrs$d.init}])`; const queryAllInitial$3 = (key2) => document.querySelectorAll(attrSelectorInitial$9(key2)); const defaults$a = { /** * Use non-modal interface for dialog */ nonModal: false, /** * Move the dialog to the document end (hoist out of content) * - helpful if dialogs are within editor body, etc */ documentEnd: false, /** * Requires styling that reduces any padding/border on dialog */ clickOutsideCloses: true, /** * Whether or not to pause videos when dialog closes (currently just youtube and native) */ pauseVideos: true }; let currentDefaults$3 = { ...defaults$a }; function setDefaults$3(options) { currentDefaults$3 = Object.assign({}, currentDefaults$3, options); } function init$g() { document.addEventListener(getName$1("pageModified"), setup$d); setup$d(); } function setup$d() { const dialogs = queryAllInitial$3("dialog"); dialogs.forEach(setupDialog); const triggers = queryAllInitial$3("trigger"); triggers.forEach(setupTrigger$2); } function setupTrigger$2(trigger) { trigger.addEventListener("click", handleTrigger); trigger.setAttribute(attrs$d.init, ""); function handleTrigger() { var _a; const id2 = trigger.dataset.uluDialogTrigger; const dialog2 = document.getElementById(id2); if (!dialog2) { console.error("Could not locate dialog (id)", id2); return; } if (((_a = dialog2 == null ? void 0 : dialog2.tagName) == null ? void 0 : _a.toLowerCase()) !== "dialog") { console.error("Attempted to trigger non <dialog> element. Did you mean to use modal builder?"); return; } const options = getDialogOptions(dialog2); dialog2[options.nonModal ? "show" : "showModal"](); } } function setupDialog(dialog2) { const options = getDialogOptions(dialog2); dialog2.addEventListener("click", handleClicks); dialog2.setAttribute(attrs$d.init, ""); if (options.documentEnd) { document.body.appendChild(dialog2); } if (options.pauseVideos) { prepVideos(dialog2); } function handleClicks(event) { const { target } = event; const closeFromButton = target.closest("[data-ulu-dialog-close]"); let closeFromOutside = options.clickOutsideCloses && target === dialog2 && wasClickOutside(dialog2, event); if (closeFromOutside || closeFromButton) { if (options.pauseVideos) { pauseVideos(dialog2); } dialog2.close(); } } } function getDialogOptions(dialog2) { const options = getDatasetJson(dialog2, "uluDialog"); return Object.assign({}, currentDefaults$3, options); } function prepVideos(dialog2) { prepVideos$1(dialog2); } function pauseVideos(dialog2) { pauseVideos$1(dialog2); const nativeVideos = dialog2.querySelectorAll("video"); nativeVideos.forEach((video) => video.pause()); } const dialog = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, attrs: attrs$d, defaults: defaults$a, getDialogOptions, init: init$g, setDefaults: setDefaults$3, setup: setup$d, setupDialog, setupTrigger: setupTrigger$2 }, Symbol.toStringTag, { value: "Module" })); const attrs$c = { builder: "data-ulu-modal-builder", body: "data-ulu-modal-builder-body", resizer: "data-ulu-modal-builder-resizer" }; const attrSelector$c = (key2) => `[${attrs$c[key2]}]`; const defaults$9 = { title: null, titleIcon: null, nonModal: false, documentEnd: true, allowResize: false, position: "center", bodyFills: false, noBackdrop: false, size: "default", print: false, noMinHeight: false, class: "", classCloseIcon: wrapSettingString("iconClassClose"), classResizerIcon: wrapSettingString("iconClassDragX"), debug: false, templateCloseIcon(config2) { return `<span class="modal__close-icon ${config2.classCloseIcon}" aria-hidden="true"></span>`; }, templateResizerIcon(config2) { return `<span class="modal__resizer-icon ${config2.classResizerIcon}" aria-hidden="true"></span>`; }, /** * Default modal template * @param {String} id ID for new modal * @param {Object} config Resolved options * @returns {String} Markup for modal */ template(id2, config2) { const classes = [ "modal", `modal--${config2.position}`, `modal--${config2.size}`, `modal--${config2.allowResize ? "resize" : "no-resize"}`, ...!config2.title ? ["modal--no-header"] : [], ...config2.bodyFills ? ["modal--body-fills"] : [], ...config2.noBackdrop ? ["modal--no-backdrop"] : [], ...config2.noMinHeight ? ["modal--no-min-height"] : [], ...config2.class ? [config2.class] : [] ]; return ` <dialog id="${id2}" class="${classes.join(" ")}"> ${config2.title ? ` <header class="modal__header"> <h2 class="modal__title"> ${config2.titleIcon ? `<span class="modal__title-icon ${config2.titleIcon}" aria-hidden="true"></span>` : ""} <span class="modal__title-text">${config2.title}</span> </h2> <button class="modal__close" aria-label="Close modal" ${attrs$d.close} autofocus> ${config2.templateCloseIcon(config2)} </button> </header> ` : ""} <div class="modal__body" ${attrs$c.body}></div> ${config2.hasResizer ? `<div class="modal__resizer" ${attrs$c.resizer}> ${config2.templateResizerIcon(config2)} </div>` : ""} </div> `; } }; let currentDefaults$2 = { ...defaults$9 }; function setDefaults$2(options) { currentDefaults$2 = Object.assign({}, currentDefaults$2, options); } function init$f() { document.addEventListener(getName$1("pageModified"), setup$c); setup$c(); } function setup$c() { const builders = document.querySelectorAll(attrSelector$c("builder")); builders.forEach(setupBuilder); } function setupBuilder(element) { const options = getDatasetJson(element, "uluModalBuilder"); element.removeAttribute(attrs$c.builder); buildModal(element, options); } function buildModal(content, options) { const config2 = Object.assign({}, currentDefaults$2, options); if (config2.position !== "center" && config2.allowResize) { config2.hasResizer = true; } if (config2.debug) { console.log(config2, content); } if (!content.id) { throw new Error("Missing ID on modal"); } const markup = config2.template(content.id, config2); const modal = createElementFromHtml(markup.trim()); const selectChild = (key2) => modal.querySelector(attrSelector$c(key2)); const body = selectChild("body"); const resizer2 = selectChild("resizer"); const dialogOptions = separateDialogOptions(config2); content.removeAttribute("id"); content.removeAttribute("hidden"); content.removeAttribute(attrs$c.builder); content.parentNode.replaceChild(modal, content); body.appendChild(content); modal.setAttribute(attrs$d.dialog, JSON.stringify(dialogOptions)); if (config2.hasResizer) { new Resizer(modal, resizer2, { fromLeft: config2.position === "right" }); } if (config2.print) { let printClone; document.addEventListener(getName$1("beforePrint"), () => { printClone = content.cloneNode(true); modal.after(printClone); }); document.addEventListener(getName$1("afterPrint"), () => { printClone.remove(); }); } return { modal }; } function separateDialogOptions(config2) { return Object.keys(defaults$a).reduce((acc, key2) => { if (key2 in config2) { acc[key2] = config2[key2]; } return acc; }, {}); } const modalBuilder = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, buildModal, defaults: defaults$9, init: init$f, setDefaults: setDefaults$2, setup: setup$c, setupBuilder }, Symbol.toStringTag, { value: "Module" })); const linebreaks = /(\r\n|\n|\r)/gm; const multiSpace = /\s+/g; function trimWhitespace(string) { return string.replace(linebreaks, "").replace(multiSpace, " ").trim(); } const _Flipcard = class _Flipcard { constructor(container2, front, back, config2, debug = false) { if (!back) { logError$1(this, "Missing an element (container, front, back)"); } this.options = Object.assign({}, _Flipcard.defaults, config2); const { namespace } = this.options; _Flipcard.instances.push(this); this.debug = debug; this.elements = { container: container2, front, back }; this.isOpen = false; this.uid = `${namespace}-id-${_Flipcard.instances.length}`; this.stateAttr = `data-${namespace}-state`.toLowerCase(); this.setup(); this.setVisiblity(false); log(this, this); } toggle() { this.setVisiblity(!this.isOpen); } setup() { const { uid } = this; const { namespace, proxyClick: proxyClick2 } = this.options; const { container: container2, front, back } = this.elements; const control = this.elements.control = document.createElement("button"); control.classList.add(this.getClass("control-button")); control.setAttribute("type", "button"); control.innerHTML = this.createControlContent(); control.style.gridArea = namespace; control.style.zIndex = "-1"; control.addEventListener("focusin", () => { control.style.zIndex = "20"; }); control.addEventListener("focusout", () => { control.style.zIndex = "-1"; }); control.addEventListener("click", this.toggle.bind(this)); back.parentNode.insertBefore(control, back); container2.classList.add(this.options.namespace); container2.setAttribute("style", trimWhitespace(this.containerCss())); if (proxyClick2) { container2.addEventListener("click", this.onProxyClick.bind(this)); } front.style.gridArea = namespace; back.style.gridArea = namespace; control.id = `${uid}-control`; control.setAttribute("aria-controls", back.id); control.setAttribute("aria-expanded", "false"); back.id = `${uid}-back`; back.setAttribute("aria-labelledby", control.id); back.setAttribute("aria-hidden", "true"); } /** * Click handler on everything on container * - Determines if click was something that should be ignored (link, etc) */ onProxyClick({ target }) { const { exclude, allowSelection, selectionMin } = this.options.proxyClick; const selection = window.getSelection(); if (exclude && !target.matches(exclude)) { if (!allowSelection || selection.toString().length < selectionMin) { this.toggle(); } } } getClass(child) { const { namespace } = this.options; return child ? `${namespace}__${child}` : namespace; } createControlContent() { return ` <span class="hidden-visually">Show More Information</span> `; } setVisiblity(visible2) { const { back, container: container2, control } = this.elements; const state = visible2 ? "open" : "closed"; back.style.zIndex = visible2 ? "10" : "1"; back.style.visibility = visible2 ? "visible" : "hidden"; container2.setAttribute(this.stateAttr, state); back.setAttribute("aria-hidden", visible2 ? "false" : "true"); control.setAttribute("aria-expanded", visible2 ? "true" : "false"); this.isOpen = visible2; } containerCss() { return ` display: -ms-grid; display: grid; position: relative; -ms-grid-columns: 1fr; grid-template-columns: 1fr; justify-items: stretch; grid-template-areas: "${this.options.namespace}"; cursor: pointer; `; } panelCss(zIndex = 1) { return ` grid-area: ${this.options.namespace}; z-index: ${zIndex} `; } }; __publicField(_Flipcard, "instances", []); __publicField(_Flipcard, "defaults", { namespace: "Flipcard", proxyClick: { allowSelection: true, // Don't proxy click if the user has more than the minmimum selected selectionMin: 10, // Minimum length that qualifies as a selection exclude: "a, input, textarea, button" // Selectors to avoid closing a flipcard onProxyclick } }); let Flipcard = _Flipcard; const attrs$b = { init: "data-ulu-flipcard-init", flipcard: "data-ulu-flipcard", front: "data-ulu-flipcard-front", back: "data-ulu-flipcard-back" }; const attrSelector$b = (key2) => `[${attrs$b[key2]}]`; const attrSelectorInitial$8 = (key2) => `${attrSelector$b(key2)}:not([${attrs$b.init}])`; const instances$4 = []; function init$e() { document.addEventListener(getName$1("pageModified"), setup$b); setup$b(); } function setup$b() { const builders = document.querySelectorAll(attrSelectorInitial$8("flipcard")); builders.forEach(setupFlipcard); } function setupFlipcard(container2) { container2.setAttribute(attrs$b.init, ""); const options = getDatasetOptionalJson(container2, "uluFlipcard"); const config2 = Object.assign({}, options); const front = container2.querySelector(attrSelectorInitial$8("front")); const back = container2.querySelector(attrSelectorInitial$8("back")); instances$4.push(new Flipcard(container2, front, back, config2)); } const flipcard = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, Flipcard, attrs: attrs$b, init: init$e, setup: setup$b }, Symbol.toStringTag, { value: "Module" })); function init$d(selector = "[data-grid]", classes) { document.addEventListener(getName$1("pageModified"), () => setup$a(selector, classes)); document.addEventListener(getName$1("pageResized"), () => setup$a(selector, classes)); setup$a(selector, classes); } function setup$a(selector, classes) { document.querySelectorAll(selector).forEach((element) => setPositionClasses(element, classes || void 0)); } const grid = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, init: init$d, setup: setup$a }, Symbol.toStringTag, { value: "Module" })); function createPager() { return function pager(instance, dir) { const isNext = dir === "next"; const { track } = instance.elements; if (!track.children) return 400; const trackStyle = window.getComputedStyle(track); const scrollPaddingRaw = trackStyle.getPropertyValue("scroll-padding-left").replace("auto", "0px"); const scrollPadding = parseInt(scrollPaddingRaw, 10); const { scrollLeft, offsetWidth } = track; const right = scrollLeft + offsetWidth; const slides = [...track.children].map((element) => { const { offsetLeft, offsetWidth: offsetWidth2 } = element; return { element, offsetLeft, offsetRight: offsetLeft + offsetWidth2 }; }); let slideFound; if (isNext) { slideFound = slides.find((slide) => slide.offsetRight >= right); } else { let slideBeforeIndex = slides.findLastIndex((slide) => slide.offsetLeft <= scrollLeft); if (slideBeforeIndex) { let slideBefore = slides[slideBeforeIndex]; let slidesBefore = slides.slice(0, slideBeforeIndex + 1); slideFound = slidesBefore.find((slide) => { const rightEdge = slide.offsetLeft + scrollPadding + offsetWidth; return rightEdge >= slideBefore.offsetRight; }); } } if (slideFound) { return isNext ? slideFound.offsetLeft : slideFound.offsetLeft + scrollPadding; } else { return 400; } }; } const overflowScrollerPager = /* @__PURE__ */ Object.freeze(/* @__PURE__ */ Object.defineProperty({ __proto__: null, createPager }, Symbol.toStringTag, { value: "Module" })); function hasRequiredProps(required) { return (obj) => { return required.every((prop) => { return Object.prototype.hasOwnProperty.call(obj, prop); }); }; } const requiredElements$1 = [ "track", "controls" ]; const _OverflowScroller = class _OverflowScroller { constructor(elements, config2) { this.options = Object.assign({}, _OverflowScroller.defaults, config2); if (!hasRequiredProps(requiredElements$1)) { logError$1(this, "Missing a required Element"); } this.elements = { ...elements, ...this.createControls(elements.controls) }; this.nextEnabled = true; this.previousEnabled = true; this.scrollHandler = (e) => this.onScroll(e); this.elements.track.addEventListener("scroll", this.scrollHandler, { passive: true }); this.checkOverflow(); this.onScroll(); } checkOverflow() { const { track } = this.elements; this.hasOverflow = track.scrollWidth > track.clientWidth; } createControls(context) { const controls = document.createElement("ul"); const previousItem = document.createElement("li"); const nextItem = document.createElement("li"); const previous = this.createControlButton("previous"); const next = this.createControlButton("next"); const itemClass = this.getClass("controls-item"); nextItem.classList.add(itemClass); nextItem.classList.add(itemClass + "--next"); previousItem.classList.add(itemClass); previousItem.classList.add(itemClass + "--previous"); controls.classList.add(this.getClass("controls")); previousItem.appendChild(previous); nextItem.appendChild(next); controls.appendChild(previousItem); controls.appendChild(nextItem); previous.addEventListener("click", this.previous.bind(this)); next.addEventListener("click", this.next.bind(this)); context.appendChild(controls); return { controls, previousItem, nextItem, previous, next }; } createControlButton(action) { const button = document.createElement("button"); button.classList.add(this.getClass("control-button")); button.classList.add(this.getClass(`control-button--${action}`)); button.classList.add(...this.options.buttonClasses); button.setAttribute("type", "button"); button.innerHTML = this.getControlContent(action); return button; } getControlContent(action) { const classes = this.options[action === "next" ? "iconClassNext" : "iconClassPrevious"]; return ` <span class="hidden-visually">${action}</span> <span class="${classes}" aria-hidden="true"></span> `; } onScroll(event) { if (!this.hasOverflow) return; this.onScrollHorizontal(); } onScrollHorizontal() { const { nextEnabled, previousEnabled } = this; const { track } = this.elements; const { offsetStart, offsetEnd } = this.options; const { scrollWidth, clientWidth, scrollLeft } = track; const atStart = scrollLeft <= offsetStart; const atEnd = scrollWidth - scrollLeft - offsetEnd <= clientWidth; if (atStart && previousEnabled) { this.setControlState("previous", false); } else if (!atStart && !previousEnabled) { this.setControlState("previous", true); } if (atEnd && nextEnabled) { this.setControlState("next", false); } else if (!atEnd && !nextEnabled) { this.setControlState("next", true); } } setControlState(dir, enabled) { const isNext = dir === "next"; const { next, nextItem, previous, previousItem } = this.elements; const item = isNext ? nextItem : previousItem; const button = isNext ? next : previous; const classlistMethod = enabled ? "remove" : "add"; item.classList[classlistMethod](this.getClass("controls-item--disabled")); button.classList[enabled ? "remove" : "add"](this.getClass("control--disabled")); if (enabled) { button.removeAttribute("disabled"); } else { button.setAttribute("disabled", ""); } this[isNext ? "nextEnabled" : "previousEnabled"] = enabled; } resolveAmount(dir) { const isNext = dir === "next"; const { amount } = this.options; const { scrollLeft, offsetWidth } = this.elements.track; if (amount === "auto") { return isNext ? scrollLeft + offsetWidth : scrollLeft - offsetWidth; } else if (typeof amount === "function") { return amount(this, dir); } else if (typeof amount === "number") { return isNext ? scrollLeft + amount : scrollLeft - amount; } logError$1("Unable to resolve amount for scroll"); return 500; } next() { this.elements.track.scrollTo({ top: 0, left: this.resolveAmount("next"), behavior: "smooth" }); } previous() { this.elements.track.scrollTo({ top: 0, left: this.resolveAmount("previous"), behavior: "smooth" }); } getClass(child) { con