@n8d/htwoo-core
Version:
hTWOo is a Fluent Design Framework purely in HTML and CSS
1,189 lines (1,178 loc) • 45.8 kB
JavaScript
;
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;