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,29 @@
export type EventListener = (...args: any[]) => void;
export interface EventListenerOptions {
isFirst?: boolean;
}
interface EventListenerCollection {
/**
* List of listeners to run before the others
* They are run in the opposite order of the registration order
*/
highPriority: Map<EventListener, true>;
/**
* List of events to run after the high priority listeners
* They are run in the registration order
*/
regular: Map<EventListener, true>;
}
export declare class EventManager {
maxListeners: number;
warnOnce: boolean;
events: {
[eventName: string]: EventListenerCollection;
};
on(eventName: string, listener: EventListener, options?: EventListenerOptions): void;
removeListener(eventName: string, listener: EventListener): void;
removeAllListeners(): void;
emit(eventName: string, ...args: any[]): void;
once(eventName: string, listener: EventListener): void;
}
export {};

View File

@@ -0,0 +1,69 @@
// Used https://gist.github.com/mudge/5830382 as a starting point.
// See https://github.com/browserify/events/blob/master/events.js for
// the Node.js (https://nodejs.org/api/events.html) polyfill used by webpack.
export class EventManager {
constructor() {
this.maxListeners = 20;
this.warnOnce = false;
this.events = {};
}
on(eventName, listener, options = {}) {
let collection = this.events[eventName];
if (!collection) {
collection = {
highPriority: new Map(),
regular: new Map()
};
this.events[eventName] = collection;
}
if (options.isFirst) {
collection.highPriority.set(listener, true);
} else {
collection.regular.set(listener, true);
}
if (process.env.NODE_ENV !== 'production') {
const collectionSize = collection.highPriority.size + collection.regular.size;
if (collectionSize > this.maxListeners && !this.warnOnce) {
this.warnOnce = true;
console.warn([`Possible EventEmitter memory leak detected. ${collectionSize} ${eventName} listeners added.`].join('\n'));
}
}
}
removeListener(eventName, listener) {
if (this.events[eventName]) {
this.events[eventName].regular.delete(listener);
this.events[eventName].highPriority.delete(listener);
}
}
removeAllListeners() {
this.events = {};
}
emit(eventName, ...args) {
const collection = this.events[eventName];
if (!collection) {
return;
}
const highPriorityListeners = Array.from(collection.highPriority.keys());
const regularListeners = Array.from(collection.regular.keys());
for (let i = highPriorityListeners.length - 1; i >= 0; i -= 1) {
const listener = highPriorityListeners[i];
if (collection.highPriority.has(listener)) {
listener.apply(this, args);
}
}
for (let i = 0; i < regularListeners.length; i += 1) {
const listener = regularListeners[i];
if (collection.regular.has(listener)) {
listener.apply(this, args);
}
}
}
once(eventName, listener) {
// eslint-disable-next-line consistent-this
const that = this;
this.on(eventName, function oneTimeListener(...args) {
that.removeListener(eventName, oneTimeListener);
listener.apply(that, args);
});
}
}

View File

@@ -0,0 +1,11 @@
type Listener<T> = (value: T) => void;
export declare class Store<T> {
value: T;
listeners: Set<Listener<T>>;
static create<T>(value: T): Store<T>;
constructor(value: T);
subscribe: (fn: Listener<T>) => () => void;
getSnapshot: () => T;
update: (value: T) => void;
}
export {};

View File

@@ -0,0 +1,24 @@
export class Store {
static create(value) {
return new Store(value);
}
constructor(_value) {
this.value = void 0;
this.listeners = void 0;
this.subscribe = fn => {
this.listeners.add(fn);
return () => {
this.listeners.delete(fn);
};
};
this.getSnapshot = () => {
return this.value;
};
this.update = value => {
this.value = value;
this.listeners.forEach(l => l(value));
};
this.value = _value;
this.listeners = new Set();
}
}

View File

