wip:milestone 0 fixes
Some checks failed
CI/CD Pipeline / unit-tests (push) Failing after 1m16s
CI/CD Pipeline / integration-tests (push) Failing after 2m32s
CI/CD Pipeline / lint (push) Successful in 5m22s
CI/CD Pipeline / e2e-tests (push) Has been skipped
CI/CD Pipeline / build (push) Has been skipped

This commit is contained in:
2026-03-15 12:35:42 +02:00
parent 6708cf28a7
commit cffdf8af86
61266 changed files with 4511646 additions and 1938 deletions

View File

@@ -0,0 +1,6 @@
export * from './useGridApiEventHandler';
export * from './useGridApiMethod';
export * from './useGridLogger';
export { useGridSelector } from './useGridSelector';
export * from './useGridNativeEventListener';
export * from './useFirstRender';

View File

@@ -0,0 +1,8 @@
import * as React from 'react';
export const useFirstRender = callback => {
const isFirstRender = React.useRef(true);
if (isFirstRender.current) {
isFirstRender.current = false;
callback();
}
};

View File

@@ -0,0 +1,9 @@
import * as React from 'react';
import { GridApiContext } from '../../components/GridApiContext';
export function useGridApiContext() {
const apiRef = React.useContext(GridApiContext);
if (apiRef === undefined) {
throw new Error(['MUI: Could not find the data grid context.', 'It looks like you rendered your component outside of a DataGrid, DataGridPro or DataGridPremium parent component.', 'This can also happen if you are bundling multiple versions of the data grid.'].join('\n'));
}
return apiRef;
}

View File

@@ -0,0 +1,95 @@
import * as React from 'react';
import { TimerBasedCleanupTracking } from '../../utils/cleanupTracking/TimerBasedCleanupTracking';
import { FinalizationRegistryBasedCleanupTracking } from '../../utils/cleanupTracking/FinalizationRegistryBasedCleanupTracking';
/**
* Signal to the underlying logic what version of the public component API
* of the data grid is exposed.
*/
var GridSignature = /*#__PURE__*/function (GridSignature) {
GridSignature["DataGrid"] = "DataGrid";
GridSignature["DataGridPro"] = "DataGridPro";
return GridSignature;
}(GridSignature || {});
// We use class to make it easier to detect in heap snapshots by name
class ObjectToBeRetainedByReact {}
// Based on https://github.com/Bnaya/use-dispose-uncommitted/blob/main/src/finalization-registry-based-impl.ts
// Check https://github.com/facebook/react/issues/15317 to get more information
export function createUseGridApiEventHandler(registryContainer) {
let cleanupTokensCounter = 0;
return function useGridApiEventHandler(apiRef, eventName, handler, options) {
if (registryContainer.registry === null) {
registryContainer.registry = typeof FinalizationRegistry !== 'undefined' ? new FinalizationRegistryBasedCleanupTracking() : new TimerBasedCleanupTracking();
}
const [objectRetainedByReact] = React.useState(new ObjectToBeRetainedByReact());
const subscription = React.useRef(null);
const handlerRef = React.useRef();
handlerRef.current = handler;
const cleanupTokenRef = React.useRef(null);
if (!subscription.current && handlerRef.current) {
const enhancedHandler = (params, event, details) => {
if (!event.defaultMuiPrevented) {
handlerRef.current?.(params, event, details);
}
};
subscription.current = apiRef.current.subscribeEvent(eventName, enhancedHandler, options);
cleanupTokensCounter += 1;
cleanupTokenRef.current = {
cleanupToken: cleanupTokensCounter
};
registryContainer.registry.register(objectRetainedByReact,
// The callback below will be called once this reference stops being retained
() => {
subscription.current?.();
subscription.current = null;
cleanupTokenRef.current = null;
}, cleanupTokenRef.current);
} else if (!handlerRef.current && subscription.current) {
subscription.current();
subscription.current = null;
if (cleanupTokenRef.current) {
registryContainer.registry.unregister(cleanupTokenRef.current);
cleanupTokenRef.current = null;
}
}
React.useEffect(() => {
if (!subscription.current && handlerRef.current) {
const enhancedHandler = (params, event, details) => {
if (!event.defaultMuiPrevented) {
handlerRef.current?.(params, event, details);
}
};
subscription.current = apiRef.current.subscribeEvent(eventName, enhancedHandler, options);
}
if (cleanupTokenRef.current && registryContainer.registry) {
// If the effect was called, it means that this render was committed
// so we can trust the cleanup function to remove the listener.
registryContainer.registry.unregister(cleanupTokenRef.current);
cleanupTokenRef.current = null;
}
return () => {
subscription.current?.();
subscription.current = null;
};
}, [apiRef, eventName, options]);
};
}
const registryContainer = {
registry: null
};
// TODO: move to @mui/x-data-grid/internals
// eslint-disable-next-line @typescript-eslint/naming-convention
export const unstable_resetCleanupTracking = () => {
registryContainer.registry?.reset();
registryContainer.registry = null;
};
export const useGridApiEventHandler = createUseGridApiEventHandler(registryContainer);
const optionsSubscriberOptions = {
isFirst: true
};
export function useGridApiOptionHandler(apiRef, eventName, handler) {
// Validate that only one per event name?
useGridApiEventHandler(apiRef, eventName, handler, optionsSubscriberOptions);
}
export { GridSignature };

