"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "createOverflowManager", { enumerable: true, get: function() { return createOverflowManager; } }); const _consts = require("./consts"); const _createResizeObserver = require("./createResizeObserver"); const _debounce = require("./debounce"); const _priorityQueue = require("./priorityQueue"); function createOverflowManager() { // calls to `offsetWidth or offsetHeight` can happen multiple times in an update // Use a cache to avoid causing too many recalcs and avoid scripting time to meausure sizes const sizeCache = new Map(); let container; let overflowMenu; // Set as true when resize observer is observing let observing = false; // If true, next update will dispatch to onUpdateOverflow even if queue top states don't change // Initially true to force dispatch on first mount let forceDispatch = true; const options = { padding: 10, overflowAxis: 'horizontal', overflowDirection: 'end', minimumVisible: 0, onUpdateItemVisibility: ()=>undefined, onUpdateOverflow: ()=>undefined }; const overflowItems = {}; const overflowDividers = {}; let disposeResizeObserver = ()=>null; const getNextItem = (queueToDequeue, queueToEnqueue)=>{ const nextItem = queueToDequeue.dequeue(); queueToEnqueue.enqueue(nextItem); return overflowItems[nextItem]; }; const groupManager = createGroupManager(); function compareItems(lt, rt) { if (!lt || !rt) { return 0; } const lte = overflowItems[lt]; const rte = overflowItems[rt]; if (lte.priority !== rte.priority) { return lte.priority > rte.priority ? 1 : -1; } const positionStatusBit = options.overflowDirection === 'end' ? Node.DOCUMENT_POSITION_FOLLOWING : Node.DOCUMENT_POSITION_PRECEDING; // eslint-disable-next-line no-bitwise return lte.element.compareDocumentPosition(rte.element) & positionStatusBit ? 1 : -1; } function getElementAxisSize(horizontal, vertical, el) { if (!sizeCache.has(el)) { sizeCache.set(el, options.overflowAxis === 'horizontal' ? el[horizontal] : el[vertical]); } return sizeCache.get(el); } const getOffsetSize = getElementAxisSize.bind(null, 'offsetWidth', 'offsetHeight'); const getClientSize = getElementAxisSize.bind(null, 'clientWidth', 'clientHeight'); const invisibleItemQueue = (0, _priorityQueue.createPriorityQueue)((a, b)=>-1 * compareItems(a, b)); const visibleItemQueue = (0, _priorityQueue.createPriorityQueue)(compareItems); function occupiedSize() { const totalItemSize = visibleItemQueue.all().map((id)=>overflowItems[id].element).map(getOffsetSize).reduce((prev, current)=>prev + current, 0); const totalDividerSize = Object.entries(groupManager.groupVisibility()).reduce((acc, [id, state])=>acc + (state !== 'hidden' && overflowDividers[id] ? getOffsetSize(overflowDividers[id].element) : 0), 0); const overflowMenuSize = invisibleItemQueue.size() > 0 && overflowMenu ? getOffsetSize(overflowMenu) : 0; return totalItemSize + totalDividerSize + overflowMenuSize; } const showItem = ()=>{ const item = getNextItem(invisibleItemQueue, visibleItemQueue); options.onUpdateItemVisibility({ item, visible: true }); if (item.groupId) { groupManager.showItem(item.id, item.groupId); if (groupManager.isSingleItemVisible(item.id, item.groupId)) { var _overflowDividers_item_groupId; (_overflowDividers_item_groupId = overflowDividers[item.groupId]) === null || _overflowDividers_item_groupId === void 0 ? void 0 : _overflowDividers_item_groupId.element.removeAttribute(_consts.DATA_OVERFLOWING); } } }; const hideItem = ()=>{ const item = getNextItem(visibleItemQueue, invisibleItemQueue); options.onUpdateItemVisibility({ item, visible: false }); if (item.groupId) { if (groupManager.isSingleItemVisible(item.id, item.groupId)) { var _overflowDividers_item_groupId; (_overflowDividers_item_groupId = overflowDividers[item.groupId]) === null || _overflowDividers_item_groupId === void 0 ? void 0 : _overflowDividers_item_groupId.element.setAttribute(_consts.DATA_OVERFLOWING, ''); } groupManager.hideItem(item.id, item.groupId); } }; const dispatchOverflowUpdate = ()=>{ const visibleItemIds = visibleItemQueue.all(); const invisibleItemIds = invisibleItemQueue.all(); const visibleItems = visibleItemIds.map((itemId)=>overflowItems[itemId]); const invisibleItems = invisibleItemIds.map((itemId)=>overflowItems[itemId]); options.onUpdateOverflow({ visibleItems, invisibleItems, groupVisibility: groupManager.groupVisibility() }); }; const processOverflowItems = ()=>{ if (!container) { return false; } sizeCache.clear(); const availableSize = getClientSize(container) - options.padding; // Snapshot of the visible/invisible state to compare for updates const visibleTop = visibleItemQueue.peek(); const invisibleTop = invisibleItemQueue.peek(); while(compareItems(invisibleItemQueue.peek(), visibleItemQueue.peek()) > 0){ hideItem(); // hide elements whose priority become smaller than the highest priority of the hidden one } // Run the show/hide step twice - the first step might not be correct if // it was triggered by a new item being added - new items are always visible by default. for(let i = 0; i < 2; i++){ // Add items until available width is filled - can result in overflow while(occupiedSize() < availableSize && invisibleItemQueue.size() > 0 || invisibleItemQueue.size() === 1 // attempt to show the last invisible item hoping it's size does not exceed overflow menu size ){ showItem(); } // Remove items until there's no more overflow while(occupiedSize() > availableSize && visibleItemQueue.size() > options.minimumVisible){ hideItem(); } } // only update when the state of visible/invisible items has changed return visibleItemQueue.peek() !== visibleTop || invisibleItemQueue.peek() !== invisibleTop; }; const forceUpdate = ()=>{ if (processOverflowItems() || forceDispatch) { forceDispatch = false; dispatchOverflowUpdate(); } }; const update = (0, _debounce.debounce)(forceUpdate); const observe = (observedContainer, userOptions)=>{ Object.assign(options, userOptions); observing = true; Object.values(overflowItems).forEach((item)=>visibleItemQueue.enqueue(item.id)); container = observedContainer; disposeResizeObserver = (0, _createResizeObserver.observeResize)(container, (entries)=>{ if (!entries[0] || !container) { return; } update(); }); }; const addItem = (item)=>{ if (overflowItems[item.id]) { return; } overflowItems[item.id] = item; // some options can affect priority which are only set on `observe` if (observing) { // Updates to elements might not change the queue tops // i.e. new element is enqueued but the top of the queue stays the same // force a dispatch on the next batched update forceDispatch = true; visibleItemQueue.enqueue(item.id); } if (item.groupId) { groupManager.addItem(item.id, item.groupId); item.element.setAttribute(_consts.DATA_OVERFLOW_GROUP, item.groupId); } update(); }; const addOverflowMenu = (el)=>{ overflowMenu = el; }; const addDivider = (divider)=>{ if (!divider.groupId || overflowDividers[divider.groupId]) { return; } divider.element.setAttribute(_consts.DATA_OVERFLOW_GROUP, divider.groupId); overflowDividers[divider.groupId] = divider; }; const removeOverflowMenu = ()=>{ overflowMenu = undefined; }; const removeDivider = (groupId)=>{ if (!overflowDividers[groupId]) { return; } const divider = overflowDividers[groupId]; if (divider.groupId) { delete overflowDividers[groupId]; divider.element.removeAttribute(_consts.DATA_OVERFLOW_GROUP); } }; const removeItem = (itemId)=>{ if (!overflowItems[itemId]) { return; } if (observing) { // We might be removing an item in an overflow which would not affect the tops, // but we need to update anyway to update the overflow menu state forceDispatch = true; } const item = overflowItems[itemId]; visibleItemQueue.remove(itemId); invisibleItemQueue.remove(itemId); if (item.groupId) { groupManager.removeItem(item.id, item.groupId); item.element.removeAttribute(_consts.DATA_OVERFLOW_GROUP); } sizeCache.delete(item.element); delete overflowItems[itemId]; update(); }; const disconnect = ()=>{ disposeResizeObserver(); // reset flags container = undefined; observing = false; forceDispatch = true; // clear all entries Object.keys(overflowItems).forEach((itemId)=>removeItem(itemId)); Object.keys(overflowDividers).forEach((dividerId)=>removeDivider(dividerId)); removeOverflowMenu(); sizeCache.clear(); }; return { addItem, disconnect, forceUpdate, observe, removeItem, update, addOverflowMenu, removeOverflowMenu, addDivider, removeDivider }; } const createGroupManager = ()=>{ const groupVisibility = {}; const groups = {}; function updateGroupVisibility(groupId) { const group = groups[groupId]; if (group.invisibleItemIds.size && group.visibleItemIds.size) { groupVisibility[groupId] = 'overflow'; } else if (group.visibleItemIds.size === 0) { groupVisibility[groupId] = 'hidden'; } else { groupVisibility[groupId] = 'visible'; } } function isGroupVisible(groupId) { return groupVisibility[groupId] === 'visible' || groupVisibility[groupId] === 'overflow'; } return { groupVisibility: ()=>groupVisibility, isSingleItemVisible (itemId, groupId) { return isGroupVisible(groupId) && groups[groupId].visibleItemIds.has(itemId) && groups[groupId].visibleItemIds.size === 1; }, addItem (itemId, groupId) { var _groups, _groupId; var _; (_ = (_groups = groups)[_groupId = groupId]) !== null && _ !== void 0 ? _ : _groups[_groupId] = { visibleItemIds: new Set(), invisibleItemIds: new Set() }; groups[groupId].visibleItemIds.add(itemId); updateGroupVisibility(groupId); }, removeItem (itemId, groupId) { groups[groupId].invisibleItemIds.delete(itemId); groups[groupId].visibleItemIds.delete(itemId); updateGroupVisibility(groupId); }, showItem (itemId, groupId) { groups[groupId].invisibleItemIds.delete(itemId); groups[groupId].visibleItemIds.add(itemId); updateGroupVisibility(groupId); }, hideItem (itemId, groupId) { groups[groupId].invisibleItemIds.add(itemId); groups[groupId].visibleItemIds.delete(itemId); updateGroupVisibility(groupId); } }; };