Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[utils] Add all @material-ui/core/utils to @material-ui/utils #23264

Merged
merged 35 commits into from
Oct 28, 2020
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
5901bda
move core/utils to utils
mnajdova Oct 26, 2020
c9489d7
prettier
mnajdova Oct 26, 2020
e866f5e
MuiError
mnajdova Oct 26, 2020
080dd7e
ts-ignore
mnajdova Oct 26, 2020
c625415
Add compat for usage in utils itself
eps1lon Oct 27, 2020
bd907da
Fix lint
eps1lon Oct 27, 2020
324b3a8
exported new utils as unstable_
mnajdova Oct 27, 2020
11bbc63
Merge pull request #11 from eps1lon/feat/add-core-utils-to-utils-comp…
mnajdova Oct 27, 2020
a9a4247
replaced .ts files with original .js & d.ts files
mnajdova Oct 28, 2020
3957119
fixed import
mnajdova Oct 28, 2020
328ca77
removed file
mnajdova Oct 28, 2020
2747327
removed file
mnajdova Oct 28, 2020
9066f7a
Update packages/material-ui-utils/macros/__fixtures__/relative-import…
mnajdova Oct 28, 2020
e0f137c
Update packages/material-ui-utils/macros/__fixtures__/relative-import…
mnajdova Oct 28, 2020
2d1ac56
Update packages/material-ui-utils/macros/__fixtures__/relative-import…
mnajdova Oct 28, 2020
c9e7ba0
Update packages/material-ui-utils/src/capitalize.js
mnajdova Oct 28, 2020
7a3b4c2
Update packages/material-ui-utils/src/capitalize.js
mnajdova Oct 28, 2020
fca1e8d
Update packages/material-ui-utils/src/createChainedFunction.js
mnajdova Oct 28, 2020
ebfdc06
Update packages/material-ui-utils/src/setRef.ts
mnajdova Oct 28, 2020
623a89e
Update packages/material-ui-utils/src/setRef.ts
mnajdova Oct 28, 2020
76810d7
Update packages/material-ui-utils/src/setRef.ts
mnajdova Oct 28, 2020
831d74b
Update packages/material-ui-utils/src/setRef.ts
mnajdova Oct 28, 2020
e0bc921
Update packages/material-ui-utils/src/setRef.ts
mnajdova Oct 28, 2020
b5fe610
Update packages/material-ui-utils/src/useControlled.d.ts
mnajdova Oct 28, 2020
39dac58
Update packages/material-ui-utils/src/useForkRef.js
mnajdova Oct 28, 2020
b65508e
Update packages/material-ui-utils/src/useControlled.js
mnajdova Oct 28, 2020
6fd966f
Update packages/material-ui-utils/src/useIsFocusVisible.js
mnajdova Oct 28, 2020
837e33f
Update packages/material-ui-utils/src/useIsFocusVisible.js
mnajdova Oct 28, 2020
9eefcca
Update packages/material-ui-utils/src/useIsFocusVisible.js
mnajdova Oct 28, 2020
5706036
Update packages/material-ui-utils/src/useIsFocusVisible.js
mnajdova Oct 28, 2020
21c706d
Update packages/material-ui-utils/src/useIsFocusVisible.js
mnajdova Oct 28, 2020
18be046
Removed private method comment
mnajdova Oct 28, 2020
8438658
removed private method comment
mnajdova Oct 28, 2020
e2104a5
remove private method comment
mnajdova Oct 28, 2020
1312f19
Update packages/material-ui-utils/src/createChainedFunction.js
mnajdova Oct 28, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/material-ui-utils/src/capitalize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// TODO: error TS7016: Could not find a declaration file for module '../macros/MuiError.macro'. '/tmp/material-ui/packages/material-ui-utils/macros/MuiError.macro.js' implicitly has an 'any' type.
// import MuiError from '../macros/MuiError.macro';
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@eps1lon FYI we have this issue with the macros

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you @ts-expect-error this instead for now so that we don't sacrifice the runtime behavior for typings?

eps1lon marked this conversation as resolved.
Show resolved Hide resolved
// It should to be noted that this function isn't equivalent to `text-transform: capitalize`.
//
// A strict capitalization should uppercase the first letter of each word a the sentence.
// We only handle the first word.
export default function capitalize(string: string): string {
if (typeof string !== 'string') {
throw new Error('Material-UI: capitalize(string) expects a string argument.');
}

return string.charAt(0).toUpperCase() + string.slice(1);
}
37 changes: 37 additions & 0 deletions packages/material-ui-utils/src/createChainedFunction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
export type ChainedFunction = ((...args: any[]) => void) | undefined | null;