View File

@@ -0,0 +1,11 @@
import * as React from 'react';
export function useGridApiMethod(privateApiRef, apiMethods, visibility) {
const isFirstRender = React.useRef(true);
React.useEffect(() => {
isFirstRender.current = false;
privateApiRef.current.register(visibility, apiMethods);
}, [privateApiRef, visibility, apiMethods]);
if (isFirstRender.current) {
privateApiRef.current.register(visibility, apiMethods);
}
}

View File

@@ -0,0 +1,5 @@
import * as React from 'react';
/**
* Hook that instantiate a [[GridApiRef]].
*/
export const useGridApiRef = () => React.useRef({});

View File

@@ -0,0 +1,24 @@
import { gridVisibleColumnDefinitionsSelector } from '../features/columns/gridColumnsSelector';
import { useGridSelector } from './useGridSelector';
import { useGridRootProps } from './useGridRootProps';
import { gridColumnGroupsHeaderMaxDepthSelector } from '../features/columnGrouping/gridColumnGroupsSelector';
import { gridPinnedRowsCountSelector, gridRowCountSelector } from '../features/rows/gridRowsSelector';
import { useGridPrivateApiContext } from './useGridPrivateApiContext';
export const useGridAriaAttributes = () => {
const apiRef = useGridPrivateApiContext();
const rootProps = useGridRootProps();
const visibleColumns = useGridSelector(apiRef, gridVisibleColumnDefinitionsSelector);
const totalRowCount = useGridSelector(apiRef, gridRowCountSelector);
const headerGroupingMaxDepth = useGridSelector(apiRef, gridColumnGroupsHeaderMaxDepthSelector);
const pinnedRowsCount = useGridSelector(apiRef, gridPinnedRowsCountSelector);
let role = 'grid';
if (rootProps.experimentalFeatures?.ariaV7 && rootProps.treeData) {
role = 'treegrid';
}
return {
role,
'aria-colcount': visibleColumns.length,
'aria-rowcount': headerGroupingMaxDepth + 1 + pinnedRowsCount + totalRowCount,
'aria-multiselectable': !rootProps.disableMultipleRowSelection
};
};

View File

@@ -0,0 +1,8 @@
import * as React from 'react';
export const useGridInitializeState = (initializer, privateApiRef, props) => {
const isInitialized = React.useRef(false);
if (!isInitialized.current) {
privateApiRef.current.state = initializer(privateApiRef.current.state, props, privateApiRef);
isInitialized.current = true;
}
};

View File

@@ -0,0 +1,10 @@
import * as React from 'react';
export function useGridLogger(privateApiRef, name) {
const logger = React.useRef(null);
if (logger.current) {
return logger.current;
}
const newLogger = privateApiRef.current.getLogger(name);
logger.current = newLogger;
return newLogger;
}

View File

@@ -0,0 +1,33 @@
import * as React from 'react';
import { isFunction } from '../../utils/utils';
import { useGridLogger } from './useGridLogger';
export const useGridNativeEventListener = (apiRef, ref, eventName, handler, options) => {
const logger = useGridLogger(apiRef, 'useNativeEventListener');
const [added, setAdded] = React.useState(false);
const handlerRef = React.useRef(handler);
const wrapHandler = React.useCallback(event => {
return handlerRef.current && handlerRef.current(event);
}, []);
React.useEffect(() => {
handlerRef.current = handler;
}, [handler]);
React.useEffect(() => {
let targetElement;
if (isFunction(ref)) {
targetElement = ref();
} else {
targetElement = ref && ref.current ? ref.current : null;
}
if (targetElement && eventName && !added) {
logger.debug(`Binding native ${eventName} event`);
targetElement.addEventListener(eventName, wrapHandler, options);
const boundElem = targetElement;
setAdded(true);
const unsubscribe = () => {
logger.debug(`Clearing native ${eventName} event`);
boundElem.removeEventListener(eventName, wrapHandler, options);
};
apiRef.current.subscribeEvent('unmount', unsubscribe);
}
}, [ref, wrapHandler, eventName, added, logger, options, apiRef]);
};

View File

@@ -0,0 +1,12 @@
import * as React from 'react';
export const GridPrivateApiContext = /*#__PURE__*/React.createContext(undefined);
if (process.env.NODE_ENV !== 'production') {
GridPrivateApiContext.displayName = 'GridPrivateApiContext';
}
export function useGridPrivateApiContext() {
const privateApiRef = React.useContext(GridPrivateApiContext);
if (privateApiRef === undefined) {
throw new Error(['MUI: Could not find the data grid private context.', 'It looks like you rendered your component outside of a DataGrid, DataGridPro or DataGridPremium parent component.', 'This can also happen if you are bundling multiple versions of the data grid.'].join('\n'));
}
return privateApiRef;
}

