@trimble-oss/moduswebcomponents
Version:
Modus Web Components is a modern, accessible UI library built with Stencil JS that provides reusable web components following Trimble's Modus design system. This updated version focuses on improved flexibility, enhanced theming options, comprehensive cust
203 lines (202 loc) • 8.06 kB
JavaScript
/**
* WithTreeMenu story behavior split by theme.
*
* | Concern | Connect | Modern / Classic |
* |---------|---------|-------------------|
* | Navbar ↔ side-nav sync (`handleMenuOpenChange`) | Shared | Shared |
* | Stale `mainMenuOpenChange` after click-outside collapse (`suppressNextMenuOpen`) | Shared | Shared |
* | `expandedChange` submenu cleanup | Flyout + `blockExpand` | `collapseSubmenu()` only |
* | Data row start slot | Flyout dropdown | `master_data` icon |
* | `hideFlyout` / flyout timers | Yes | No |
* | Collapsed rail CSS (hide labels, end slot, grid) | Yes | No |
* | `min-width` 5rem collapsed rail | Story arg (Connect layout) | Default 4rem when arg omitted |
*/
import { isConnectSideNavTheme, SIDE_NAV_DATA_FLYOUT_MENU_GAP, } from "./modus-wc-side-navigation-tree-item-end-action.story-styles";
/** Side-nav width transition (modus-wc-side-navigation.scss). */
export const WITH_TREE_MENU_SIDE_NAV_COLLAPSE_MS = 220;
const cancelPendingFlyoutOpen = (state) => {
if (state.flyoutOpenTimer) {
clearTimeout(state.flyoutOpenTimer);
state.flyoutOpenTimer = null;
}
if (state.collapseFlyoutTimer) {
clearTimeout(state.collapseFlyoutTimer);
state.collapseFlyoutTimer = null;
}
};
export const hideWithTreeMenuFlyout = (state) => {
cancelPendingFlyoutOpen(state);
if (state.dataIconDropdown) {
state.dataIconDropdown.menuVisible = false;
}
};
export const setWithTreeMenuDataFlyoutDisabled = (state, disabled) => {
if (state.dataIconDropdown) {
state.dataIconDropdown.disabled =
disabled;
}
};
const syncDataFlyoutMenuOffset = (state) => {
if (!state.dataIconDropdown)
return;
const sideNavHost = state.dataIconDropdown.closest('modus-wc-side-navigation');
const navPanel = sideNavHost === null || sideNavHost === void 0 ? void 0 : sideNavHost.querySelector('.modus-wc-side-navigation');
const button = state.dataIconDropdown.querySelector('modus-wc-button');
if (!navPanel || !button)
return;
const navRect = navPanel.getBoundingClientRect();
const btnRect = button.getBoundingClientRect();
const triggerRect = state.dataIconDropdown.getBoundingClientRect();
const anchorRight = Math.max(btnRect.right, triggerRect.right);
const offset = Math.round(navRect.right - anchorRight + SIDE_NAV_DATA_FLYOUT_MENU_GAP);
state.dataIconDropdown.menuOffset =
offset;
};
export const openWithTreeMenuDataFlyout = (state, defer = false) => {
if (!isConnectSideNavTheme())
return;
cancelPendingFlyoutOpen(state);
const open = () => {
state.flyoutOpenTimer = null;
if (state.dataIconDropdown) {
syncDataFlyoutMenuOffset(state);
state.dataIconDropdown.menuVisible = true;
}
};
if (defer) {
state.flyoutOpenTimer = setTimeout(open, 0);
}
else {
open();
}
};
const collapseSubmenuInline = (treeItem) => {
const submenu = treeItem.querySelector('.modus-wc-menu-dropdown');
const liElement = treeItem.querySelector(':scope > li');
if (!submenu || !liElement)
return;
submenu.classList.remove('modus-wc-menu-dropdown-show');
liElement.classList.remove('modus-wc-menu-item-expanded');
liElement.classList.remove('modus-wc-menu-dropdown-show');
const host = treeItem;
if (host && 'isExpanded' in host) {
host.isExpanded = false;
}
};
const openDataSubmenuInline = (dataItem) => {
const submenu = dataItem.querySelector('.modus-wc-menu-dropdown');
const liElement = dataItem.querySelector(':scope > li');
if (!submenu ||
!liElement ||
submenu.classList.contains('modus-wc-menu-dropdown-show')) {
return;
}
submenu.classList.add('modus-wc-menu-dropdown-show');
liElement.classList.add('modus-wc-menu-item-expanded');
liElement.classList.add('modus-wc-menu-dropdown-show');
const host = dataItem;
if (host && 'isExpanded' in host) {
host.isExpanded = true;
}
};
const getSelectedTreeItem = (root) => {
const items = Array.from(root.querySelectorAll('modus-wc-tree-item'));
for (const item of items) {
if (item.selected) {
return item;
}
}
return null;
};
const selectionInDataSection = (root) => {
const selected = getSelectedTreeItem(root);
if (!selected)
return false;
if (selected.getAttribute('value') === 'data')
return true;
return Boolean(selected.closest('modus-wc-tree-item[value="data"] modus-wc-tree-menu'));
};
const shouldOpenDataFlyoutOnCollapse = (root) => { var _a; return ((_a = getSelectedTreeItem(root)) === null || _a === void 0 ? void 0 : _a.getAttribute('value')) === 'data'; };
const collapseTreeSubmenus = (eventSource) => {
eventSource.querySelectorAll('modus-wc-tree-item').forEach((treeItem) => {
const item = treeItem;
if (item.hasSubmenu && typeof item.collapseSubmenu === 'function') {
void item.collapseSubmenu();
}
});
};
/** Modern/Classic: same pattern as WithSubmenu — no flyout or blockExpand. */
export const handleWithTreeMenuExpandedChangeClassicModern = (e, eventSource, menuOpenSuppress) => {
if (!e.detail) {
menuOpenSuppress.set(true);
collapseTreeSubmenus(eventSource);
}
};
/** Connect: flyout, blockExpand, and inline submenu coordination. */
export const handleWithTreeMenuExpandedChangeConnect = (e, eventSource, flyoutState, menuOpenSuppress) => {
hideWithTreeMenuFlyout(flyoutState);
const treeItems = eventSource.querySelectorAll('modus-wc-tree-item');
if (e.detail) {
setWithTreeMenuDataFlyoutDisabled(flyoutState, true);
treeItems.forEach((treeItem) => {
const item = treeItem;
if (item.hasSubmenu) {
item.blockExpand = false;
}
});
if (selectionInDataSection(eventSource)) {
const dataItem = eventSource.querySelector('modus-wc-tree-item[value="data"]');
if (dataItem) {
setTimeout(() => openDataSubmenuInline(dataItem), 0);
}
}
}
else {
menuOpenSuppress.set(true);
setWithTreeMenuDataFlyoutDisabled(flyoutState, false);
treeItems.forEach((treeItem) => {
const item = treeItem;
if (item.hasSubmenu) {
item.blockExpand = true;
collapseSubmenuInline(treeItem);
if (typeof item.collapseSubmenu === 'function') {
void item.collapseSubmenu();
}
}
});
if (shouldOpenDataFlyoutOnCollapse(eventSource)) {
flyoutState.collapseFlyoutTimer = setTimeout(() => {
flyoutState.collapseFlyoutTimer = null;
openWithTreeMenuDataFlyout(flyoutState);
}, WITH_TREE_MENU_SIDE_NAV_COLLAPSE_MS);
}
}
};
/** Navbar hamburger ↔ side-nav expanded (all themes). */
export const handleWithTreeMenuMenuOpenChange = (e, menuOpenSuppress) => {
if (menuOpenSuppress.get() && e.detail) {
menuOpenSuppress.set(false);
return;
}
menuOpenSuppress.set(false);
const eventSource = e.target;
const storyContainer = eventSource === null || eventSource === void 0 ? void 0 : eventSource.closest('.layout-with-navbar');
const sideNav = storyContainer
? storyContainer.querySelector('modus-wc-side-navigation')
: document.querySelector('modus-wc-side-navigation');
if (sideNav) {
sideNav.expanded = e.detail;
}
};
export const resetWithTreeMenuForNonConnectTheme = (flyoutState) => {
hideWithTreeMenuFlyout(flyoutState);
setWithTreeMenuDataFlyoutDisabled(flyoutState, true);
document
.querySelectorAll('modus-wc-side-navigation.side-navigation modus-wc-tree-item')
.forEach((treeItem) => {
const item = treeItem;
if (item.hasSubmenu) {
item.blockExpand = false;
}
});
};