@itwin/itwinui-react
Version:
A react component library for iTwinUI
370 lines (369 loc) • 11.4 kB
JavaScript
;
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;
};