View File

@@ -0,0 +1,9 @@
import * as React from 'react';
import { GridRootPropsContext } from '../../context/GridRootPropsContext';
export const useGridRootProps = () => {
const contextValue = React.useContext(GridRootPropsContext);
if (!contextValue) {
throw new Error('MUI: useGridRootProps should only be used inside the DataGrid, DataGridPro or DataGridPremium component.');
}
return contextValue;
};

View File

@@ -0,0 +1,47 @@
import * as React from 'react';
import { useLazyRef } from './useLazyRef';
import { useOnMount } from './useOnMount';
import { buildWarning } from '../../utils/warning';
import { fastObjectShallowCompare } from '../../utils/fastObjectShallowCompare';
const stateNotInitializedWarning = buildWarning(['MUI: `useGridSelector` has been called before the initialization of the state.', 'This hook can only be used inside the context of the grid.']);
function isOutputSelector(selector) {
return selector.acceptsApiRef;
}
function applySelector(apiRef, selector) {
if (isOutputSelector(selector)) {
return selector(apiRef);
}
return selector(apiRef.current.state);
}
const defaultCompare = Object.is;
export const objectShallowCompare = fastObjectShallowCompare;
const createRefs = () => ({
state: null,
equals: null,
selector: null
});
export const useGridSelector = (apiRef, selector, equals = defaultCompare) => {
if (process.env.NODE_ENV !== 'production') {
if (!apiRef.current.state) {
stateNotInitializedWarning();
}
}
const refs = useLazyRef(createRefs);
const didInit = refs.current.selector !== null;
const [state, setState] = React.useState(
// We don't use an initialization function to avoid allocations
didInit ? null : applySelector(apiRef, selector));
refs.current.state = state;
refs.current.equals = equals;
refs.current.selector = selector;
useOnMount(() => {
return apiRef.current.store.subscribe(() => {
const newState = applySelector(apiRef, refs.current.selector);
if (!refs.current.equals(refs.current.state, newState)) {
refs.current.state = newState;
setState(newState);
}
});
});
return state;
};

View File

@@ -0,0 +1,40 @@
import * as React from 'react';
import { gridPaginationRowRangeSelector, gridPaginatedVisibleSortedGridRowEntriesSelector } from '../features/pagination/gridPaginationSelector';
import { gridExpandedSortedRowEntriesSelector } from '../features/filter/gridFilterSelector';
export const getVisibleRows = (apiRef, props) => {
let rows;
let range;
if (props.pagination && props.paginationMode === 'client') {
range = gridPaginationRowRangeSelector(apiRef);
rows = gridPaginatedVisibleSortedGridRowEntriesSelector(apiRef);
} else {
rows = gridExpandedSortedRowEntriesSelector(apiRef);
if (rows.length === 0) {
range = null;
} else {
range = {
firstRowIndex: 0,
lastRowIndex: rows.length - 1
};
}
}
return {
rows,
range
};
};
/**
* Computes the list of rows that are reachable by scroll.
* Depending on whether pagination is enabled, it will return the rows in the current page.
* - If the pagination is disabled or in server mode, it equals all the visible rows.
* - If the row tree has several layers, it contains up to `state.pageSize` top level rows and all their descendants.
* - If the row tree is flat, it only contains up to `state.pageSize` rows.
*/
export const useGridVisibleRows = (apiRef, props) => {
const response = getVisibleRows(apiRef, props);
return React.useMemo(() => ({
rows: response.rows,
range: response.range
}), [response.rows, response.range]);
};

View File

@@ -0,0 +1,11 @@
import * as React from 'react';
const UNINITIALIZED = {};
// See https://github.com/facebook/react/issues/14490 for when to use this.
export function useLazyRef(init, initArg) {
const ref = React.useRef(UNINITIALIZED);
if (ref.current === UNINITIALIZED) {
ref.current = init(initArg);
}
return ref;
}

View File

@@ -0,0 +1,7 @@
import * as React from 'react';
const EMPTY = [];
export function useOnMount(fn) {
/* eslint-disable react-hooks/exhaustive-deps */
React.useEffect(fn, EMPTY);
/* eslint-enable react-hooks/exhaustive-deps */
}

View File

@@ -0,0 +1,28 @@
import { useLazyRef } from './useLazyRef';
import { useOnMount } from './useOnMount';
class Timeout {
constructor() {
this.currentId = 0;
this.clear = () => {
if (this.currentId !== 0) {
clearTimeout(this.currentId);
this.currentId = 0;
}
};
this.disposeEffect = () => {
return this.clear;
};
}
static create() {
return new Timeout();
}
start(delay, fn) {
this.clear();
this.currentId = setTimeout(fn, delay);
}
}
export function useTimeout() {
const timeout = useLazyRef(Timeout.create).current;
useOnMount(timeout.disposeEffect);
return timeout;
}