UNPKG

@n8d/htwoo-core

Version:

hTWOo is a Fluent Design Framework purely in HTML and CSS

1,189 lines (1,178 loc) • 45.8 kB
'use strict'; var __classPrivateFieldSet = (undefined && undefined.__classPrivateFieldSet) || function (receiver, state, value, kind, f) { if (kind === "m") throw new TypeError("Private method is not writable"); if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it"); return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value; }; var __classPrivateFieldGet = (undefined && undefined.__classPrivateFieldGet) || function (receiver, state, kind, f) { if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter"); if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it"); return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver); }; var _HOODialog_launcher, _HOODialog_dialog, _HOODialog_dialogType, _HOODialog_closer, _HOODialog_options, _HOODialog_showDialog, _HOODialog_backdropClick, _HOODialog_keyboardClose, _HOODialog_closeDialog; /** * Dialog type options */ exports.DialogType = void 0; (function (DialogType) { DialogType["DIALOG"] = "dialog"; DialogType["MODAL"] = "modal"; })(exports.DialogType || (exports.DialogType = {})); /** * HOODialog Class - Handles accessible dialog functionality */ class HOODialog { /** * Constructor for HOODialog * @param launcher - CSS selector for the element that opens the dialog * @param dialog - CSS selector for the dialog element * @param dialogType - Type of dialog (DIALOG or MODAL) * @param options - Configuration options */ constructor(launcher, dialog, dialogType = exports.DialogType.DIALOG, options = HOODialog.options) { // Private properties _HOODialog_launcher.set(this, void 0); _HOODialog_dialog.set(this, void 0); _HOODialog_dialogType.set(this, void 0); _HOODialog_closer.set(this, void 0); _HOODialog_options.set(this, { closer: null, backdropCloser: true, escCloser: true }); /** * Open the dialog */ _HOODialog_showDialog.set(this, () => { console.debug('-> Fired showDialog -- ', __classPrivateFieldGet(this, _HOODialog_dialog, "f")); if (__classPrivateFieldGet(this, _HOODialog_dialogType, "f") === exports.DialogType.DIALOG) { __classPrivateFieldGet(this, _HOODialog_dialog, "f").show(); } else if (__classPrivateFieldGet(this, _HOODialog_dialogType, "f") === exports.DialogType.MODAL) { __classPrivateFieldGet(this, _HOODialog_dialog, "f").showModal(); // FIX: Make backdrop click optional // Capture click on backdrop __classPrivateFieldGet(this, _HOODialog_dialog, "f").addEventListener('click', __classPrivateFieldGet(this, _HOODialog_backdropClick, "f")); } else { throw new Error(`Invalid dialog type specified for ${__classPrivateFieldGet(this, _HOODialog_dialog, "f")}`); } if (__classPrivateFieldGet(this, _HOODialog_closer, "f")) { __classPrivateFieldGet(this, _HOODialog_closer, "f").addEventListener('click', __classPrivateFieldGet(this, _HOODialog_closeDialog, "f")); } const autofocus = __classPrivateFieldGet(this, _HOODialog_dialog, "f").querySelector('[autofocus]'); if (autofocus) { console.debug('No Autofocus'); __classPrivateFieldGet(this, _HOODialog_dialog, "f").focus(); } // Capture close on ESC click __classPrivateFieldGet(this, _HOODialog_dialog, "f").addEventListener('keydown', __classPrivateFieldGet(this, _HOODialog_keyboardClose, "f")); } /** * Handle clicks on the dialog backdrop */ ); /** * Handle clicks on the dialog backdrop */ _HOODialog_backdropClick.set(this, (event) => { const rect = __classPrivateFieldGet(this, _HOODialog_dialog, "f").getBoundingClientRect(); const isInDialog = (rect.top <= event.clientY && event.clientY <= rect.top + rect.height && rect.left <= event.clientX && event.clientX <= rect.left + rect.width); if (!isInDialog && event.target === __classPrivateFieldGet(this, _HOODialog_dialog, "f")) { __classPrivateFieldGet(this, _HOODialog_dialog, "f").close(); } } /** * Close the dialog on keyCode ESC */ ); /** * Close the dialog on keyCode ESC */ _HOODialog_keyboardClose.set(this, (event) => { if (event.keyCode === 27) { __classPrivateFieldGet(this, _HOODialog_dialog, "f").close(); } } /** * Close the dialog used for custom event or close button */ ); /** * Close the dialog used for custom event or close button */ _HOODialog_closeDialog.set(this, (event) => { console.debug('closing dialog'); __classPrivateFieldGet(this, _HOODialog_dialog, "f").close(); } /** * Constructor for HOODialog * @param launcher - CSS selector for the element that opens the dialog * @param dialog - CSS selector for the dialog element * @param dialogType - Type of dialog (DIALOG or MODAL) * @param options - Configuration options */ ); console.debug("Register dialog", dialogType); // query DOM elements const launchElement = document.querySelector(launcher); const dialogElement = document.querySelector(dialog); if (!launchElement) { throw new Error(`Launcher '${launcher}' Element cannot be found`); } __classPrivateFieldSet(this, _HOODialog_launcher, launchElement, "f"); if (!dialogElement) { throw new Error(`Dialog '${dialog}' Element not found`); } __classPrivateFieldSet(this, _HOODialog_dialog, dialogElement, "f"); __classPrivateFieldGet(this, _HOODialog_launcher, "f").addEventListener('click', __classPrivateFieldGet(this, _HOODialog_showDialog, "f")); __classPrivateFieldSet(this, _HOODialog_dialogType, dialogType, "f"); // Handle options with proper defaults __classPrivateFieldSet(this, _HOODialog_options, { closer: options.closer === undefined ? null : options.closer, backdropCloser: options.backdropCloser === undefined ? true : options.backdropCloser, escCloser: options.escCloser === undefined ? true : options.escCloser }, "f"); // in case a close button is defined for the dialog if (options.closer !== null) { const closerElement = dialogElement.querySelector(options.closer); __classPrivateFieldGet(this, _HOODialog_options, "f").closer = options.closer; if (closerElement) { __classPrivateFieldSet(this, _HOODialog_closer, closerElement, "f"); } } console.debug('AFTER LAUNCH ::: ', __classPrivateFieldGet(this, _HOODialog_dialogType, "f"), __classPrivateFieldGet(this, _HOODialog_launcher, "f"), __classPrivateFieldGet(this, _HOODialog_dialog, "f"), __classPrivateFieldGet(this, _HOODialog_closer, "f")); } } _HOODialog_launcher = new WeakMap(), _HOODialog_dialog = new WeakMap(), _HOODialog_dialogType = new WeakMap(), _HOODialog_closer = new WeakMap(), _HOODialog_options = new WeakMap(), _HOODialog_showDialog = new WeakMap(), _HOODialog_backdropClick = new WeakMap(), _HOODialog_keyboardClose = new WeakMap(), _HOODialog_closeDialog = new WeakMap(); /** Default options for modal dialog */ HOODialog.options = { closer: null, backdropCloser: true, escCloser: true }; /** * Enum of available dialog types */ HOODialog.dialogType = exports.DialogType; // Improved from: // Origin 1: https://24ways.org/2019/making-a-better-custom-select-element/ // Origin 2: https://css-tricks.com/making-a-better-custom-select-element/ // Code Pen: https://codepen.io/chriscoyier/pen/yLyyZrr /** * Initialize an accessible custom select element * @param listItem - The custom select container element */ const ariaSelect = (listItem) => { console.debug('ariaSelect', listItem); // Get elements within the select component const csSelector = listItem; console.log('csSelector', csSelector); const csInput = csSelector.querySelector('input'); if (!csInput) { console.error('Input element not found within select component'); return; } const csList = csSelector.querySelector('ul'); if (!csList) { console.error('List element not found within select component'); return; } const csOptions = csList.querySelectorAll('li.hoo-option'); if (!csOptions || csOptions.length === 0) { console.warn('No options found within select component'); } csSelector.querySelectorAll('svg'); // Status element to announce to screen readers const csStatus = document.querySelector('#custom-select-status'); if (!csStatus) { console.warn('Status element #custom-select-status not found'); } // Convert NodeList to Array for easier manipulation const aOptions = Array.from(csOptions); // Create and set start point for the state tracker let csState = "initial"; // Set up ARIA attributes for accessibility csSelector.setAttribute('role', 'combobox'); csSelector.setAttribute('aria-haspopup', 'listbox'); csSelector.setAttribute('aria-owns', 'custom-select-list'); // container owns the list... csInput.setAttribute('aria-autocomplete', 'both'); csInput.setAttribute('aria-controls', 'custom-select-list'); // ...but the input controls it csList.setAttribute('role', 'listbox'); csOptions.forEach((option) => { option.setAttribute('role', 'option'); option.setAttribute('tabindex', "-1"); // make li elements keyboard focusable by script only }); // Set up status message for screen readers if (csStatus) { csStatus.textContent = csOptions.length + " options available. Arrow down to browse or start typing to filter."; } // Initial state toggleList('Shut'); /** * Toggle the visibility of the dropdown list * @param whichWay - 'Open' or 'Shut' */ function toggleList(whichWay) { if (whichWay === 'Open') { csList.classList.remove('hidden-all'); csSelector.setAttribute('aria-expanded', 'true'); } else { // === 'Shut' csList.classList.add('hidden-all'); csSelector.setAttribute('aria-expanded', 'false'); } } /** * Find the currently focused element * @returns The element that currently has focus */ function findFocus() { return document.activeElement; } /** * Move focus between elements * @param fromHere - Current element with focus * @param toThere - Direction to move focus ('input', 'forward', or 'back') */ function moveFocus(fromHere, toThere) { // Get currently visible options const aCurrentOptions = aOptions.filter((option) => { return option.style.display === ''; }); // Don't move if no options are visible if (aCurrentOptions.length === 0) { return; } if (toThere === 'input') { csInput.focus(); return; } // Handle focus movement based on starting point switch (fromHere) { case csInput: if (toThere === 'forward') { aCurrentOptions[0].focus(); } else if (toThere === 'back') { aCurrentOptions[aCurrentOptions.length - 1].focus(); } break; case csOptions[0]: if (toThere === 'forward') { aCurrentOptions[1]?.focus(); } else if (toThere === 'back') { csInput.focus(); } break; case csOptions[csOptions.length - 1]: if (toThere === 'forward') { aCurrentOptions[0].focus(); } else if (toThere === 'back') { aCurrentOptions[aCurrentOptions.length - 2]?.focus(); } break; default: // middle list or filtered items const currentItem = findFocus(); const whichOne = aCurrentOptions.indexOf(currentItem); if (toThere === 'forward') { const nextOne = aCurrentOptions[whichOne + 1]; if (nextOne) nextOne.focus(); } else if (toThere === 'back' && whichOne > 0) { const previousOne = aCurrentOptions[whichOne - 1]; if (previousOne) previousOne.focus(); } else { // if whichOne = 0 csInput.focus(); } break; } } /** * Filter options based on input value */ function doFilter() { const terms = csInput.value; const aFilteredOptions = aOptions.filter((option) => { return option.innerText.toUpperCase().startsWith(terms.toUpperCase()); }); console.debug(aFilteredOptions); // Hide all options first csOptions.forEach(option => option.style.display = "none"); // Show only filtered options aFilteredOptions.forEach((option) => { option.style.display = ""; }); setState('filtered'); updateStatus(aFilteredOptions.length); } /** * Update status message for screen readers * @param howMany - Number of options currently available */ function updateStatus(howMany) { if (csStatus) { csStatus.textContent = howMany + " options available."; } } /** * Make a selection from the dropdown * @param whichOption - The selected option element */ function makeChoice(whichOption) { const optionValue = whichOption.dataset.value; if (optionValue) { csInput.value = optionValue; } moveFocus(document.activeElement, 'input'); } /** * Update the component state * @param newState - The new state to set */ function setState(newState) { csState = newState; } /** * Handle keyboard navigation and actions * @param whichKey - The key that was pressed */ function doKeyAction(whichKey) { const currentFocus = findFocus(); switch (whichKey) { case 'Enter': if (csState === 'initial') { // if state = initial, toggleOpen and set state to opened toggleList('Open'); setState('opened'); } else if (csState === 'opened' && currentFocus.tagName === 'LI') { // if state = opened and focus on list, makeChoice and set state to closed makeChoice(currentFocus); toggleList('Shut'); setState('closed'); } else if (csState === 'opened' && currentFocus === csInput) { // if state = opened and focus on input, close it toggleList('Shut'); setState('closed'); } else if (csState === 'filtered' && currentFocus.tagName === 'LI') { // if state = filtered and focus on list, makeChoice and set state to closed makeChoice(currentFocus); toggleList('Shut'); setState('closed'); } else if (csState === 'filtered' && currentFocus === csInput) { // if state = filtered and focus on input, set state to opened toggleList('Open'); setState('opened'); } else { // i.e. csState is closed, or csState is opened/filtered but other focus point? // if state = closed, set state to filtered? i.e. open but keep existing input? toggleList('Open'); setState('filtered'); } break; case 'Escape': // if state = opened or filtered, set state to initial if (csState === 'opened' || csState === 'filtered') { toggleList('Shut'); setState('initial'); } break; case 'ArrowDown': if (csState === 'initial' || csState === 'closed') { // if state = initial or closed, set state to opened and moveFocus to first toggleList('Open'); moveFocus(csInput, 'forward'); setState('opened'); } else { // if state is opened or filtered, move focus forward toggleList('Open'); moveFocus(currentFocus, 'forward'); } break; case 'ArrowUp': if (csState === 'initial' || csState === 'closed') { // if state = initial or closed, set state to opened and moveFocus to last toggleList('Open'); moveFocus(csInput, 'back'); setState('opened'); } else { // if state is opened or filtered, move focus back moveFocus(currentFocus, 'back'); } break; default: if (csState === 'initial') { // if state = initial, toggle open, doFilter and set state to filtered toggleList('Open'); doFilter(); setState('filtered'); } else if (csState === 'opened') { // if state = opened, doFilter and set state to filtered doFilter(); setState('filtered'); } else if (csState === 'closed') { // if state = closed, doFilter and set state to filtered doFilter(); setState('filtered'); } else { // already filtered doFilter(); } break; } } // Event Listeners // Handle clicks on the custom select csSelector.addEventListener('click', function (e) { const currentFocus = findFocus(); switch (csState) { case 'initial': // if state = initial, toggleOpen and set state to opened toggleList('Open'); setState('opened'); break; case 'opened': // if state = opened and focus on input, toggleShut and set state to initial if (currentFocus === csInput) { toggleList('Shut'); setState('initial'); } else if (currentFocus.tagName === 'LI') { // if state = opened and focus on list, makeChoice, toggleShut and set state to closed makeChoice(currentFocus); toggleList('Shut'); setState('closed'); } break; case 'filtered': // if state = filtered and focus on list, makeChoice and set state to closed if (currentFocus.tagName === 'LI') { makeChoice(currentFocus); toggleList('Shut'); setState('closed'); } // if state = filtered and focus on input, do nothing (wait for next user input) break; case 'closed': // if state = closed, toggleOpen and set state to filtered? or opened? toggleList('Open'); setState('filtered'); break; } }); // Handle keyboard events csSelector.addEventListener('keyup', function (e) { doKeyAction(e.key); }); // Close the select when clicking outside document.addEventListener('click', function (e) { const target = e.target; if (!target.closest('.hoo-select')) { // click outside of the custom group toggleList('Shut'); setState('initial'); } }); }; /** * Update visibility of optgroups based on their children's visibility */ function updateOptgroupVisibility() { const optgroups = document.querySelectorAll('.hoo-optgroup'); optgroups.forEach(optgroup => { const options = optgroup.querySelectorAll('.hoo-option'); const hasVisibleOption = Array.from(options).some(option => option.style.display !== 'none'); // Hide or show the optgroup based on visibility of its options optgroup.style.display = hasVisibleOption ? '' : 'none'; }); } // Run initially to set visibility updateOptgroupVisibility(); // Setup mutation observer to handle dynamic changes const selectDropdown = document.querySelector('.hoo-select-dropdown'); if (selectDropdown) { const observer = new MutationObserver(() => { updateOptgroupVisibility(); }); observer.observe(selectDropdown, { childList: true, subtree: true, attributes: true, attributeFilter: ['style'] // Monitor only style changes }); } /** * Position types for sticky tables */ var Position; (function (Position) { Position["LEFT"] = "left"; Position["RIGHT"] = "right"; Position["TOP"] = "top"; Position["BOTTOM"] = "bottom"; })(Position || (Position = {})); /** * Handle collapse and expand actions for collapsible tables * @param event - Click event */ const collapseAndExpand = (event) => { const target = event.target; const parentRow = target.closest("tr"); if (!parentRow) return; const parentTable = target.closest("table"); if (!parentTable) return; const section = parentRow.dataset.sectionheader; if (!section) return; let query; if (section === "all") { query = "tr[data-section]"; } else { query = `tr[data-section=${section}]`; } const sectionRows = parentTable.querySelectorAll(query); for (let i = 0; i < sectionRows.length; i++) { const currentItem = sectionRows[i]; if (currentItem.classList.contains('is-hidden')) { currentItem.classList.remove('is-hidden'); currentItem.classList.add('is-visible'); currentItem.setAttribute('aria-hidden', 'false'); parentRow.setAttribute('aria-expanded', 'true'); } else { currentItem.classList.add('is-hidden'); currentItem.classList.remove('is-visible'); currentItem.setAttribute('aria-hidden', 'true'); parentRow.setAttribute('aria-expanded', 'false'); } } if (section === "all") { const subSection = document.querySelectorAll("tbody tr.collapsable"); if (parentRow.getAttribute('aria-expanded') === "true") { for (let i = 0; i < subSection.length; i++) { const currentItem = subSection[i]; currentItem.setAttribute('aria-hidden', 'false'); currentItem.setAttribute('aria-expanded', 'true'); } } else { for (let i = 0; i < subSection.length; i++) { const currentItem = subSection[i]; currentItem.setAttribute('aria-hidden', 'true'); currentItem.setAttribute('aria-expanded', 'false'); } } } }; /** * Initialize collapsible functionality for tables */ const initCollapsability = () => { const collapseTables = document.querySelectorAll('.hoo-table.is-collapsable'); collapseTables.forEach(table => { const collapseRows = table.querySelectorAll('.collapsable'); collapseRows.forEach(tableRow => { tableRow.addEventListener('click', collapseAndExpand); }); }); }; /** * The position object for sticky positioning */ ({ "left": Position.LEFT, "right": Position.RIGHT, "top": Position.TOP, "bottom": Position.BOTTOM }); /** * Fix the offset for sticky elements * @param parent - Parent element * @param selector - Selector for sticky elements * @param offset - Position offset type */ const stickyOffsetFixup = (parent, selector, offset) => { const innerDefinition = parent.querySelectorAll(selector); for (let j = 0; j < innerDefinition.length; j++) { const innerElement = innerDefinition[j]; if (offset === Position.LEFT) { innerElement.style[offset] = innerElement.offsetLeft + "px"; } if (offset === Position.RIGHT) { innerElement.style[offset] = innerElement.offsetRight + "px"; } if (offset === Position.TOP) { innerElement.style[offset] = innerElement.offsetTop + "px"; } if (offset === Position.BOTTOM) { innerElement.style[offset] = innerElement.offsetBottom + "px"; } } }; /** * Initialize sticky table functionality */ const initSticky = () => { const allStickyTables = document.querySelectorAll("table.sticky"); for (let i = 0; i < allStickyTables.length; i++) { const stickyTable = allStickyTables[i]; stickyOffsetFixup(stickyTable, "tr td.is-sticky.left", Position.LEFT); stickyOffsetFixup(stickyTable, "tr th.is-sticky.left", Position.LEFT); } }; /** * Initialize all table functionality */ const initTables = () => { initCollapsability(); initSticky(); }; const elemPivotBar = '.hoo-pivotbar'; const elemPivotButton = '.hoo-button-pivot'; const dropDownPivotButton = '.hoo-navitem-text'; const stateIsActive = 'is-active'; /** * Handle pivot button click events * @param event - Click event */ const changePivot = (event) => { event.preventDefault(); const target = event.target; console.log(target); let currentButton; if (target.classList.contains(elemPivotButton.substr(1))) { currentButton = target; } else { currentButton = target.closest(elemPivotButton); } if (!currentButton) { if (target.classList.contains(dropDownPivotButton.substr(1))) { currentButton = target; } else { currentButton = target.closest(dropDownPivotButton); } } if (!currentButton) return; const currentPivotBar = target.closest(elemPivotBar); if (!currentPivotBar) return; const allButtons = currentPivotBar.querySelectorAll(elemPivotButton); allButtons.forEach(item => { item.classList.remove(stateIsActive); }); currentButton.classList.add(stateIsActive); }; /** * Initialize pivot bar functionality */ const initPivot = () => { // register event on regular buttons const pivotBarsButtons = document.querySelectorAll(`${elemPivotBar} ${elemPivotButton}`); pivotBarsButtons.forEach(pivotButton => { const button = pivotButton; button.dataset.ref = button.textContent?.trim() || ''; button.addEventListener('click', changePivot); }); }; /** * Reset the current page indicator for all nav items * @param allNavItems - Collection of navigation items */ const resetCurrentPage = (allNavItems) => { allNavItems.forEach((item) => { item.removeAttribute('aria-current'); }); }; /** * Handle navigation item click events * @param event - Click event */ const handleNavItemClick = (event) => { const target = event.target; let curClickItem = target; // Skip handling for button and icon clicks if (!curClickItem.classList.contains('hoo-buttonitem') && !curClickItem.classList.contains('hoo-icon')) { const curNavItem = curClickItem.closest('.hoo-navitem'); if (curNavItem) { const curNav = curClickItem.closest('.hoo-nav'); if (!curNav) return; const allNavItems = curNav.querySelectorAll('* .hoo-navitem'); resetCurrentPage(allNavItems); curNavItem.setAttribute('aria-current', 'true'); } } }; /** * Handle icon click events for expanding/collapsing submenus * @param event - Click event */ const handleIconClick = (event) => { const target = event.target; const curIcon = target; const subMenu = curIcon.closest('.hoo-navitem'); if (!subMenu) return; if (subMenu.getAttribute('aria-expanded') === 'true') { subMenu.setAttribute('aria-expanded', 'false'); } else { subMenu.setAttribute('aria-expanded', 'true'); } }; /** * Initialize menu functionality */ const initMenu = () => { const navMenus = document.querySelectorAll('.hoo-nav'); navMenus.forEach(nav => { // Add click handlers to navigation items const navItems = nav.querySelectorAll('.hoo-navitem'); navItems.forEach(navItem => { navItem.addEventListener('click', handleNavItemClick); }); // Add click handlers to navigation icons const navIcons = nav.querySelectorAll('.hoo-buttonicon'); navIcons.forEach(navIcon => { navIcon.addEventListener('click', handleIconClick); }); }); }; /** * File upload handler class * Manages file uploads with drag and drop functionality */ class FileUploadHandler { /** * Initialize the file upload handler * @param htmlElement - The file upload container element */ constructor(htmlElement) { this._files = []; /** * Handle the dragover event */ this.dragOver = (event) => { event.preventDefault(); this._fileLabel.classList.add('drag-over'); // Visual feedback }; /** * Handle the dragleave event */ this.dragLeave = (event) => { event.preventDefault(); this._fileLabel.classList.remove('drag-over'); }; /** * Handle the drop event */ this.dragDrop = (event) => { event.preventDefault(); this._fileLabel.classList.remove('drag-over'); const files = event.dataTransfer?.files; if (files && files.length > 0) { console.log('Files dropped:', files); this.fileChangedEvent({ target: { files } }); } }; /** * Handle file selection changes */ this.fileChangedEvent = (event) => { let files; console.debug('FileUploadHandler.fileChanged', event); const target = event.target; if (!target.files || target.files.length === 0) { this._infileOutput.innerHTML = ""; return; } files = Array.from(target.files); const fileOutput = document.createElement('ul'); fileOutput.classList.add('hoo-infile-list'); console.debug('FileUploadHandler', files); files.forEach(file => { const fileItem = document.createElement('li'); fileItem.innerText = file.name; fileOutput.appendChild(fileItem); }); this._infileOutput.innerHTML = `<div class='hoo-infile-selection'>${this._infileOutput.title || 'Files Selected'}</div>${fileOutput.outerHTML}`; }; this._fileControl = htmlElement; console.debug('FileUploadHandler', this._fileControl); this._fileLabel = htmlElement.querySelector('.hoo-infile-label'); if (!this._fileLabel) { throw new Error('File label element not found (.hoo-infile-label)'); } this._fileLabel.addEventListener('dragover', this.dragOver); this._fileLabel.addEventListener('dragleave', this.dragLeave); this._fileLabel.addEventListener('drop', this.dragDrop); this._originalFileControl = this._fileControl.querySelector('.hoo-infile-context'); if (!this._originalFileControl) { throw new Error('File input element not found (.hoo-infile-context)'); } console.debug('FileUploadHandler', this._originalFileControl, this._originalFileControl.getAttribute('aria-describedby')); this._originalFileControl.addEventListener('change', this.fileChangedEvent); const describedBy = this._originalFileControl.getAttribute('aria-describedby'); if (!describedBy) { throw new Error('File input is missing aria-describedby attribute'); } console.debug('FileUploadHandler', describedBy, this._infileOutput, `#${describedBy.trim()}`); this._infileOutput = document.querySelector(`#${describedBy.trim()}`); if (!this._infileOutput) { throw new Error(`Element with id "${describedBy.trim()}" not found`); } console.debug('Infile Output:', this._infileOutput); } } /** * Array to store overflow items for each container */ const overflowItems = []; /** * Default offset for overflow width calculations */ const defaultOffset = 40; /** * Handle overflow items based on available container width * @param targetWidth - Width of the overflow container * @param children - Child elements inside the overflow container * @param curContainer - Current overflow container * @param itemIndex - Index of the current container */ const getOverflowItems = (targetWidth, children, curContainer, itemIndex) => { // Filter items that should go into overflow let curOverFlowItems = overflowItems[itemIndex].filter(item => { return item.overallWidth > targetWidth - defaultOffset; }); // Filter items that can fit in the container let curItems = overflowItems[itemIndex].filter(item => { return item.overallWidth < targetWidth - defaultOffset; }); // Flyout Button ellipses const overflowControl = curContainer.querySelector('.hoo-buttonicon-overflow .hoo-buttonflyout'); if (!overflowControl) return; // Add items to overflow menu if (overflowControl && overflowControl.children.length < curOverFlowItems.length) { for (let i = 0; i < curOverFlowItems.length; i++) { const itemRef = curOverFlowItems[i].ref; const element = curContainer.querySelector(`[data-ref="${itemRef}"]`); if (element) { let listItem = document.createElement('li'); // Moves the Element into a new list item with all Events attached listItem.appendChild(element); // Append list item overflowControl.appendChild(listItem); } } } // Toggle overflow button visibility if (overflowControl.children.length !== 0) { const buttonEnabled = overflowControl.closest('.hoo-buttonicon-overflow'); if (buttonEnabled) { buttonEnabled.classList.add('is-active'); } } else { const buttonEnabled = overflowControl.closest('.hoo-buttonicon-overflow'); if (buttonEnabled) { buttonEnabled.classList.remove('is-active'); } } // Move items back from overflow if there's room if (overflowControl && overflowControl.children.length > curOverFlowItems.length) { for (let i = 0; i < curItems.length; i++) { const itemRef = curItems[i].ref; const overflowElement = overflowControl.querySelector(`[data-ref="${itemRef}"]`); if (overflowElement) { // Move elements back from overflow menu curContainer.appendChild(overflowElement); } } } /** * Cleanup empty <li> elements */ for (let i = 0; i < overflowControl.children.length; i++) { if (overflowControl.children[i].children.length === 0) { overflowControl.children[i].remove(); } } }; /** * Handle all entries in the overflow menu * @param entry - ResizeObserver entry * @param index - Index of the entry */ const entryHandler = (entry, index) => { const parent = entry.target.parentElement; if (!parent) return; const childButtons = Array.from(parent.querySelectorAll('.hoo-overflow > *')); if (childButtons.length > 0) { getOverflowItems(parent.clientWidth, childButtons, entry.target, index); } }; /** * Handle the overflow behavior for all '.hoo.overflow' containers * @param entries - ResizeObserver entries */ const overflow = (entries) => { // Handle the overflow behaviour for all '.hoo.overflow' containers entries.forEach((item, index) => { if (item.target.children) { initOverflowElements(item.target.children, index); // Handle the resizing entryHandler(item, index); } }); }; /** * Initialize overflow elements data * @param children - Collection of child elements * @param index - Container index */ const initOverflowElements = (children, index) => { let overallWidth = 0; if (overflowItems.length <= index) { overflowItems[index] = []; for (let i = 0; i < children.length; i++) { const child = children[i]; overallWidth += child.clientWidth; if (!child.classList.contains("hoo-buttonicon-overflow")) { const currentItem = { chlld: child, ref: "ref-" + index + "-" + i, width: child.clientWidth, overallWidth: overallWidth }; child.dataset.ref = currentItem.ref; overflowItems[index].push(currentItem); } } } }; /** * Initialize overflow functionality by setting up ResizeObserver * for all overflow containers */ const init = () => { const items = document.querySelectorAll('.hoo-overflow'); if (items.length !== 0) { const ofObserver = new ResizeObserver(overflow); items.forEach(item => { ofObserver.observe(item); }); } }; var overflow$1 = /*#__PURE__*/Object.freeze({ __proto__: null, init: init }); /** * Register event handlers for button elements matching a selector * @param classSelector - CSS selector for the buttons * @param handleWith - Event handler function */ const splitButtonReg = (classSelector, handleWith) => { const allSplitButtons = document.querySelectorAll(classSelector); allSplitButtons.forEach(element => { element.addEventListener('click', ((event) => { handleWith(event); })); }); }; /** * Handle button flyout toggle * @param curSplitButton - The button that was clicked * @param parentElement - Parent element of the button */ const _btnFlyOut = (curSplitButton, parentElement) => { // Set aria values let ariaPressed = curSplitButton.getAttribute('aria-pressed'); if (ariaPressed === null) { curSplitButton.setAttribute('aria-pressed', 'true'); } else { curSplitButton.setAttribute('aria-pressed', ariaPressed === 'true' ? 'false' : 'true'); } parentElement.classList.toggle('show-flyout'); }; /** * Handle button click events * @param event - Click event */ const buttonClick = (event) => { const curSplitButton = event.target; const parentElement = curSplitButton.parentElement; _btnFlyOut(curSplitButton, parentElement); }; /** * Handle split button click events * @param event - Click event */ const splitButtonClick = (event) => { console.log("\nEVENT: splitButtonClick"); const curSplitButton = event.target; const parentElement = curSplitButton.parentElement; _btnFlyOut(curSplitButton, parentElement); }; /** * Handle delete and slide animation * @param event - Click event */ const animateDeleteAndSlide = (event) => { const eventTarget = event.target; if (!eventTarget.parentElement?.dataset.animation) return; const animationClass = eventTarget.parentElement.dataset.animation; // Add class eventTarget.classList.add(animationClass); const computedStyles = window.getComputedStyle(eventTarget); const animationDuration = parseFloat(computedStyles.getPropertyValue('animation-duration')) * 1000; console.log(computedStyles, computedStyles.getPropertyValue('animation-duration'), animationDuration); setTimeout(() => { eventTarget.remove(); }, animationDuration); }; /** * Handle add and slide animation * @param event - Click event */ const animateAddAndSlide = (event) => { const eventTarget = event.target; if (!eventTarget.parentElement?.dataset.animation) return; const animationClass = eventTarget.parentElement.dataset.animation; // Add class eventTarget.classList.add(animationClass); const newDomElement = document.importNode(eventTarget, true); newDomElement.classList.add(animationClass); const computedStyles = window.getComputedStyle(newDomElement); const animationDuration = parseFloat(computedStyles.getPropertyValue('animation-duration')) * 1000; eventTarget.parentElement.appendChild(newDomElement); newDomElement.addEventListener('click', animateAddAndSlide); console.log(computedStyles, computedStyles.getPropertyValue('animation-duration'), animationDuration); setTimeout(() => { newDomElement.classList.remove('anim-add-slide'); }, animationDuration); }; /** * Register animation handlers for elements * @param classname - CSS class selector * @param handleWith - Event handler function */ const registerAnimation = (classname, handleWith) => { const animationBlocks = document.querySelectorAll(`${classname} > .sg-anim-block`); animationBlocks.forEach(element => { element.addEventListener('click', handleWith); }); }; /** * Register file input handlers * @param classname - CSS class selector for file inputs */ const registerInputHandler = (classname) => { const allFileHandlers = document.querySelectorAll(classname); allFileHandlers.forEach(fileHandler => { new FileUploadHandler(fileHandler); }); }; /** * Register ARIA select elements */ const registerAriaSelect = () => { const selects = document.querySelectorAll('.hoo-select'); console.debug('ALL SELECTED', selects); if (selects) { selects.forEach((item) => { console.debug(item); ariaSelect(item); }); } }; /** * Register dialog elements */ const registerDialog = () => { try { const dialog1 = new HOODialog('#btn-dialog', '#myDialog', exports.DialogType.DIALOG, { closer: '#closer-dlg', backdropCloser: true, escCloser: true }); const dialog2 = new HOODialog('#btn-modal-dialog', '#myDialog-1', exports.DialogType.MODAL, { closer: '#closer-mdl', backdropCloser: true, escCloser: true }); } catch (e) { } }; /** * Main initialization function to run after DOM is loaded */ const afterLoaded = () => { registerDialog(); splitButtonReg('.hoo-buttonsplit > .hoo-buttonsplit-carret', splitButtonClick); splitButtonReg('.hoo-buttonsplit-primary > .hoo-buttonsplit-carret', splitButtonClick); splitButtonReg('button.hoo-buttonicon-overflow', buttonClick); splitButtonReg('button.hoo-buttoncmd', buttonClick); splitButtonReg('button.hoo-buttoncmdbar', buttonClick); splitButtonReg('button.hoo-buttonicon-flyout', buttonClick); splitButtonReg('button.hoo-buttoncontext', buttonClick); registerAnimation('.anim-deleteNslide', animateDeleteAndSlide); registerAnimation('.anim-addNslide', animateAddAndSlide); registerInputHandler('.hoo-input-file'); registerAriaSelect(); /** Init Table Helper */ initTables(); /** Init Pivot Bars */ initPivot(); /** Init Menu Bars */ initMenu(); init(); setTimeout(() => { const tmpHidden = document.querySelectorAll('.tmp-hidden'); console.log(tmpHidden); tmpHidden.forEach(item => { item.addEventListener("focus", ((event) => { const target = event.target; target.classList.remove('.tmp-hidden'); console.log(tmpHidden); })); }); }, 1000); }; // Initialize the application when the window loads window.onload = afterLoaded; exports.FileUploadHandler = FileUploadHandler; exports.HOODialog = HOODialog; exports.ariaSelect = ariaSelect; exports.initMenu = initMenu; exports.initPivot = initPivot; exports.initTables = initTables; exports.overflow = overflow$1;