@@ -0,0 +1,9 @@
export type UnregisterToken = {
cleanupToken: number;
};
export type UnsubscribeFn = () => void;
export interface CleanupTracking {
register(object: any, unsubscribe: UnsubscribeFn, unregisterToken: UnregisterToken): void;
unregister(unregisterToken: UnregisterToken): void;
reset(): void;
}

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,7 @@
import { CleanupTracking, UnsubscribeFn, UnregisterToken } from './CleanupTracking';
export declare class FinalizationRegistryBasedCleanupTracking implements CleanupTracking {
registry: FinalizationRegistry<UnsubscribeFn>;
register(object: any, unsubscribe: UnsubscribeFn, unregisterToken: UnregisterToken): void;
unregister(unregisterToken: UnregisterToken): void;
reset(): void;
}

View File

@@ -0,0 +1,18 @@
export class FinalizationRegistryBasedCleanupTracking {
constructor() {
this.registry = new FinalizationRegistry(unsubscribe => {
if (typeof unsubscribe === 'function') {
unsubscribe();
}
});
}
register(object, unsubscribe, unregisterToken) {
this.registry.register(object, unsubscribe, unregisterToken);
}
unregister(unregisterToken) {
this.registry.unregister(unregisterToken);
}
// eslint-disable-next-line class-methods-use-this
reset() {}
}

View File

@@ -0,0 +1,10 @@
/// <reference types="node" />
import { CleanupTracking, UnregisterToken, UnsubscribeFn } from './CleanupTracking';
export declare class TimerBasedCleanupTracking implements CleanupTracking {
timeouts?: Map<number, NodeJS.Timeout> | undefined;
cleanupTimeout: number;
constructor(timeout?: number);
register(object: any, unsubscribe: UnsubscribeFn, unregisterToken: UnregisterToken): void;
unregister(unregisterToken: UnregisterToken): void;
reset(): void;
}

View File

@@ -0,0 +1,38 @@
// If no effect ran after this amount of time, we assume that the render was not committed by React
const CLEANUP_TIMER_LOOP_MILLIS = 1000;
export class TimerBasedCleanupTracking {
constructor(timeout = CLEANUP_TIMER_LOOP_MILLIS) {
this.timeouts = new Map();
this.cleanupTimeout = CLEANUP_TIMER_LOOP_MILLIS;
this.cleanupTimeout = timeout;
}
register(object, unsubscribe, unregisterToken) {
if (!this.timeouts) {
this.timeouts = new Map();
}
const timeout = setTimeout(() => {
if (typeof unsubscribe === 'function') {
unsubscribe();
}
this.timeouts.delete(unregisterToken.cleanupToken);
}, this.cleanupTimeout);
this.timeouts.set(unregisterToken.cleanupToken, timeout);
}
unregister(unregisterToken) {
const timeout = this.timeouts.get(unregisterToken.cleanupToken);
if (timeout) {
this.timeouts.delete(unregisterToken.cleanupToken);
clearTimeout(timeout);
}
}
reset() {
if (this.timeouts) {
this.timeouts.forEach((value, key) => {
this.unregister({
cleanupToken: key
});
});
this.timeouts = undefined;
}
}
}

View File

@@ -0,0 +1,5 @@
export type ControllablePromise<T = unknown> = Promise<T> & {
resolve: T extends unknown ? (value?: T) => void : (value: T) => void;
reject: (reason?: any) => void;
};
export declare function createControllablePromise(): ControllablePromise<unknown>;

View File

@@ -0,0 +1,11 @@
export function createControllablePromise() {
let resolve;
let reject;
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve;
reject = _reject;
});
promise.resolve = resolve;
promise.reject = reject;
return promise;
}

View File

