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

Portal accepts React.CSSProperties rather than CSSStyleDeclaration #1069

Merged
merged 2 commits into from
May 9, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion packages/zent/jest.config.json
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
"<rootDir>/src/form/.+",
"<rootDir>/src/portal/.+",
"<rootDir>/src/loading/.+",
"<rootDir>/src/slider/Points.tsx"
"<rootDir>/src/slider/Points.tsx",
"<rootDir>/src/utils/style/warnValidStyle.ts"
],
"moduleNameMapper": {
"fecha": "<rootDir>/../../node_modules/fecha/src/fecha.js",
Expand Down
2 changes: 2 additions & 0 deletions packages/zent/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@types/react-transition-group": "^2.0.16",
"@types/sortablejs": "^1.7.2",
"@types/tinycolor2": "^1.4.1",
"@types/warning": "^3.0.0",
"autosize": "^4.0.0",
"big.js": "^5.2.2",
"classnames": "^2.2.6",
Expand All @@ -61,6 +62,7 @@
"tinycolor2": "^1.4.1",
"tslib": "^1.9.3",
"utility-types": "^3.4.1",
"warning": "^4.0.3",
"zenticons": "1.4.1"
},
"devDependencies": {
Expand Down
4 changes: 2 additions & 2 deletions packages/zent/src/loading/FullScreenLoading.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { IFullScreenLoadingProps, FullScreenDefaultProps } from './props';
import LoadingMask from './components/LoadingMask';
import { Portal } from '../portal';

const NO_STYLE: Partial<CSSStyleDeclaration> = {};
const NO_STYLE: React.CSSProperties = {};

export function FullScreenLoading(props: IFullScreenLoadingProps) {
const {
Expand All @@ -26,7 +26,7 @@ export function FullScreenLoading(props: IFullScreenLoadingProps) {
return null;
}

const style = isNumber(zIndex) ? { zIndex: `${zIndex}` } : NO_STYLE;
const style = isNumber(zIndex) ? { zIndex } : NO_STYLE;

return (
<Portal
Expand Down
3 changes: 2 additions & 1 deletion packages/zent/src/pop/position.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CSSProperties } from 'react';
import capitalize from 'lodash-es/capitalize';
import isFunction from 'lodash-es/isFunction';

Expand All @@ -11,7 +12,7 @@ const ARROW_OFFSET_V = 9;

const createPosition = (x, y, side) => {
return {
getCSSStyle(): Partial<CSSStyleDeclaration> {
getCSSStyle(): CSSProperties {
return {
position: 'absolute',
left: `${Math.round(x)}px`,
Expand Down
7 changes: 4 additions & 3 deletions packages/zent/src/popover/placement/invisible.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CSSProperties } from 'react';
import createPlacement from './create';
import { PositionFunctionImpl } from '../position-function';

Expand All @@ -9,13 +10,13 @@ const locate: PositionFunctionImpl = () => {
const y = -100000;

return {
getCSSStyle(): Partial<CSSStyleDeclaration> {
getCSSStyle(): CSSProperties {
return {
position: 'fixed',
left: `${x}px`,
top: `${y}px`,
zIndex: '-10',
opacity: '0',
zIndex: -10,
opacity: 0,
};
},

Expand Down
4 changes: 3 additions & 1 deletion packages/zent/src/popover/position-function.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { CSSProperties } from 'react';

export interface IPopoverPosition {
getCSSStyle: () => Partial<CSSStyleDeclaration>;
getCSSStyle: () => CSSProperties;
name: string;
}

Expand Down
40 changes: 31 additions & 9 deletions packages/zent/src/portal/Portal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,24 @@ import noop from 'lodash-es/noop';
import PurePortal, { IPurePortalProps } from './PurePortal';
import { getNodeFromSelector, hasScrollbarY } from './util';
import { SCROLLBAR_WIDTH } from '../utils/getScrollbarWidth';
import { setValueForStyles } from '../utils/style/CSSPropertyOperations';

function diffStyle(prev: React.CSSProperties, next: React.CSSProperties) {
const result: React.CSSProperties = {};
const prevKeys = Object.keys(prev) as Array<keyof React.CSSProperties>;
for (let i = 0; i < prevKeys.length; i += 1) {
const key = prevKeys[i];
if (!next[key]) {
result[key] = '';
}
}
const nextKeys = Object.keys(next) as Array<keyof React.CSSProperties>;
for (let i = 0; i < prevKeys.length; i += 1) {
const key = nextKeys[i];
result[key] = next[key];
}
return result;
}

export interface IPortalProps extends Partial<IPurePortalProps> {
visible?: boolean;
Expand All @@ -26,7 +44,7 @@ export interface IPortalProps extends Partial<IPurePortalProps> {
onClose?: (e: KeyboardEvent | TouchEvent | MouseEvent) => void;
children?: React.ReactNode;
className?: string;
style?: Partial<CSSStyleDeclaration>;
style?: React.CSSProperties;
}

export interface IPortalImperativeHandlers {
Expand All @@ -53,6 +71,7 @@ export const Portal = forwardRef<IPortalImperativeHandlers, IPortalProps>(
const parent = useMemo(() => getNodeFromSelector(selector), [selector]);
const propsRef = useRef<IPortalProps>(props);
propsRef.current = props;
const prevStyleRef = useRef<React.CSSProperties | undefined>(style);
const purePortalRef = useRef<PurePortal>(null);

// Methods for use on ref
Expand All @@ -77,17 +96,20 @@ export const Portal = forwardRef<IPortalImperativeHandlers, IPortalProps>(
return noop;
}
parent.appendChild(node);
className && (node.className = className);
if (style) {
const cssKeys = Object.keys(style) as Array<keyof CSSStyleDeclaration>;
if (cssKeys.length) {
node.style.cssText = cssKeys.map(k => `${k}: ${style[k]}`).join('; ');
}
}
return () => {
parent.removeChild(node);
};
}, [visible, node, parent, style, className]);
}, [visible, node, parent]);

useLayoutEffect(() => {
className && (node.className = className);
}, [node, className]);

useLayoutEffect(() => {
const result = diffStyle(prevStyleRef.current || {}, style || {});
setValueForStyles(node, result);
prevStyleRef.current = style;
}, [node, style]);

useLayoutEffect(() => {
if (!visible || !useLayerForClickAway) {
Expand Down
80 changes: 80 additions & 0 deletions packages/zent/src/utils/style/CSSProperty.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

/**
* CSS properties which accept numbers but are not in units of "px".
*/
export const isUnitlessNumber: Record<string, boolean> = {
animationIterationCount: true,
borderImageOutset: true,
borderImageSlice: true,
borderImageWidth: true,
boxFlex: true,
boxFlexGroup: true,
boxOrdinalGroup: true,
columnCount: true,
columns: true,
flex: true,
flexGrow: true,
flexPositive: true,
flexShrink: true,
flexNegative: true,
flexOrder: true,
gridArea: true,
gridRow: true,
gridRowEnd: true,
gridRowSpan: true,
gridRowStart: true,
gridColumn: true,
gridColumnEnd: true,
gridColumnSpan: true,
gridColumnStart: true,
fontWeight: true,
lineClamp: true,
lineHeight: true,
opacity: true,
order: true,
orphans: true,
tabSize: true,
widows: true,
zIndex: true,
zoom: true,

// SVG-related properties
fillOpacity: true,
floodOpacity: true,
stopOpacity: true,
strokeDasharray: true,
strokeDashoffset: true,
strokeMiterlimit: true,
strokeOpacity: true,
strokeWidth: true,
};

/**
* @param {string} prefix vendor-specific prefix, eg: Webkit
* @param {string} key style name, eg: transitionDuration
* @return {string} style name prefixed with `prefix`, properly camelCased, eg:
* WebkitTransitionDuration
*/
function prefixKey(prefix: string, key: string): string {
return prefix + key.charAt(0).toUpperCase() + key.substring(1);
}

/**
* Support style names that may come passed in prefixed by adding permutations
* of vendor prefixes.
*/
const prefixes = ['Webkit', 'ms', 'Moz', 'O'];

// Using Object.keys here, or else the vanilla for-in loop makes IE8 go into an
// infinite loop, because it iterates over the newly added props too.
Object.keys(isUnitlessNumber).forEach(prop => {
prefixes.forEach(prefix => {
isUnitlessNumber[prefixKey(prefix, prop)] = isUnitlessNumber[prop];
});
});
49 changes: 49 additions & 0 deletions packages/zent/src/utils/style/CSSPropertyOperations.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import dangerousStyleValue from './dangerousStyleValue';
import warnValidStyle from './warnValidStyle';

import { CSSProperties } from 'react';

/**
* Operations for dealing with CSS properties.
*/

/**
* Sets the value for multiple styles on a node. If a value is specified as
* '' (empty string), the corresponding style property will be unset.
*
* @param {DOMElement} node
* @param {object} styles
*/
export function setValueForStyles(node: HTMLElement, styles: CSSProperties) {
const style = node.style;
const keys = Object.keys(styles) as Array<keyof CSSProperties>;
for (let i = 0; i < keys.length; i += 1) {
let styleName = keys[i];
const isCustomProperty = styleName.indexOf('--') === 0;
if (process.env.NODE_ENV !== 'production') {
if (!isCustomProperty) {
warnValidStyle(styleName, styles[styleName as any]);
}
}
const styleValue = dangerousStyleValue(
styleName,
styles[styleName],
isCustomProperty
);
if (styleName === 'float') {
styleName = 'cssFloat' as any;
}
if (isCustomProperty) {
style.setProperty(styleName, styleValue);
} else {
style[styleName as any] = styleValue;
}
}
}
Loading