/**
* Safe chained function
*
* Will only create a new function if needed,
* otherwise will pass back existing functions or null.
* @param {function} functions to chain
* @returns {function|null}
*/
export default function createChainedFunction(
...funcs: ChainedFunction[]
): (...args: any[]) => never {
return funcs.reduce(
(acc, func) => {
if (func == null) {
return acc;
}

if (process.env.NODE_ENV !== 'production') {
if (typeof func !== 'function') {
console.error(
'Material-UI: Invalid Argument Type, must only provide functions, undefined, or null.',
);
}
}

return function chainedFunction(...args) {
// @ts-ignore
acc.apply(this, args);
// @ts-ignore
func.apply(this, args);
};
},
() => {},
) as (...args: any[]) => never;
}
26 changes: 26 additions & 0 deletions packages/material-ui-utils/src/debounce.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
export interface Cancelable {
clear(): void;
}

// Corresponds to 10 frames at 60 Hz.
// A few bytes payload overhead when lodash/debounce is ~3 kB and debounce ~300 B.
export default function debounce<T extends (...args: any[]) => any>(
func: T,
wait: number = 166,
): T & Cancelable {
let timeout: any;
function debounced(...args: any[]) {
const later = () => {
// @ts-ignore
func.apply(this, args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
}

debounced.clear = () => {
clearTimeout(timeout);
};

return debounced as T & Cancelable;
}
20 changes: 20 additions & 0 deletions packages/material-ui-utils/src/deprecatedPropType.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
export default function deprecatedPropType<T>(validator: T, reason: string): T | Function {
if (process.env.NODE_ENV === 'production') {
return () => null;
}

// @ts-ignore
return (props, propName, componentName, location, propFullName) => {
const componentNameSafe = componentName || '<<anonymous>>';
const propFullNameSafe = propFullName || propName;

if (typeof props[propName] !== 'undefined') {
return new Error(
`The ${location} \`${propFullNameSafe}\` of ` +
`\`${componentNameSafe}\` is deprecated. ${reason}`,
);
}

return null;
};
}
16 changes: 16 additions & 0 deletions packages/material-ui-utils/src/getScrollbarSize.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
// A change of the browser zoom change the scrollbar size.
// Credit https://github.com/twbs/bootstrap/blob/3ffe3a5d82f6f561b82ff78d82b32a7d14aed558/js/src/modal.js#L512-L519
export default function getScrollbarSize(doc: Document): number {
const scrollDiv = doc.createElement('div');
scrollDiv.style.width = '99px';
scrollDiv.style.height = '99px';
scrollDiv.style.position = 'absolute';
scrollDiv.style.top = '-9999px';
scrollDiv.style.overflow = 'scroll';

doc.body.appendChild(scrollDiv);
const scrollbarSize = scrollDiv.offsetWidth - scrollDiv.clientWidth;
doc.body.removeChild(scrollDiv);

return scrollbarSize;
}
18 changes: 18 additions & 0 deletions packages/material-ui-utils/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,21 @@ export { default as getDisplayName } from './getDisplayName';
export { default as HTMLElementType } from './HTMLElementType';
export { default as ponyfillGlobal } from './ponyfillGlobal';
export { default as refType } from './refType';
export { default as capitalize } from './capitalize';
export { default as createChainedFunction } from './createChainedFunction';
export { default as debounce } from './debounce';
export { default as deprecatedPropType } from './deprecatedPropType';
export { default as isMuiElement } from './isMuiElement';
export { default as ownerDocument } from './ownerDocument';
export { default as ownerWindow } from './ownerWindow';
export { default as requirePropFactory } from './requirePropFactory';
export { default as setRef } from './setRef';
export { default as unstable_useEnhancedEffect } from './useEnhancedEffect';
export { default as unstable_useId } from './useId';
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need the unstable prefix if we treat all the modules here private?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If they're here, they're public.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I forgot where I said but I would be way more comfortable by renaming /utils to internal or even unsafe_internal. There are already plenty of packages depending on /types and I suspect the same happened with /utils.

Or we release it as an alpha.

Either way, I agree that the current state of /utils being on the same release line as /core (5.x) does not signal that the package is private. It's simply not enough that we treat it as private since we have some responsibility to not clutter npm with more underdocumented packages.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we then proceed with #23270 move all utils in unstyled and export them as unstable_? Then for avoiding breaking changes, we can reuse them in the core and just reexport them.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Then for avoiding breaking changes, we can reuse them in the core and just reexport them.

I don't understand what that solves.

If we change the API of useId and only we consume useId then everything is fine.
If useId can also be used by library consumers directly then we can only release a breaking change either by

  1. bumping the major version number of the package
  2. bumping the pre-release number if it's an alpha
  3. do nothing (but documentation) if it's prefixed as unstable_
    unstable_ is still only an informal convention so we should still be careful.

Copy link
Member

@oliviertassinari oliviertassinari Oct 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or should we move them to the new@material-ui/unstyled package and again to export them as unstable_

Considering that some of the utils are required by the system, it would mean the system needs to depend on unstyled. I assume that it's not something we want because the two are solving independent problems.

Should we move all these utils to @material-ui/utils as this PR is doing and export them all as unstable_

I think that this option can work 👍.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, let me update the exports then

Copy link
Member Author

@mnajdova mnajdova Oct 27, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done, everything in @material-ui/utils that comes from @material-ui/core/utils is exported as unstable_

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We're importing unstable_createFunction from /utils and re-export it as createChainedFunction from /core/utils. I don't know how this is supposed to work: If /utils can change the API of createChainedFunction on every release and /core pins /utils to major then createChainedFunction from /core/utils can also change on every release and is therefore unstable.

What did we try to accomplish by adding (and not moving) /core/utils to /utils?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't want to make changes to the existing core/utils as we have imports all over the project from there. I started like that in #23203 prepared codemods as well, but we decided to go wtih the minimal changes necessary for unblocking ourselves, which is to move the utils to core, but still re-export them from the core in order to avoid changes in the core.

export { default as unsupportedProp } from './unsupportedProp';
export { default as useControlled } from './useControlled';
export { default as useEventCallback } from './useEventCallback';
export { default as useForkRef } from './useForkRef';
export { default as useIsFocusVisible } from './useIsFocusVisible';
export { default as getScrollbarSize } from './getScrollbarSize';
export { detectScrollType, getNormalizedScrollLeft } from './scrollLeft';
40 changes: 40 additions & 0 deletions packages/material-ui-utils/src/isMuiElement.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as React from 'react';

export type ClassNameMap<ClassKey extends string = string> = Record<ClassKey, string>;

export interface StyledComponentProps<ClassKey extends string = string> {
/**
* Override or extend the styles applied to the component.
*/
classes?: Partial<ClassNameMap<ClassKey>>;
innerRef?: React.Ref<any>;
}

/**
* @private ONLY USE FROM WITHIN mui-org/material-ui
*
* Internal helper type for conform (describeConformance) components that are decorated with `withStyles
* However, we don't declare classes on this type.
* It is recommended to declare them manually with an interface so that each class can have a separate JSDOC.
*/
export type StandardProps<C, Removals extends keyof C = never> = Omit<C, 'classes' | Removals> &
// each component declares it's classes in a separate interface for proper JSDOC
StyledComponentProps<never> & {
ref?: C extends { ref?: infer RefType } ? RefType : React.Ref<unknown>;
// TODO: Remove implicit props. Up to each component.
className?: string;
style?: React.CSSProperties;
};

export type NamedMuiComponent = React.ComponentType & { muiName: string };

export interface NamedMuiElement {
type: NamedMuiComponent;
props: StandardProps<{}>;
key: string | number | null;
}

export default function isMuiElement(element: any, muiNames: string[]): element is NamedMuiElement {
// @ts-ignore
return React.isValidElement(element) && muiNames.indexOf(element.type.muiName) !== -1;
}
3 changes: 3 additions & 0 deletions packages/material-ui-utils/src/ownerDocument.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export default function ownerDocument(node: Node | undefined): Document {
return (node && node.ownerDocument) || document;
}
6 changes: 6 additions & 0 deletions packages/material-ui-utils/src/ownerWindow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import ownerDocument from './ownerDocument';

export default function ownerWindow(node: Node | undefined): Window {
const doc = ownerDocument(node);
return doc.defaultView || window;
}
25 changes: 25 additions & 0 deletions packages/material-ui-utils/src/requirePropFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
export default function requirePropFactory(componentNameInError: string): any {
if (process.env.NODE_ENV === 'production') {
return () => null;
}

const requireProp = (requiredProp: string): any => (
props: { [key: string]: any },
propName: string,
componentName: string,
location: string,
propFullName: string,
) => {
const propFullNameSafe = propFullName || propName;

if (typeof props[propName] !== 'undefined' && !props[requiredProp]) {
return new Error(
`The prop \`${propFullNameSafe}\` of ` +
`\`${componentNameInError}\` must be used on \`${requiredProp}\`.`,
);
}

return null;
};
return requireProp;
}
77 changes: 77 additions & 0 deletions packages/material-ui-utils/src/scrollLeft.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
// Source from https://github.com/alitaheri/normalize-scroll-left
let cachedType: string;

/**
* Based on the jquery plugin https://github.com/othree/jquery.rtl-scroll-type
*
* Types of scrollLeft, assuming scrollWidth=100 and direction is rtl.
*
* Type | <- Most Left | Most Right -> | Initial
* ---------------- | ------------ | ------------- | -------
* default | 0 | 100 | 100
* negative (spec*) | -100 | 0 | 0
* reverse | 100 | 0 | 0
*
* Edge 85: default
* Safari 14: negative
* Chrome 85: negative
* Firefox 81: negative
* IE11: reverse
*
* spec* https://drafts.csswg.org/cssom-view/#dom-window-scroll
*/
export function detectScrollType(): string {
if (cachedType) {
return cachedType;
}

const dummy = document.createElement('div');
const container = document.createElement('div');
container.style.width = '10px';
container.style.height = '1px';
dummy.appendChild(container);
dummy.dir = 'rtl';
dummy.style.fontSize = '14px';
dummy.style.width = '4px';
dummy.style.height = '1px';
dummy.style.position = 'absolute';
dummy.style.top = '-1000px';
dummy.style.overflow = 'scroll';

document.body.appendChild(dummy);

cachedType = 'reverse';

if (dummy.scrollLeft > 0) {
cachedType = 'default';
} else {
dummy.scrollLeft = 1;
if (dummy.scrollLeft === 0) {
cachedType = 'negative';
}
}

document.body.removeChild(dummy);
return cachedType;
}

// Based on https://stackoverflow.com/a/24394376
export function getNormalizedScrollLeft(element: Element, direction: string): number {
const scrollLeft = element.scrollLeft;

// Perform the calculations only when direction is rtl to avoid messing up the ltr behavior
if (direction !== 'rtl') {
return scrollLeft;
}

const type = detectScrollType();

switch (type) {
case 'negative':
return element.scrollWidth - element.clientWidth + scrollLeft;
case 'reverse':
return element.scrollWidth - element.clientWidth - scrollLeft;
default:
return scrollLeft;
}
}
25 changes: 25 additions & 0 deletions packages/material-ui-utils/src/setRef.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import * as React from 'react';

/**
* TODO v5: consider to make it private
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
*
* passes {value} to {ref}
*
* WARNING: Be sure to only call this inside a callback that is passed as a ref.
* Otherwise make sure to cleanup previous {ref} if it changes. See
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
* https://github.com/mui-org/material-ui/issues/13539
*
* useful if you want to expose the ref of an inner component to the public api
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
* while still using it inside the component
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
* @param ref a ref callback or ref object if anything falsy this is a no-op
mnajdova marked this conversation as resolved.
Show resolved Hide resolved
*/
export default function setRef<T>(
ref: React.MutableRefObject<T | null> | ((instance: T | null) => void) | null | undefined,
value: T | null,
): void {
if (typeof ref === 'function') {
ref(value);
} else if (ref) {
ref.current = value;
}
}
7 changes: 7 additions & 0 deletions packages/material-ui-utils/src/unsupportedProp.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function unsupportedProp(
props: { [key: string]: any },
propName: string,
componentName: string,
location: string,
propFullName: string
): Error | null;
19 changes: 19 additions & 0 deletions packages/material-ui-utils/src/unsupportedProp.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
export default function unsupportedProp(
props: { [key: string]: any },
propName: string,
componentName: string,
location: string,
propFullName: string,
): Error | null {
if (process.env.NODE_ENV === 'production') {
return null;
}

const propFullNameSafe = propFullName || propName;

if (typeof props[propName] !== 'undefined') {
return new Error(`The prop \`${propFullNameSafe}\` is not supported. Please remove it.`);
}

return null;
}
Loading