@@ -0,0 +1,24 @@
import * as React from 'react';
import { Selector, SelectorResultArray } from 'reselect';
import type { GridCoreApi } from '../models/api/gridCoreApi';
export interface OutputSelector<State, Result> {
(apiRef: React.MutableRefObject<{
state: State;
instanceId: GridCoreApi['instanceId'];
}>): Result;
(state: State, instanceId?: GridCoreApi['instanceId']): Result;
acceptsApiRef: boolean;
}
type StateFromSelector<T> = T extends (first: infer F, ...args: any[]) => any ? F extends {
state: infer F2;
} ? F2 : F : never;
type StateFromSelectorList<Selectors extends readonly any[]> = Selectors extends [
f: infer F,
...rest: infer R
] ? StateFromSelector<F> extends StateFromSelectorList<R> ? StateFromSelector<F> : StateFromSelectorList<R> : {};
type SelectorArgs<Selectors extends ReadonlyArray<Selector<any>>, Result> = [selectors: [...Selectors], combiner: (...args: SelectorResultArray<Selectors>) => Result] | [...Selectors, (...args: SelectorResultArray<Selectors>) => Result];
type CreateSelectorFunction = <Selectors extends ReadonlyArray<Selector<any>>, Result>(...items: SelectorArgs<Selectors, Result>) => OutputSelector<StateFromSelectorList<Selectors>, Result>;
export declare const createSelector: CreateSelectorFunction;
export declare const createSelectorMemoized: CreateSelectorFunction;
export declare const unstable_resetCreateSelectorCache: () => void;
export {};

View File

@@ -0,0 +1,115 @@
import { createSelector as reselectCreateSelector } from 'reselect';
import { buildWarning } from './warning';
const cacheContainer = {
cache: new WeakMap()
};
const missingInstanceIdWarning = buildWarning(['MUI: A selector was called without passing the instance ID, which may impact the performance of the grid.', 'To fix, call it with `apiRef`, e.g. `mySelector(apiRef)`, or pass the instance ID explicitly, e.g. `mySelector(state, apiRef.current.instanceId)`.']);
function checkIsAPIRef(value) {
return 'current' in value && 'instanceId' in value.current;
}
const DEFAULT_INSTANCE_ID = {
id: 'default'
};
export const createSelector = (a, b, c, d, e, f, ...rest) => {
if (rest.length > 0) {
throw new Error('Unsupported number of selectors');
}
let selector;
if (a && b && c && d && e && f) {
selector = (stateOrApiRef, instanceIdParam) => {
const isAPIRef = checkIsAPIRef(stateOrApiRef);
const instanceId = instanceIdParam != null ? instanceIdParam : isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID;
const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef;
const va = a(state, instanceId);
const vb = b(state, instanceId);
const vc = c(state, instanceId);
const vd = d(state, instanceId);
const ve = e(state, instanceId);
return f(va, vb, vc, vd, ve);
};
} else if (a && b && c && d && e) {
selector = (stateOrApiRef, instanceIdParam) => {
const isAPIRef = checkIsAPIRef(stateOrApiRef);
const instanceId = instanceIdParam != null ? instanceIdParam : isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID;
const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef;
const va = a(state, instanceId);
const vb = b(state, instanceId);
const vc = c(state, instanceId);
const vd = d(state, instanceId);
return e(va, vb, vc, vd);
};
} else if (a && b && c && d) {
selector = (stateOrApiRef, instanceIdParam) => {
const isAPIRef = checkIsAPIRef(stateOrApiRef);
const instanceId = instanceIdParam != null ? instanceIdParam : isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID;
const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef;
const va = a(state, instanceId);
const vb = b(state, instanceId);
const vc = c(state, instanceId);
return d(va, vb, vc);
};
} else if (a && b && c) {
selector = (stateOrApiRef, instanceIdParam) => {
const isAPIRef = checkIsAPIRef(stateOrApiRef);
const instanceId = instanceIdParam != null ? instanceIdParam : isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID;
const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef;
const va = a(state, instanceId);
const vb = b(state, instanceId);
return c(va, vb);
};
} else if (a && b) {
selector = (stateOrApiRef, instanceIdParam) => {
const isAPIRef = checkIsAPIRef(stateOrApiRef);
const instanceId = instanceIdParam != null ? instanceIdParam : isAPIRef ? stateOrApiRef.current.instanceId : DEFAULT_INSTANCE_ID;
const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef;
const va = a(state, instanceId);
return b(va);
};
} else {
throw new Error('Missing arguments');
}
// We use this property to detect if the selector was created with createSelector
// or it's only a simple function the receives the state and returns part of it.
selector.acceptsApiRef = true;
return selector;
};
export const createSelectorMemoized = (...args) => {
const selector = (...selectorArgs) => {
var _cache$get, _cache$get3;
const [stateOrApiRef, instanceId] = selectorArgs;
const isAPIRef = checkIsAPIRef(stateOrApiRef);
const cacheKey = isAPIRef ? stateOrApiRef.current.instanceId : instanceId != null ? instanceId : DEFAULT_INSTANCE_ID;
const state = isAPIRef ? stateOrApiRef.current.state : stateOrApiRef;
if (process.env.NODE_ENV !== 'production') {
if (cacheKey.id === 'default') {
missingInstanceIdWarning();
}
}
const {
cache
} = cacheContainer;
if (cache.get(cacheKey) && (_cache$get = cache.get(cacheKey)) != null && _cache$get.get(args)) {
var _cache$get2;
// We pass the cache key because the called selector might have as
// dependency another selector created with this `createSelector`.
return (_cache$get2 = cache.get(cacheKey)) == null ? void 0 : _cache$get2.get(args)(state, cacheKey);
}
const newSelector = reselectCreateSelector(...args);
if (!cache.get(cacheKey)) {
cache.set(cacheKey, new Map());
}
(_cache$get3 = cache.get(cacheKey)) == null || _cache$get3.set(args, newSelector);
return newSelector(state, cacheKey);
};
// We use this property to detect if the selector was created with createSelector
// or it's only a simple function the receives the state and returns part of it.
selector.acceptsApiRef = true;
return selector;
};
// eslint-disable-next-line @typescript-eslint/naming-convention
export const unstable_resetCreateSelectorCache = () => {
cacheContainer.cache = new WeakMap();
};

