UNPKG

@tanstack/table-core

Version:

Headless UI for building powerful tables & datagrids for TS/JS.

288 lines (286 loc) 9.39 kB
import { cloneState, hasOwn, makeObjectMap } from "../../utils.js"; //#region src/features/row-expanding/rowExpandingFeature.utils.ts /** * Creates the default expanded state. * * The feature default is an empty map, meaning no rows are expanded. Reset APIs * use this value when `defaultState` is `true`. * * @example * ```ts * const expanded = getDefaultExpandedState() * ``` */ function getDefaultExpandedState() { return makeObjectMap(); } /** * Schedules an expanded-state reset after row-structure changes. * * The reset runs when `autoResetAll`, `autoResetExpanded`, or the default * client-side expanding behavior allows it. Manual expanding opts out unless * the reset options explicitly opt back in. * * @example * ```ts * table_autoResetExpanded(table) * ``` */ function table_autoResetExpanded(table) { if (!table.atoms.expanded) return; if (table.options.autoResetAll ?? table.options.autoResetExpanded ?? !table.options.manualExpanding) table._reactivity.schedule(() => table_resetExpanded(table)); } /** * Routes an expanded-state updater through the table's expanded change handler. * * The updater may be `true`, a row-id map, or a function of the previous * expanded state, matching the instance `table.setExpanded` behavior. * * @example * ```ts * table_setExpanded(table, (old) => ({ ...old, [rowId]: true })) * ``` */ function table_setExpanded(table, updater) { table.options.onExpandedChange?.(updater); } /** * Expands or collapses every row. * * Passing `true` stores the special expanded-all state. Passing `false` stores * an empty map. Omitting the value toggles based on whether all rows are * currently expanded. * * The call is a no-op (no `onExpandedChange`) when no row can expand or when * the requested state matches the current state exactly. * * @example * ```ts * table_toggleAllRowsExpanded(table) * ``` */ function table_toggleAllRowsExpanded(table, expanded) { const currentExpanded = table.atoms.expanded?.get() ?? {}; if (expanded ?? !table_getIsAllRowsExpanded(table)) { if (currentExpanded === true) return; if (!table_getCanSomeRowsExpand(table)) return; table_setExpanded(table, true); } else { if (currentExpanded !== true && !Object.keys(currentExpanded).length) return; table_setExpanded(table, makeObjectMap()); } } /** * Resets `expanded` to the configured initial state or feature default. * * With no argument, the reset clones `table.initialState.expanded` when it * exists. Passing `true` ignores initial state and resets to `{}`. * * @example * ```ts * table_resetExpanded(table) * table_resetExpanded(table, true) * ``` */ function table_resetExpanded(table, defaultState) { const initialExpanded = table.initialState.expanded; table_setExpanded(table, defaultState ? makeObjectMap() : initialExpanded === true ? true : Object.assign(makeObjectMap(), cloneState(initialExpanded ?? {}))); } /** * Checks whether at least one pre-paginated row can expand. * * Pagination is intentionally ignored so controls can reflect expandable rows * that may not be on the current page. * * @example * ```ts * const canExpand = table_getCanSomeRowsExpand(table) * ``` */ function table_getCanSomeRowsExpand(table) { return table.getPrePaginatedRowModel().flatRows.some((row) => row_getCanExpand(row)); } /** * Creates an event handler that toggles all rows expanded. * * @example * ```ts * const onClick = table_getToggleAllRowsExpandedHandler(table) * ``` */ function table_getToggleAllRowsExpandedHandler(table) { return (_e) => { table_toggleAllRowsExpanded(table); }; } /** * Checks whether any row is expanded. * * The special expanded-all value `true` counts as some rows expanded. * * @example * ```ts * const someExpanded = table_getIsSomeRowsExpanded(table) * ``` */ function table_getIsSomeRowsExpanded(table) { const expanded = table.atoms.expanded?.get() ?? {}; return expanded === true || Object.values(expanded).some(Boolean); } /** * Checks whether every expandable row in the current row model is expanded. * * The special expanded-all value `true` returns true immediately. Empty * expanded state returns false. Rows that cannot expand are ignored, so a * materialized expanded-all map (which only contains expandable row ids) * still counts as all rows expanded. * * @example * ```ts * const allExpanded = table_getIsAllRowsExpanded(table) * ``` */ function table_getIsAllRowsExpanded(table) { const expanded = table.atoms.expanded?.get() ?? {}; if (expanded === true) return true; if (!Object.keys(expanded).length) return false; const expandableRows = table.getRowModel().flatRows.filter((row) => row_getCanExpand(row)); if (!expandableRows.length) return false; if (expandableRows.some((row) => !row_getIsExpanded(row))) return false; return true; } /** * Computes the deepest expanded row id depth. * * Row ids are split on `.`; expanded-all state scans the current row model's * expandable rows, while explicit expanded state scans its expanded id keys. * * @example * ```ts * const depth = table_getExpandedDepth(table) * ``` */ function table_getExpandedDepth(table) { let maxDepth = 0; const expanded = table.atoms.expanded?.get(); (expanded === true ? Object.values(table.getRowModel().rowsById).filter((row) => row_getCanExpand(row)).map((row) => row.id) : Object.keys(expanded ?? {})).forEach((id) => { const splitId = id.split("."); maxDepth = Math.max(maxDepth, splitId.length); }); return maxDepth; } /** * Expands or collapses this row. * * Omitting `expanded` toggles the row. If the current state is expanded-all, * the function first materializes that state into a row-id map (containing * only expandable row ids) before applying the row-specific change. * * The call is a no-op (no `onExpandedChange`) when the requested state matches * the current state, or when expanding a row that cannot expand. Collapsing is * always allowed so stale expanded ids can be cleaned up. * * @example * ```ts * row_toggleExpanded(row) * ``` */ function row_toggleExpanded(row, expanded) { const currentExpanded = row.table.atoms.expanded?.get() ?? {}; const currentExists = currentExpanded === true || isExpandedRowId(currentExpanded, row.id); const targetExpanded = expanded ?? !currentExists; if (targetExpanded === currentExists) return; if (targetExpanded && !row_getCanExpand(row)) return; table_setExpanded(row.table, (old) => { const exists = old === true ? true : isExpandedRowId(old, row.id); let oldExpanded = makeObjectMap(); if (old === true) Object.values(row.table.getRowModel().rowsById).forEach((rowModelRow) => { if (row_getCanExpand(rowModelRow)) oldExpanded[rowModelRow.id] = true; }); else oldExpanded = Object.assign(makeObjectMap(), old); if (!exists && targetExpanded) { oldExpanded[row.id] = true; return oldExpanded; } if (exists && !targetExpanded) { const rest = makeObjectMap(); const rowIds = Object.keys(oldExpanded); for (let i = 0; i < rowIds.length; i++) { const rowId = rowIds[i]; if (rowId !== row.id && oldExpanded[rowId]) rest[rowId] = true; } return rest; } return old; }); } /** * Checks whether this row is expanded. * * `options.getIsRowExpanded` can override state-derived behavior. Otherwise * the row is expanded when expanded state is `true` or contains this row id. * * @example * ```ts * const expanded = row_getIsExpanded(row) * ``` */ function row_getIsExpanded(row) { const expanded = row.table.atoms.expanded?.get() ?? {}; return !!(row.table.options.getIsRowExpanded?.(row) ?? (expanded === true || isExpandedRowId(expanded, row.id))); } function isExpandedRowId(expanded, rowId) { return !!(expanded && expanded !== true && hasOwn(expanded, rowId) && expanded[rowId]); } /** * Checks whether this row can be expanded. * * `options.getRowCanExpand` wins when provided. Otherwise rows can expand when * expanding is enabled and the row has subRows. * * @example * ```ts * const canExpand = row_getCanExpand(row) * ``` */ function row_getCanExpand(row) { return row.table.options.getRowCanExpand?.(row) ?? ((row.table.options.enableExpanding ?? true) && !!row.subRows.length); } /** * Checks whether every ancestor of this row is expanded. * * The current row is not considered; only its parent chain is walked. * * @example * ```ts * const parentsExpanded = row_getIsAllParentsExpanded(row) * ``` */ function row_getIsAllParentsExpanded(row) { let isFullyExpanded = true; let currentRow = row; while (isFullyExpanded && currentRow.parentId) { currentRow = row.table.getRow(currentRow.parentId, true); isFullyExpanded = row_getIsExpanded(currentRow); } return isFullyExpanded; } /** * Creates a row control handler that toggles this row's expanded state. * * The handler is a no-op when the row cannot expand. * * @example * ```ts * const onClick = row_getToggleExpandedHandler(row) * ``` */ function row_getToggleExpandedHandler(row) { const canExpand = row_getCanExpand(row); return () => { if (!canExpand) return; row_toggleExpanded(row); }; } //#endregion export { getDefaultExpandedState, row_getCanExpand, row_getIsAllParentsExpanded, row_getIsExpanded, row_getToggleExpandedHandler, row_toggleExpanded, table_autoResetExpanded, table_getCanSomeRowsExpand, table_getExpandedDepth, table_getIsAllRowsExpanded, table_getIsSomeRowsExpanded, table_getToggleAllRowsExpandedHandler, table_resetExpanded, table_setExpanded, table_toggleAllRowsExpanded };