UNPKG

@itwin/itwinui-react

Version:

A react component library for iTwinUI

370 lines (369 loc) 11.4 kB
'use strict'; Object.defineProperty(exports, '__esModule', { value: true, }); Object.defineProperty(exports, 'useResizeColumns', { enumerable: true, get: function () { return useResizeColumns; }, }); const _reacttable = require('react-table'); const useResizeColumns = (ownerDocument) => (hooks) => { hooks.getResizerProps = [defaultGetResizerProps(ownerDocument)]; hooks.stateReducers.push(reducer); hooks.useInstanceBeforeDimensions.push(useInstanceBeforeDimensions); }; const isTouchEvent = (event) => 'touchstart' === event.type; const defaultGetResizerProps = (ownerDocument) => (props, { instance, header, nextHeader }) => { let { dispatch } = instance; let onResizeStart = (e, header) => { if (isTouchEvent(e) && e.touches && e.touches.length > 1) return; let headerIdWidths = getLeafHeaders(header).map((d) => [ d.id, getHeaderWidth(d), ]); let nextHeaderIdWidths = nextHeader ? getLeafHeaders(nextHeader).map((d) => [d.id, getHeaderWidth(d)]) : []; let clientX = isTouchEvent(e) ? Math.round(e.touches[0].clientX) : e.clientX; let dispatchMove = (clientXPos) => dispatch({ type: _reacttable.actions.columnResizing, clientX: clientXPos, }); let dispatchEnd = () => dispatch({ type: _reacttable.actions.columnDoneResizing, }); let handlersAndEvents = { mouse: { moveEvent: 'mousemove', moveHandler: (e) => dispatchMove(e.clientX), upEvent: 'mouseup', upHandler: () => { ownerDocument.current?.removeEventListener( 'mousemove', handlersAndEvents.mouse.moveHandler, ); ownerDocument.current?.removeEventListener( 'mouseup', handlersAndEvents.mouse.upHandler, ); ownerDocument.current?.removeEventListener( 'mouseleave', handlersAndEvents.mouse.upHandler, ); dispatchEnd(); }, }, touch: { moveEvent: 'touchmove', moveHandler: (e) => { if (e.cancelable) { e.preventDefault(); e.stopPropagation(); } dispatchMove(e.touches[0].clientX); }, upEvent: 'touchend', upHandler: () => { ownerDocument.current?.removeEventListener( handlersAndEvents.touch.moveEvent, handlersAndEvents.touch.moveHandler, ); ownerDocument.current?.removeEventListener( handlersAndEvents.touch.upEvent, handlersAndEvents.touch.moveHandler, ); dispatchEnd(); }, }, }; let events = isTouchEvent(e) ? handlersAndEvents.touch : handlersAndEvents.mouse; let passiveIfSupported = passiveEventSupported() ? { passive: false, } : false; ownerDocument.current?.addEventListener( events.moveEvent, events.moveHandler, passiveIfSupported, ); ownerDocument.current?.addEventListener( events.upEvent, events.upHandler, passiveIfSupported, ); if (!isTouchEvent(e)) ownerDocument.current?.addEventListener( 'mouseleave', handlersAndEvents.mouse.upHandler, passiveIfSupported, ); dispatch({ type: _reacttable.actions.columnStartResizing, columnId: header.id, columnWidth: getHeaderWidth(header), nextColumnWidth: getHeaderWidth(nextHeader), headerIdWidths, nextHeaderIdWidths, clientX, }); }; return [ props, { onClick: (e) => { e.stopPropagation(); }, onMouseDown: (e) => { e.persist(); e.preventDefault(); e.stopPropagation(); onResizeStart(e, header); }, onTouchStart: (e) => { e.persist(); e.preventDefault(); onResizeStart(e, header); }, style: { cursor: 'col-resize', }, draggable: false, role: 'separator', }, ]; }; useResizeColumns.pluginName = 'useResizeColumns'; const reducer = (newState, action, previousState, instance) => { if (action.type === _reacttable.actions.init) return { ...newState, columnResizing: { columnWidths: {}, }, }; if (action.type === _reacttable.actions.resetResize) return { ...newState, columnResizing: { columnWidths: {}, }, }; if (action.type === _reacttable.actions.columnStartResizing) { let { clientX, columnId, columnWidth, nextColumnWidth, headerIdWidths, nextHeaderIdWidths, } = action; return { ...newState, columnResizing: { ...newState.columnResizing, startX: clientX, columnWidth, nextColumnWidth, headerIdWidths, nextHeaderIdWidths, isResizingColumn: columnId, }, }; } if (action.type === _reacttable.actions.columnResizing) { let { clientX } = action; let { startX = 0, columnWidth = 1, nextColumnWidth = 1, headerIdWidths = [], nextHeaderIdWidths = [], } = newState.columnResizing; if (!instance) return newState; let deltaX = clientX - startX; let newColumnWidths = getColumnWidths(headerIdWidths, deltaX / columnWidth); let isTableWidthDecreasing = calculateTableWidth(newColumnWidths, instance.flatHeaders) < instance.tableWidth; let newNextColumnWidths = instance?.columnResizeMode === 'fit' || (instance?.columnResizeMode === 'expand' && isTableWidthDecreasing) ? getColumnWidths(nextHeaderIdWidths, -deltaX / nextColumnWidth) : {}; if ( !isNewColumnWidthsValid(newColumnWidths, instance.flatHeaders) || !isNewColumnWidthsValid(newNextColumnWidths, instance.flatHeaders) || !isNewTableWidthValid( { ...newColumnWidths, ...newNextColumnWidths, }, instance, ) ) return newState; instance?.flatHeaders.forEach((h) => { if (!h.width) h.width = h.resizeWidth; }); return { ...newState, columnResizing: { ...newState.columnResizing, columnWidths: { ...newState.columnResizing.columnWidths, ...newColumnWidths, ...newNextColumnWidths, }, }, }; } if (action.type === _reacttable.actions.columnDoneResizing) return { ...newState, columnResizing: { ...newState.columnResizing, startX: void 0, isResizingColumn: void 0, }, }; return newState; }; const getColumnWidths = (headerIdWidths, deltaPercentage) => { let columnWidths = {}; headerIdWidths.forEach(([headerId, headerWidth]) => { columnWidths[headerId] = Math.max( headerWidth + headerWidth * deltaPercentage, 0, ); }); return columnWidths; }; const isNewColumnWidthsValid = (columnWidths, headers) => { if (Object.values(columnWidths).some((width) => width <= 1)) return false; for (let [headerId, width] of Object.entries(columnWidths)) { let header = headers?.find((h) => h.id === headerId); if (!header) continue; let minWidth = header.minWidth || 0; let maxWidth = header.maxWidth || 1 / 0; if (width < minWidth || width > maxWidth) return false; } return true; }; const isNewTableWidthValid = (columnWidths, instance) => { if ('fit' === instance.columnResizeMode) return true; let newTableWidth = 0; for (let header of instance.flatHeaders) newTableWidth += columnWidths[header.id] ? columnWidths[header.id] : getHeaderWidth(header); if (Math.round(newTableWidth) < instance.tableWidth) return false; return true; }; const useInstanceBeforeDimensions = (instance) => { let { flatHeaders, getHooks, state: { columnResizing }, columnResizeMode, } = instance; let getInstance = (0, _reacttable.useGetLatest)(instance); flatHeaders.forEach((header, index) => { let resizeWidth = columnResizing.columnWidths[header.id]; header.width = resizeWidth || header.width || header.originalWidth; header.isResizing = columnResizing.isResizingColumn === header.id; let headerToResize = header.disableResizing && 'fit' === columnResizeMode ? getPreviousResizableHeader(header, instance) : header; let nextResizableHeader = 'expand' === columnResizeMode && index === flatHeaders.length - 1 ? getPreviousResizableHeader(header, instance) : getNextResizableHeader(header, instance); header.canResize = null != header.disableResizing ? !header.disableResizing : true; if ('fit' === columnResizeMode) header.isResizerVisible = (header.canResize && !!nextResizableHeader) || (headerToResize && !!instance.flatHeaders[index + 1]?.canResize); else header.isResizerVisible = header.canResize && !!headerToResize; header.getResizerProps = (0, _reacttable.makePropGetter)( getHooks().getResizerProps, { instance: getInstance(), header: headerToResize, nextHeader: nextResizableHeader, }, ); }); }; const getPreviousResizableHeader = (headerColumn, instance) => { let headersList = ( headerColumn.parent?.columns || instance.flatHeaders ).filter(({ isVisible }) => isVisible); let headerIndex = headersList.findIndex((h) => h.id === headerColumn.id); return [...headersList] .slice(0, headerIndex) .reverse() .find((h) => !h.disableResizing); }; const getNextResizableHeader = (headerColumn, instance) => { let headersList = ( headerColumn.parent?.columns || instance.flatHeaders ).filter(({ isVisible }) => isVisible); let headerIndex = headersList.findIndex((h) => h.id === headerColumn.id); return [...headersList] .slice(headerIndex + 1) .find((h) => !h.disableResizing); }; function getLeafHeaders(header) { let leafHeaders = []; let recurseHeader = (header) => { if (header.columns && header.columns.length) header.columns.map(recurseHeader); leafHeaders.push(header); }; recurseHeader(header); return leafHeaders; } const getHeaderWidth = (header) => { if (!header) return 0; return 'string' == typeof header.width && Number.isNaN(Number(header.width)) ? Number(header.resizeWidth || 0) : Number(header.width || header.resizeWidth || 0); }; const calculateTableWidth = (columnWidths, headers) => { let newTableWidth = 0; for (let header of headers) newTableWidth += columnWidths[header.id] ? columnWidths[header.id] : getHeaderWidth(header); return newTableWidth; }; let passiveSupported = null; const passiveEventSupported = () => { if (null != passiveSupported) return passiveSupported; try { let options = { once: true, get passive() { passiveSupported = true; return false; }, }; window.addEventListener('test', () => {}, options); } catch { passiveSupported = false; } return passiveSupported; };