View File

@@ -0,0 +1 @@
export declare function doesSupportPreventScroll(): boolean;

View File

@@ -0,0 +1,13 @@
// Based on https://stackoverflow.com/a/59518678
let cachedSupportsPreventScroll;
export function doesSupportPreventScroll() {
if (cachedSupportsPreventScroll === undefined) {
document.createElement('div').focus({
get preventScroll() {
cachedSupportsPreventScroll = true;
return false;
}
});
}
return cachedSupportsPreventScroll;
}

View File

@@ -0,0 +1,15 @@
/// <reference types="react" />
import { GridRowId } from '../models/gridRows';
export declare function isOverflown(element: Element): boolean;
export declare function findParentElementFromClassName(elem: Element, className: string): Element | null;
export declare function getRowEl(cell?: Element | null): HTMLElement | null;
export declare function isGridCellRoot(elem: Element | null): boolean;
export declare function isGridHeaderCellRoot(elem: Element | null): boolean;
export declare function getGridColumnHeaderElement(root: Element, field: string): HTMLDivElement | null;
export declare function getGridRowElement(root: Element, id: GridRowId): HTMLDivElement | null;
export declare function getGridCellElement(root: Element, { id, field }: {
id: GridRowId;
field: string;
}): HTMLDivElement | null;
export declare const getActiveElement: (root?: Document | ShadowRoot) => Element | null;
export declare function isEventTargetInPortal(event: React.SyntheticEvent): boolean;

View File

@@ -0,0 +1,63 @@
import { gridClasses } from '../constants/gridClasses';
export function isOverflown(element) {
return element.scrollHeight > element.clientHeight || element.scrollWidth > element.clientWidth;
}
export function findParentElementFromClassName(elem, className) {
return elem.closest(`.${className}`);
}
export function getRowEl(cell) {
if (!cell) {
return null;
}
return findParentElementFromClassName(cell, gridClasses.row);
}
// TODO remove
export function isGridCellRoot(elem) {
return elem != null && elem.classList.contains(gridClasses.cell);
}
export function isGridHeaderCellRoot(elem) {
return elem != null && elem.classList.contains(gridClasses.columnHeader);
}
function escapeOperandAttributeSelector(operand) {
return operand.replace(/["\\]/g, '\\$&');
}
export function getGridColumnHeaderElement(root, field) {
return root.querySelector(`[role="columnheader"][data-field="${escapeOperandAttributeSelector(field)}"]`);
}
function getGridRowElementSelector(id) {
return `.${gridClasses.row}[data-id="${escapeOperandAttributeSelector(String(id))}"]`;
}
export function getGridRowElement(root, id) {
return root.querySelector(getGridRowElementSelector(id));
}
export function getGridCellElement(root, {
id,
field
}) {
const rowSelector = getGridRowElementSelector(id);
const cellSelector = `.${gridClasses.cell}[data-field="${escapeOperandAttributeSelector(field)}"]`;
const selector = `${rowSelector} ${cellSelector}`;
return root.querySelector(selector);
}
// https://www.abeautifulsite.net/posts/finding-the-active-element-in-a-shadow-root/
export const getActiveElement = (root = document) => {
const activeEl = root.activeElement;
if (!activeEl) {
return null;
}
if (activeEl.shadowRoot) {
return getActiveElement(activeEl.shadowRoot);
}
return activeEl;
};
export function isEventTargetInPortal(event) {
if (
// The target is not an element when triggered by a Select inside the cell
// See https://github.com/mui/material-ui/issues/10534
event.target.nodeType === 1 && !event.currentTarget.contains(event.target)) {
return true;
}
return false;
}

View File

@@ -0,0 +1,12 @@
import { GridExportExtension } from '../models/gridExport';
/**
* I have hesitated to use https://github.com/eligrey/FileSaver.js.
* If we get bug reports that this project solves, we should consider using it.
*
* Related resources.
* https://blog.logrocket.com/programmatic-file-downloads-in-the-browser-9a5186298d5c/
* https://github.com/mbrn/filefy/blob/ec4ed0b7415d93be7158c23029f2ea1fa0b8e2d9/src/core/BaseBuilder.ts
* https://unpkg.com/browse/@progress/kendo-file-saver@1.0.7/dist/es/save-as.js
* https://github.com/ag-grid/ag-grid/blob/9565c219b6210aa85fa833c929d0728f9d163a91/community-modules/csv-export/src/csvExport/downloader.ts
*/
export declare function exportAs<ExtraExtensions extends string>(blob: Blob, extension?: GridExportExtension | ExtraExtensions, filename?: string): void;

View File

@@ -0,0 +1,38 @@
/**
* I have hesitated to use https://github.com/eligrey/FileSaver.js.
* If we get bug reports that this project solves, we should consider using it.
*
* Related resources.
* https://blog.logrocket.com/programmatic-file-downloads-in-the-browser-9a5186298d5c/
* https://github.com/mbrn/filefy/blob/ec4ed0b7415d93be7158c23029f2ea1fa0b8e2d9/src/core/BaseBuilder.ts
* https://unpkg.com/browse/@progress/kendo-file-saver@1.0.7/dist/es/save-as.js
* https://github.com/ag-grid/ag-grid/blob/9565c219b6210aa85fa833c929d0728f9d163a91/community-modules/csv-export/src/csvExport/downloader.ts
*/
export function exportAs(blob, extension = 'csv', filename = document.title || 'untitled') {
const fullName = `${filename}.${extension}`;
// Test download attribute first
// https://github.com/eligrey/FileSaver.js/issues/193
if ('download' in HTMLAnchorElement.prototype) {
// Create an object URL for the blob object
const url = URL.createObjectURL(blob);
// Create a new anchor element
const a = document.createElement('a');
a.href = url;
a.download = fullName;
// Programmatically trigger a click on the anchor element
// Useful if you want the download to happen automatically
// Without attaching the anchor element to the DOM
a.click();
// https://github.com/eligrey/FileSaver.js/issues/205
setTimeout(() => {
URL.revokeObjectURL(url);
});
return;
}
throw new Error('MUI: exportAs not supported');
}

View File

@@ -0,0 +1 @@
export declare function fastMemo<T>(component: T): T;

View File

@@ -0,0 +1,5 @@
import * as React from 'react';
import { fastObjectShallowCompare } from './fastObjectShallowCompare';
export function fastMemo(component) {
return /*#__PURE__*/React.memo(component, fastObjectShallowCompare);
}

View File

@@ -0,0 +1 @@
export declare function fastObjectShallowCompare<T extends Record<string, any> | null>(a: T, b: T): boolean;

View File

@@ -0,0 +1,32 @@
const is = Object.is;
export function fastObjectShallowCompare(a, b) {
if (a === b) {
return true;
}
if (!(a instanceof Object) || !(b instanceof Object)) {
return false;
}
let aLength = 0;
let bLength = 0;
/* eslint-disable no-restricted-syntax */
/* eslint-disable guard-for-in */
for (const key in a) {
aLength += 1;
if (!is(a[key], b[key])) {
return false;
}
if (!(key in b)) {
return false;
}
}
/* eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unused-vars */
for (const _ in b) {
bLength += 1;
}
/* eslint-enable no-restricted-syntax */
/* eslint-enable guard-for-in */
return aLength === bLength;
}

View File

@@ -0,0 +1,12 @@
import { Localization as CoreLocalization } from '@mui/material/locale';
import { GridLocaleText } from '../models/api/gridLocaleTextApi';
export interface Localization {
components: {
MuiDataGrid: {
defaultProps: {
localeText: Partial<GridLocaleText>;
};
};
};
}
export declare const getGridLocalization: (gridTranslations: Partial<GridLocaleText>, coreTranslations?: CoreLocalization) => Localization;

View File

@@ -0,0 +1,15 @@
import _extends from "@babel/runtime/helpers/esm/extends";
export const getGridLocalization = (gridTranslations, coreTranslations) => {
var _coreTranslations$com;
return {
components: {
MuiDataGrid: {
defaultProps: {
localeText: _extends({}, gridTranslations, {
MuiTablePagination: (coreTranslations == null || (_coreTranslations$com = coreTranslations.components) == null || (_coreTranslations$com = _coreTranslations$com.MuiTablePagination) == null ? void 0 : _coreTranslations$com.defaultProps) || {}
})
}
}
}
};
};

View File

@@ -0,0 +1,3 @@
/// <reference types="react" />
import type { GridPrivateApiCommunity } from '../models/api/gridApiCommunity';
export declare function getPublicApiRef<PrivateApi extends GridPrivateApiCommunity>(apiRef: React.MutableRefObject<PrivateApi>): import("react").MutableRefObject<ReturnType<PrivateApi["getPublicApi"]>>;

View File

@@ -0,0 +1,5 @@
export function getPublicApiRef(apiRef) {
return {
current: apiRef.current.getPublicApi()
};
}

View File

@@ -0,0 +1 @@
export type { OutputSelector } from './createSelector';

View File

@@ -0,0 +1 @@
export {};

View File

@@ -0,0 +1,20 @@
import * as React from 'react';
export declare const isEscapeKey: (key: string) => boolean;
export declare const isEnterKey: (key: string) => boolean;
export declare const isTabKey: (key: string) => boolean;
export declare const isSpaceKey: (key: string) => boolean;
export declare const isArrowKeys: (key: string) => boolean;
export declare const isHomeOrEndKeys: (key: string) => boolean;
export declare const isPageKeys: (key: string) => boolean;
export declare const isDeleteKeys: (key: string) => boolean;
export declare function isPrintableKey(event: React.KeyboardEvent<HTMLElement>): boolean;
export declare const GRID_MULTIPLE_SELECTION_KEYS: string[];
export declare const GRID_CELL_EXIT_EDIT_MODE_KEYS: string[];
export declare const GRID_CELL_EDIT_COMMIT_KEYS: string[];
export declare const isMultipleKey: (key: string) => boolean;
export declare const isCellEnterEditModeKeys: (event: React.KeyboardEvent<HTMLElement>) => boolean;
export declare const isCellExitEditModeKeys: (key: string) => boolean;
export declare const isCellEditCommitKeys: (key: string) => boolean;
export declare const isNavigationKey: (key: string) => boolean;
export declare const isKeyboardEvent: (event: any) => event is React.KeyboardEvent<HTMLElement>;
export declare const isHideMenuKey: (key: React.KeyboardEvent['key']) => boolean;

View File

@@ -0,0 +1,30 @@
export const isEscapeKey = key => key === 'Escape'; // TODO remove
export const isEnterKey = key => key === 'Enter'; // TODO remove
export const isTabKey = key => key === 'Tab'; // TODO remove
export const isSpaceKey = key => key === ' ';
export const isArrowKeys = key => key.indexOf('Arrow') === 0;
export const isHomeOrEndKeys = key => key === 'Home' || key === 'End';
export const isPageKeys = key => key.indexOf('Page') === 0;
export const isDeleteKeys = key => key === 'Delete' || key === 'Backspace';
// Non printable keys have a name, e.g. "ArrowRight", see the whole list:
// https://developer.mozilla.org/en-US/docs/Web/API/UI_Events/Keyboard_event_key_values
// So event.key.length === 1 is often enough.
//
// However, we also need to ignore shortcuts, for example: select all:
// - Windows: Ctrl+A, event.ctrlKey is true
// - macOS: ⌘ Command+A, event.metaKey is true
export function isPrintableKey(event) {
return event.key.length === 1 && !event.ctrlKey && !event.metaKey;
}
export const GRID_MULTIPLE_SELECTION_KEYS = ['Meta', 'Control', 'Shift'];
export const GRID_CELL_EXIT_EDIT_MODE_KEYS = ['Enter', 'Escape', 'Tab'];
export const GRID_CELL_EDIT_COMMIT_KEYS = ['Enter', 'Tab'];
export const isMultipleKey = key => GRID_MULTIPLE_SELECTION_KEYS.indexOf(key) > -1;
export const isCellEnterEditModeKeys = event => isEnterKey(event.key) || isDeleteKeys(event.key) || isPrintableKey(event);
export const isCellExitEditModeKeys = key => GRID_CELL_EXIT_EDIT_MODE_KEYS.indexOf(key) > -1;
export const isCellEditCommitKeys = key => GRID_CELL_EDIT_COMMIT_KEYS.indexOf(key) > -1;
export const isNavigationKey = key => isHomeOrEndKeys(key) || isArrowKeys(key) || isPageKeys(key) || isSpaceKey(key);
export const isKeyboardEvent = event => !!event.key;
export const isHideMenuKey = key => isTabKey(key) || isEscapeKey(key);

View File

@@ -0,0 +1,6 @@
{
"sideEffects": false,
"module": "./index.js",
"main": "../node/utils/index.js",
"types": "./index.d.ts"
}

View File

@@ -0,0 +1,39 @@
export declare function isNumber(value: unknown): value is number;
export declare function isFunction(value: any): value is Function;
export declare function isObject<TObject = Record<PropertyKey, any>>(value: unknown): value is TObject;
export declare function localStorageAvailable(): boolean;
export declare function escapeRegExp(value: string): string;
/**
* Follows the CSS specification behavior for min and max
* If min > max, then the min have priority
*/
export declare const clamp: (value: number, min: number, max: number) => number;
/**
* Based on `fast-deep-equal`
*
* MIT License
*
* Copyright (c) 2017 Evgeny Poberezkin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* We only type the public interface to avoid dozens of `as` in the function.
*/
export declare function isDeepEqual<T>(actual: any, expected: T): actual is T;
export declare function randomNumberBetween(seed: number, min: number, max: number): () => number;
export declare function deepClone(obj: Record<string, any>): any;

View File

@@ -0,0 +1,175 @@
export function isNumber(value) {
return typeof value === 'number' && !Number.isNaN(value);
}
export function isFunction(value) {
return typeof value === 'function';
}
export function isObject(value) {
return typeof value === 'object' && value !== null;
}
export function localStorageAvailable() {
try {
// Incognito mode might reject access to the localStorage for security reasons.
// window isn't defined on Node.js
// https://stackoverflow.com/questions/16427636/check-if-localstorage-is-available
const key = '__some_random_key_you_are_not_going_to_use__';
window.localStorage.setItem(key, key);
window.localStorage.removeItem(key);
return true;
} catch (err) {
return false;
}
}
export function escapeRegExp(value) {
return value.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&');
}
/**
* Follows the CSS specification behavior for min and max
* If min > max, then the min have priority
*/
export const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
/**
* Based on `fast-deep-equal`
*
* MIT License
*
* Copyright (c) 2017 Evgeny Poberezkin
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
* We only type the public interface to avoid dozens of `as` in the function.
*/
export function isDeepEqual(a, b) {
if (a === b) {
return true;
}
if (a && b && typeof a === 'object' && typeof b === 'object') {
if (a.constructor !== b.constructor) {
return false;
}
if (Array.isArray(a)) {
const length = a.length;
if (length !== b.length) {
return false;
}
for (let i = 0; i < length; i += 1) {
if (!isDeepEqual(a[i], b[i])) {
return false;
}
}
return true;
}
if (a instanceof Map && b instanceof Map) {
if (a.size !== b.size) {
return false;
}
const entriesA = Array.from(a.entries());
for (let i = 0; i < entriesA.length; i += 1) {
if (!b.has(entriesA[i][0])) {
return false;
}
}
for (let i = 0; i < entriesA.length; i += 1) {
const entryA = entriesA[i];
if (!isDeepEqual(entryA[1], b.get(entryA[0]))) {
return false;
}
}
return true;
}
if (a instanceof Set && b instanceof Set) {
if (a.size !== b.size) {
return false;
}
const entries = Array.from(a.entries());
for (let i = 0; i < entries.length; i += 1) {
if (!b.has(entries[i][0])) {
return false;
}
}
return true;
}
if (ArrayBuffer.isView(a) && ArrayBuffer.isView(b)) {
const length = a.length;
if (length !== b.length) {
return false;
}
for (let i = 0; i < length; i += 1) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
if (a.constructor === RegExp) {
return a.source === b.source && a.flags === b.flags;
}
if (a.valueOf !== Object.prototype.valueOf) {
return a.valueOf() === b.valueOf();
}
if (a.toString !== Object.prototype.toString) {
return a.toString() === b.toString();
}
const keys = Object.keys(a);
const length = keys.length;
if (length !== Object.keys(b).length) {
return false;
}
for (let i = 0; i < length; i += 1) {
if (!Object.prototype.hasOwnProperty.call(b, keys[i])) {
return false;
}
}
for (let i = 0; i < length; i += 1) {
const key = keys[i];
if (!isDeepEqual(a[key], b[key])) {
return false;
}
}
return true;
}
// true if both NaN, false otherwise
// eslint-disable-next-line no-self-compare
return a !== a && b !== b;
}
// Pseudo random number. See https://stackoverflow.com/a/47593316
function mulberry32(a) {
return () => {
/* eslint-disable */
let t = a += 0x6d2b79f5;
t = Math.imul(t ^ t >>> 15, t | 1);
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
return ((t ^ t >>> 14) >>> 0) / 4294967296;
/* eslint-enable */
};
}
export function randomNumberBetween(seed, min, max) {
const random = mulberry32(seed);
return () => min + (max - min) * random();
}
export function deepClone(obj) {
if (typeof structuredClone === 'function') {
return structuredClone(obj);
}
return JSON.parse(JSON.stringify(obj));
}

View File

@@ -0,0 +1,2 @@
export declare const buildWarning: (message: string | string[], gravity?: 'warning' | 'error') => () => void;
export declare const wrapWithWarningOnCall: <F extends Function>(method: F, message: string | string[]) => F | ((...args: any[]) => any);

View File

@@ -0,0 +1,24 @@
export const buildWarning = (message, gravity = 'warning') => {
let alreadyWarned = false;
const cleanMessage = Array.isArray(message) ? message.join('\n') : message;
return () => {
if (!alreadyWarned) {
alreadyWarned = true;
if (gravity === 'error') {
console.error(cleanMessage);
} else {
console.warn(cleanMessage);
}
}
};
};
export const wrapWithWarningOnCall = (method, message) => {
if (process.env.NODE_ENV === 'production') {
return method;
}
const warning = buildWarning(message);
return (...args) => {
warning();
return method(...args);
};
};