条件组合半成品
This commit is contained in:
parent
8901b57389
commit
9bb718aeac
|
@ -38,17 +38,43 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.#{$ns}CBItem {
|
.#{$ns}CBDelete {
|
||||||
display: flex;
|
@include icon-color();
|
||||||
margin-top: px2rem(10px);
|
cursor: pointer;
|
||||||
padding: 5px 10px;
|
margin-left: auto;
|
||||||
border-radius: 5px;
|
}
|
||||||
flex-direction: row;
|
|
||||||
align-items: center;
|
.#{$ns}CBGroupOrItem {
|
||||||
margin-left: px2rem(30px);
|
|
||||||
position: relative;
|
position: relative;
|
||||||
background: rgba(0, 0, 0, 0.03);
|
margin-left: px2rem(30px);
|
||||||
transition: all 0.3s ease-out;
|
|
||||||
|
&-body {
|
||||||
|
display: flex;
|
||||||
|
margin-top: px2rem(10px);
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 5px;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
background: rgba(0, 0, 0, 0.03);
|
||||||
|
transition: all 0.3s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-dragging {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.is-ghost > &-body:before {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
content: '';
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background: rgba($info, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
&-dragbar {
|
&-dragbar {
|
||||||
cursor: move;
|
cursor: move;
|
||||||
|
@ -60,11 +86,16 @@
|
||||||
@include icon-color();
|
@include icon-color();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.#{$ns}CBGroup {
|
||||||
|
margin-left: $gap-xs;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
background-color: rgba(0, 0, 0, 0.05);
|
background-color: rgba(0, 0, 0, 0.05);
|
||||||
}
|
}
|
||||||
|
|
||||||
&:hover &-dragbar {
|
&:hover > &-body > &-dragbar {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,6 +148,7 @@
|
||||||
|
|
||||||
.#{$ns}CBFunc {
|
.#{$ns}CBFunc {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
margin-left: $gap-xs;
|
||||||
|
|
||||||
&-select {
|
&-select {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
|
@ -140,3 +172,9 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.#{$ns}CBValue {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: $gap-xs;
|
||||||
|
}
|
||||||
|
|
|
@ -1,4 +1,12 @@
|
||||||
import {ExpressionComplex, Field, Funcs, Func, ExpressionFunc} from './types';
|
import {
|
||||||
|
ExpressionComplex,
|
||||||
|
Field,
|
||||||
|
Funcs,
|
||||||
|
Func,
|
||||||
|
ExpressionFunc,
|
||||||
|
Type,
|
||||||
|
FieldSimple
|
||||||
|
} from './types';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import ConditionField from './Field';
|
import ConditionField from './Field';
|
||||||
import {autobind, findTree} from '../../utils/helper';
|
import {autobind, findTree} from '../../utils/helper';
|
||||||
|
@ -20,7 +28,7 @@ export interface ExpressionProps extends ThemeProps {
|
||||||
value: ExpressionComplex;
|
value: ExpressionComplex;
|
||||||
index?: number;
|
index?: number;
|
||||||
onChange: (value: ExpressionComplex, index?: number) => void;
|
onChange: (value: ExpressionComplex, index?: number) => void;
|
||||||
valueField?: Field;
|
valueField?: FieldSimple;
|
||||||
fields?: Field[];
|
fields?: Field[];
|
||||||
funcs?: Funcs;
|
funcs?: Funcs;
|
||||||
defaultType?: 'value' | 'field' | 'func' | 'raw';
|
defaultType?: 'value' | 'field' | 'func' | 'raw';
|
||||||
|
@ -94,7 +102,14 @@ export class Expression extends React.Component<ExpressionProps> {
|
||||||
handleRawChange() {}
|
handleRawChange() {}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {value, defaultType, allowedTypes, funcs, fields} = this.props;
|
const {
|
||||||
|
value,
|
||||||
|
valueField,
|
||||||
|
defaultType,
|
||||||
|
allowedTypes,
|
||||||
|
funcs,
|
||||||
|
fields
|
||||||
|
} = this.props;
|
||||||
const inputType =
|
const inputType =
|
||||||
((value as any)?.type === 'field'
|
((value as any)?.type === 'field'
|
||||||
? 'field'
|
? 'field'
|
||||||
|
@ -128,7 +143,13 @@ export class Expression extends React.Component<ExpressionProps> {
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
|
|
||||||
{inputType === 'value' ? <Value /> : null}
|
{inputType === 'value' ? (
|
||||||
|
<Value
|
||||||
|
field={valueField!}
|
||||||
|
value={value}
|
||||||
|
onChange={this.handleValueChange}
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
|
||||||
{inputType === 'field' ? (
|
{inputType === 'field' ? (
|
||||||
<ConditionField
|
<ConditionField
|
||||||
|
|
|
@ -1,25 +1,27 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import {Fields, ConditionGroupValue, Funcs} from './types';
|
import {Fields, ConditionGroupValue, Funcs} from './types';
|
||||||
import {ClassNamesFn} from '../../theme';
|
import {ClassNamesFn, ThemeProps, themeable} from '../../theme';
|
||||||
import Button from '../Button';
|
import Button from '../Button';
|
||||||
import {ConditionItem} from './Item';
|
import GroupOrItem from './GroupOrItem';
|
||||||
import {autobind, guid} from '../../utils/helper';
|
import {autobind, guid} from '../../utils/helper';
|
||||||
import {Config} from './config';
|
import {Config} from './config';
|
||||||
|
import {Icon} from '../icons';
|
||||||
|
|
||||||
export interface ConditionGroupProps {
|
export interface ConditionGroupProps extends ThemeProps {
|
||||||
config: Config;
|
config: Config;
|
||||||
value?: ConditionGroupValue;
|
value?: ConditionGroupValue;
|
||||||
fields: Fields;
|
fields: Fields;
|
||||||
funcs?: Funcs;
|
funcs?: Funcs;
|
||||||
index?: number;
|
onChange: (value: ConditionGroupValue) => void;
|
||||||
onChange: (value: ConditionGroupValue, index?: number) => void;
|
|
||||||
classnames: ClassNamesFn;
|
|
||||||
removeable?: boolean;
|
removeable?: boolean;
|
||||||
|
onRemove?: (e: React.MouseEvent) => void;
|
||||||
|
onDragStart?: (e: React.MouseEvent) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ConditionGroup extends React.Component<ConditionGroupProps> {
|
export class ConditionGroup extends React.Component<ConditionGroupProps> {
|
||||||
getValue() {
|
getValue() {
|
||||||
return {
|
return {
|
||||||
|
id: guid(),
|
||||||
conjunction: 'and' as 'and',
|
conjunction: 'and' as 'and',
|
||||||
...this.props.value
|
...this.props.value
|
||||||
} as ConditionGroupValue;
|
} as ConditionGroupValue;
|
||||||
|
@ -31,7 +33,7 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
|
||||||
let value = this.getValue();
|
let value = this.getValue();
|
||||||
value.not = !value.not;
|
value.not = !value.not;
|
||||||
|
|
||||||
onChange(value, this.props.index);
|
onChange(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
|
@ -39,7 +41,7 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
|
||||||
const onChange = this.props.onChange;
|
const onChange = this.props.onChange;
|
||||||
let value = this.getValue();
|
let value = this.getValue();
|
||||||
value.conjunction = value.conjunction === 'and' ? 'or' : 'and';
|
value.conjunction = value.conjunction === 'and' ? 'or' : 'and';
|
||||||
onChange(value, this.props.index);
|
onChange(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
|
@ -54,7 +56,7 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
|
||||||
value.children.push({
|
value.children.push({
|
||||||
id: guid()
|
id: guid()
|
||||||
});
|
});
|
||||||
onChange(value, this.props.index);
|
onChange(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
|
@ -68,13 +70,18 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
|
||||||
|
|
||||||
value.children.push({
|
value.children.push({
|
||||||
id: guid(),
|
id: guid(),
|
||||||
conjunction: 'and'
|
conjunction: 'and',
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
id: guid()
|
||||||
|
}
|
||||||
|
]
|
||||||
});
|
});
|
||||||
onChange(value, this.props.index);
|
onChange(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleItemChange(item: any, index?: number) {
|
handleItemChange(item: any, index: number) {
|
||||||
const onChange = this.props.onChange;
|
const onChange = this.props.onChange;
|
||||||
let value = this.getValue();
|
let value = this.getValue();
|
||||||
|
|
||||||
|
@ -83,14 +90,36 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
value.children.splice(index!, 1, item);
|
value.children.splice(index!, 1, item);
|
||||||
onChange(value, this.props.index);
|
onChange(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleItemRemove(index: number) {
|
||||||
|
const onChange = this.props.onChange;
|
||||||
|
let value = this.getValue();
|
||||||
|
|
||||||
|
value.children = Array.isArray(value.children)
|
||||||
|
? value.children.concat()
|
||||||
|
: [];
|
||||||
|
|
||||||
|
value.children.splice(index, 1);
|
||||||
|
onChange(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {classnames: cx, value, fields, funcs, config} = this.props;
|
const {
|
||||||
|
classnames: cx,
|
||||||
|
value,
|
||||||
|
fields,
|
||||||
|
funcs,
|
||||||
|
config,
|
||||||
|
removeable,
|
||||||
|
onRemove,
|
||||||
|
onDragStart
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx('CBGroup')}>
|
<div className={cx('CBGroup')} data-group-id={value?.id}>
|
||||||
<div className={cx('CBGroup-toolbar')}>
|
<div className={cx('CBGroup-toolbar')}>
|
||||||
<div className={cx('CBGroup-toolbarLeft')}>
|
<div className={cx('CBGroup-toolbarLeft')}>
|
||||||
<Button onClick={this.handleNotClick} size="sm" active={value?.not}>
|
<Button onClick={this.handleNotClick} size="sm" active={value?.not}>
|
||||||
|
@ -114,43 +143,41 @@ export class ConditionGroup extends React.Component<ConditionGroupProps> {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className={cx('CBGroup-toolbarRight')}>
|
<div className={cx('CBGroup-toolbarRight')}>
|
||||||
<Button onClick={this.handleAdd} size="sm">
|
<Button onClick={this.handleAdd} size="sm" className="m-r-xs">
|
||||||
添加条件
|
添加条件
|
||||||
</Button>
|
</Button>
|
||||||
<Button onClick={this.handleAddGroup} size="sm" className="m-l-xs">
|
<Button onClick={this.handleAddGroup} size="sm" className="m-r-xs">
|
||||||
添加条件组
|
添加条件组
|
||||||
</Button>
|
</Button>
|
||||||
|
{removeable ? (
|
||||||
|
<a className={cx('CBDelete')} onClick={onRemove}>
|
||||||
|
<Icon icon="close" className="icon" />
|
||||||
|
</a>
|
||||||
|
) : null}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{Array.isArray(value?.children)
|
<div className={cx('CBGroup-body')}>
|
||||||
? value!.children.map((item, index) =>
|
{Array.isArray(value?.children)
|
||||||
(item as ConditionGroupValue).conjunction ? (
|
? value!.children.map((item, index) => (
|
||||||
<ConditionGroup
|
<GroupOrItem
|
||||||
|
draggable={value!.children!.length > 1}
|
||||||
|
onDragStart={onDragStart}
|
||||||
config={config}
|
config={config}
|
||||||
key={item.id}
|
key={item.id}
|
||||||
fields={fields}
|
fields={fields}
|
||||||
value={item as ConditionGroupValue}
|
value={item as ConditionGroupValue}
|
||||||
classnames={cx}
|
|
||||||
index={index}
|
index={index}
|
||||||
onChange={this.handleItemChange}
|
onChange={this.handleItemChange}
|
||||||
funcs={funcs}
|
funcs={funcs}
|
||||||
|
onRemove={this.handleItemRemove}
|
||||||
/>
|
/>
|
||||||
) : (
|
))
|
||||||
<ConditionItem
|
: null}
|
||||||
config={config}
|
</div>
|
||||||
key={item.id}
|
|
||||||
fields={fields}
|
|
||||||
value={item}
|
|
||||||
classnames={cx}
|
|
||||||
index={index}
|
|
||||||
onChange={this.handleItemChange}
|
|
||||||
funcs={funcs}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
)
|
|
||||||
: null}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default themeable(ConditionGroup);
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
import {Config} from './config';
|
||||||
|
import {Fields, ConditionGroupValue, Funcs, ConditionValue} from './types';
|
||||||
|
import {ThemeProps, themeable} from '../../theme';
|
||||||
|
import React from 'react';
|
||||||
|
import {Icon} from '../icons';
|
||||||
|
import {autobind} from '../../utils/helper';
|
||||||
|
import ConditionGroup from './Group';
|
||||||
|
import ConditionItem from './Item';
|
||||||
|
|
||||||
|
export interface CBGroupOrItemProps extends ThemeProps {
|
||||||
|
config: Config;
|
||||||
|
value?: ConditionGroupValue;
|
||||||
|
fields: Fields;
|
||||||
|
funcs?: Funcs;
|
||||||
|
index: number;
|
||||||
|
draggable?: boolean;
|
||||||
|
onChange: (value: ConditionGroupValue, index: number) => void;
|
||||||
|
removeable?: boolean;
|
||||||
|
onDragStart?: (e: React.MouseEvent) => void;
|
||||||
|
onRemove?: (index: number) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CBGroupOrItem extends React.Component<CBGroupOrItemProps> {
|
||||||
|
@autobind
|
||||||
|
handleItemChange(value: any) {
|
||||||
|
this.props.onChange(value, this.props.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleItemRemove() {
|
||||||
|
this.props.onRemove?.(this.props.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
classnames: cx,
|
||||||
|
value,
|
||||||
|
config,
|
||||||
|
fields,
|
||||||
|
funcs,
|
||||||
|
draggable,
|
||||||
|
onDragStart
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className={cx('CBGroupOrItem')} data-id={value?.id}>
|
||||||
|
<div className={cx('CBGroupOrItem-body')}>
|
||||||
|
{draggable ? (
|
||||||
|
<a
|
||||||
|
draggable
|
||||||
|
onDragStart={onDragStart}
|
||||||
|
className={cx('CBGroupOrItem-dragbar')}
|
||||||
|
>
|
||||||
|
<Icon icon="drag-bar" className="icon" />
|
||||||
|
</a>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
{value?.conjunction ? (
|
||||||
|
<ConditionGroup
|
||||||
|
onDragStart={onDragStart}
|
||||||
|
config={config}
|
||||||
|
fields={fields}
|
||||||
|
value={value as ConditionGroupValue}
|
||||||
|
onChange={this.handleItemChange}
|
||||||
|
funcs={funcs}
|
||||||
|
removeable
|
||||||
|
onRemove={this.handleItemRemove}
|
||||||
|
/>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<ConditionItem
|
||||||
|
config={config}
|
||||||
|
fields={fields}
|
||||||
|
value={value as ConditionValue}
|
||||||
|
onChange={this.handleItemChange}
|
||||||
|
funcs={funcs}
|
||||||
|
/>
|
||||||
|
<a className={cx('CBDelete')} onClick={this.handleItemRemove}>
|
||||||
|
<Icon icon="close" className="icon" />
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default themeable(CBGroupOrItem);
|
|
@ -8,9 +8,10 @@ import {
|
||||||
Func,
|
Func,
|
||||||
Field,
|
Field,
|
||||||
FieldSimple,
|
FieldSimple,
|
||||||
ExpressionField
|
ExpressionField,
|
||||||
|
OperatorType
|
||||||
} from './types';
|
} from './types';
|
||||||
import {ClassNamesFn} from '../../theme';
|
import {ThemeProps, themeable} from '../../theme';
|
||||||
import {Icon} from '../icons';
|
import {Icon} from '../icons';
|
||||||
import {autobind, findTree, noop} from '../../utils/helper';
|
import {autobind, findTree, noop} from '../../utils/helper';
|
||||||
import Expression from './Expression';
|
import Expression from './Expression';
|
||||||
|
@ -21,13 +22,12 @@ import ResultBox from '../ResultBox';
|
||||||
|
|
||||||
const option2value = (item: any) => item.value;
|
const option2value = (item: any) => item.value;
|
||||||
|
|
||||||
export interface ConditionItemProps {
|
export interface ConditionItemProps extends ThemeProps {
|
||||||
config: Config;
|
config: Config;
|
||||||
fields: Fields;
|
fields: Fields;
|
||||||
funcs?: Funcs;
|
funcs?: Funcs;
|
||||||
index?: number;
|
index?: number;
|
||||||
value: ConditionRule;
|
value: ConditionRule;
|
||||||
classnames: ClassNamesFn;
|
|
||||||
onChange: (value: ConditionRule, index?: number) => void;
|
onChange: (value: ConditionRule, index?: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,18 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
|
||||||
}
|
}
|
||||||
|
|
||||||
@autobind
|
@autobind
|
||||||
handleOperatorChange() {}
|
handleOperatorChange(op: OperatorType) {
|
||||||
|
const value = {...this.props.value, op: op};
|
||||||
|
this.props.onChange(value, this.props.index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleRightChange(rightValue: any) {
|
||||||
|
const value = {...this.props.value, right: rightValue};
|
||||||
|
const onChange = this.props.onChange;
|
||||||
|
|
||||||
|
onChange(value, this.props.index);
|
||||||
|
}
|
||||||
|
|
||||||
renderLeft() {
|
renderLeft() {
|
||||||
const {value, fields, funcs} = this.props;
|
const {value, fields, funcs} = this.props;
|
||||||
|
@ -101,7 +112,7 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
|
||||||
) as FieldSimple;
|
) as FieldSimple;
|
||||||
|
|
||||||
if (field) {
|
if (field) {
|
||||||
operators = field.operators || config.types[field.type].operators;
|
operators = field.operators || config.types[field.type]?.operators;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -149,25 +160,92 @@ export class ConditionItem extends React.Component<ConditionItemProps> {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderItem() {
|
renderRight() {
|
||||||
|
const {value, funcs, fields} = this.props;
|
||||||
|
|
||||||
|
if (!value?.op) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
const left = value?.left;
|
||||||
|
let leftType = '';
|
||||||
|
|
||||||
|
if ((left as ExpressionFunc)?.type === 'func') {
|
||||||
|
const func: Func = findTree(
|
||||||
|
funcs!,
|
||||||
|
(i: Func) => i.type === (left as ExpressionFunc).type
|
||||||
|
) as Func;
|
||||||
|
|
||||||
|
if (func) {
|
||||||
|
leftType = func.returnType;
|
||||||
|
}
|
||||||
|
} else if ((left as ExpressionField)?.type === 'field') {
|
||||||
|
const field: FieldSimple = findTree(
|
||||||
|
fields,
|
||||||
|
(i: FieldSimple) => i.name === (left as ExpressionField).field
|
||||||
|
) as FieldSimple;
|
||||||
|
|
||||||
|
if (field) {
|
||||||
|
leftType = field.type;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (leftType) {
|
||||||
|
return this.renderRightWidgets(leftType, value.op!);
|
||||||
|
}
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderRightWidgets(type: string, op: OperatorType) {
|
||||||
|
const {funcs, value, fields, config} = this.props;
|
||||||
|
let field = {
|
||||||
|
...config.types[type],
|
||||||
|
type
|
||||||
|
} as FieldSimple;
|
||||||
|
|
||||||
|
if ((value?.left as ExpressionField)?.type === 'field') {
|
||||||
|
const leftField: FieldSimple = findTree(
|
||||||
|
fields,
|
||||||
|
(i: FieldSimple) => i.name === (value?.left as ExpressionField).field
|
||||||
|
) as FieldSimple;
|
||||||
|
|
||||||
|
if (leftField) {
|
||||||
|
field = {
|
||||||
|
...field,
|
||||||
|
...leftField
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op === 'is_empty' || op === 'is_not_empty') {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Expression
|
||||||
|
funcs={funcs}
|
||||||
|
valueField={field}
|
||||||
|
value={value.right}
|
||||||
|
onChange={this.handleRightChange}
|
||||||
|
fields={fields}
|
||||||
|
defaultType="value"
|
||||||
|
allowedTypes={field?.valueTypes || ['value', 'field', 'func', 'raw']}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {classnames: cx} = this.props;
|
const {classnames: cx} = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={cx('CBItem')}>
|
<div className={cx('CBItem')}>
|
||||||
<a className={cx('CBItem-dragbar')}>
|
{this.renderLeft()}
|
||||||
<Icon icon="drag-bar" className="icon" />
|
{this.renderOperator()}
|
||||||
</a>
|
{this.renderRight()}
|
||||||
|
|
||||||
<div className={cx('CBItem-itemBody')}>
|
|
||||||
{this.renderLeft()}
|
|
||||||
{this.renderOperator()}
|
|
||||||
{this.renderItem()}
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default themeable(ConditionItem);
|
||||||
|
|
|
@ -1,7 +1,31 @@
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import {FieldSimple} from './types';
|
||||||
|
import {ThemeProps, themeable} from '../../theme';
|
||||||
|
import InputBox from '../InputBox';
|
||||||
|
|
||||||
export default class Value extends React.Component<any> {
|
export interface ValueProps extends ThemeProps {
|
||||||
|
value: any;
|
||||||
|
onChange: (value: any) => void;
|
||||||
|
field: FieldSimple;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Value extends React.Component<ValueProps> {
|
||||||
render() {
|
render() {
|
||||||
return <p>Value</p>;
|
const {classnames: cx, field, value, onChange} = this.props;
|
||||||
|
let input: JSX.Element | undefined = undefined;
|
||||||
|
|
||||||
|
if (field.type === 'text') {
|
||||||
|
input = (
|
||||||
|
<InputBox
|
||||||
|
value={value}
|
||||||
|
onChange={onChange}
|
||||||
|
placeholder={field.placeholder}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return <div className={cx('CBValue')}>{input}</div>;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export default themeable(Value);
|
||||||
|
|
|
@ -18,15 +18,16 @@ export const OperationMap = {
|
||||||
not_equal: '不等于',
|
not_equal: '不等于',
|
||||||
is_empty: '为空',
|
is_empty: '为空',
|
||||||
is_not_empty: '不为空',
|
is_not_empty: '不为空',
|
||||||
like: 'LIKE',
|
like: '模糊匹配',
|
||||||
not_like: 'NOT LIKE',
|
not_like: '不匹配',
|
||||||
starts_with: 'Start With',
|
starts_with: '匹配开头',
|
||||||
ends_with: 'Ends With'
|
ends_with: '匹配结尾'
|
||||||
};
|
};
|
||||||
|
|
||||||
const defaultConfig: Config = {
|
const defaultConfig: Config = {
|
||||||
types: {
|
types: {
|
||||||
text: {
|
text: {
|
||||||
|
placeholder: '请输入文本',
|
||||||
operators: [
|
operators: [
|
||||||
'equal',
|
'equal',
|
||||||
'not_equal',
|
'not_equal',
|
||||||
|
|
|
@ -3,8 +3,11 @@ import {ThemeProps, themeable} from '../../theme';
|
||||||
import {LocaleProps, localeable} from '../../locale';
|
import {LocaleProps, localeable} from '../../locale';
|
||||||
import {uncontrollable} from 'uncontrollable';
|
import {uncontrollable} from 'uncontrollable';
|
||||||
import {Fields, ConditionGroupValue, Funcs} from './types';
|
import {Fields, ConditionGroupValue, Funcs} from './types';
|
||||||
import {ConditionGroup} from './Group';
|
import ConditionGroup from './Group';
|
||||||
import defaultConfig from './config';
|
import defaultConfig from './config';
|
||||||
|
import {autobind, findTreeIndex} from '../../utils/helper';
|
||||||
|
import {findDOMNode} from 'react-dom';
|
||||||
|
import animtion from '../../utils/Animation';
|
||||||
|
|
||||||
export interface QueryBuilderProps extends ThemeProps, LocaleProps {
|
export interface QueryBuilderProps extends ThemeProps, LocaleProps {
|
||||||
fields: Fields;
|
fields: Fields;
|
||||||
|
@ -16,6 +19,148 @@ export interface QueryBuilderProps extends ThemeProps, LocaleProps {
|
||||||
export class QueryBuilder extends React.Component<QueryBuilderProps> {
|
export class QueryBuilder extends React.Component<QueryBuilderProps> {
|
||||||
config = defaultConfig;
|
config = defaultConfig;
|
||||||
|
|
||||||
|
dragTarget: HTMLElement;
|
||||||
|
ghost: HTMLElement;
|
||||||
|
host: HTMLElement;
|
||||||
|
lastX: number;
|
||||||
|
lastY: number;
|
||||||
|
lastMoveAt: number = 0;
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleDragStart(e: React.DragEvent) {
|
||||||
|
const target = e.currentTarget;
|
||||||
|
const item = target.closest('[data-id]') as HTMLElement;
|
||||||
|
this.dragTarget = item;
|
||||||
|
this.host = item.closest('[data-group-id]') as HTMLElement;
|
||||||
|
|
||||||
|
const ghost = item.cloneNode(true) as HTMLElement;
|
||||||
|
ghost.classList.add('is-ghost');
|
||||||
|
this.ghost = ghost;
|
||||||
|
|
||||||
|
e.dataTransfer.setDragImage(item.firstChild as HTMLElement, 0, 0);
|
||||||
|
|
||||||
|
const dom = findDOMNode(this) as HTMLElement;
|
||||||
|
|
||||||
|
target.addEventListener('dragend', this.handleDragEnd);
|
||||||
|
dom.addEventListener('dragover', this.handleDragOver);
|
||||||
|
dom.addEventListener('drop', this.handleDragDrop);
|
||||||
|
this.lastX = e.clientX;
|
||||||
|
this.lastY = e.clientY;
|
||||||
|
|
||||||
|
// 应该是 chrome 的一个bug,如果你马上修改,会马上执行 dragend
|
||||||
|
setTimeout(() => {
|
||||||
|
item.classList.add('is-dragging');
|
||||||
|
item.parentElement!.insertBefore(
|
||||||
|
item,
|
||||||
|
item.parentElement!.firstElementChild
|
||||||
|
); // 挪到第一个,主要是因为样式问题。
|
||||||
|
}, 5);
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleDragOver(e: DragEvent) {
|
||||||
|
e.preventDefault();
|
||||||
|
const item = (e.target as HTMLElement).closest('[data-id]') as HTMLElement;
|
||||||
|
|
||||||
|
const dx = e.clientX - this.lastX;
|
||||||
|
const dy = e.clientY - this.lastY;
|
||||||
|
const d = Math.max(Math.abs(dx), Math.abs(dy));
|
||||||
|
const now = Date.now();
|
||||||
|
|
||||||
|
// 没移动还是不要处理,免得晃动个不停。
|
||||||
|
if (d < 5) {
|
||||||
|
if (this.lastMoveAt === 0) {
|
||||||
|
} else if (now - this.lastMoveAt > 500) {
|
||||||
|
const host = (e.target as HTMLElement).closest(
|
||||||
|
'[data-group-id]'
|
||||||
|
) as HTMLElement;
|
||||||
|
|
||||||
|
if (host) {
|
||||||
|
this.host = host;
|
||||||
|
this.lastMoveAt = now;
|
||||||
|
this.lastX = 0;
|
||||||
|
this.lastY = 0;
|
||||||
|
this.handleDragOver(e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.lastMoveAt = now;
|
||||||
|
this.lastX = e.clientX;
|
||||||
|
this.lastY = e.clientY;
|
||||||
|
|
||||||
|
if (
|
||||||
|
!item ||
|
||||||
|
item.classList.contains('is-ghost') ||
|
||||||
|
item.closest('[data-group-id]') !== this.host
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const container = item.parentElement!;
|
||||||
|
const children = [].slice.apply(container!.children);
|
||||||
|
|
||||||
|
const idx = children.indexOf(item);
|
||||||
|
|
||||||
|
if (this.ghost.parentElement !== container) {
|
||||||
|
container.appendChild(this.ghost);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = item.getBoundingClientRect();
|
||||||
|
const isAfter = dy > 0 && e.clientY > rect.top + rect.height / 2;
|
||||||
|
const gIdx = isAfter ? idx : idx - 1;
|
||||||
|
const cgIdx = children.indexOf(this.ghost);
|
||||||
|
|
||||||
|
if (gIdx !== cgIdx) {
|
||||||
|
animtion.capture(container);
|
||||||
|
|
||||||
|
if (gIdx === children.length - 1) {
|
||||||
|
container.appendChild(this.ghost);
|
||||||
|
} else {
|
||||||
|
container.insertBefore(this.ghost, children[gIdx + 1]);
|
||||||
|
}
|
||||||
|
|
||||||
|
animtion.animateAll();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleDragDrop() {
|
||||||
|
const fromId = this.dragTarget.getAttribute('data-id')!;
|
||||||
|
const toId = this.host.getAttribute('data-group-id')!;
|
||||||
|
const toIndex =
|
||||||
|
[].slice.call(this.ghost.parentElement!.children).indexOf(this.ghost) - 1;
|
||||||
|
const value = this.props.value!;
|
||||||
|
|
||||||
|
const indexes = findTreeIndex([value], item => item.id === fromId);
|
||||||
|
|
||||||
|
if (indexes) {
|
||||||
|
// model.setOptions(
|
||||||
|
// spliceTree(model.options, indexes, 1, {
|
||||||
|
// ...origin,
|
||||||
|
// ...result
|
||||||
|
// })
|
||||||
|
// );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@autobind
|
||||||
|
handleDragEnd(e: Event) {
|
||||||
|
const dom = findDOMNode(this) as HTMLElement;
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
|
||||||
|
target.removeEventListener('dragend', this.handleDragEnd);
|
||||||
|
dom.removeEventListener('dragover', this.handleDragOver);
|
||||||
|
dom.removeEventListener('drop', this.handleDragDrop);
|
||||||
|
|
||||||
|
this.dragTarget.classList.remove('is-dragging');
|
||||||
|
delete this.dragTarget;
|
||||||
|
this.ghost.parentElement?.removeChild(this.ghost);
|
||||||
|
delete this.ghost;
|
||||||
|
}
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {classnames: cx, fields, funcs, onChange, value} = this.props;
|
const {classnames: cx, fields, funcs, onChange, value} = this.props;
|
||||||
|
|
||||||
|
@ -28,6 +173,7 @@ export class QueryBuilder extends React.Component<QueryBuilderProps> {
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
classnames={cx}
|
classnames={cx}
|
||||||
removeable={false}
|
removeable={false}
|
||||||
|
onDragStart={this.handleDragStart}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,10 +8,14 @@ export type FieldTypes =
|
||||||
| 'select';
|
| 'select';
|
||||||
|
|
||||||
export type OperatorType =
|
export type OperatorType =
|
||||||
| 'equals'
|
| 'equal'
|
||||||
| 'not_equals'
|
| 'not_equal'
|
||||||
| 'less_than'
|
| 'is_empty'
|
||||||
| 'less_than_or_equals';
|
| 'is_not_empty'
|
||||||
|
| 'like'
|
||||||
|
| 'not_like'
|
||||||
|
| 'starts_with'
|
||||||
|
| 'ends_with';
|
||||||
|
|
||||||
export type FieldItem = {
|
export type FieldItem = {
|
||||||
type: 'text';
|
type: 'text';
|
||||||
|
@ -64,7 +68,7 @@ export interface ConditionValue extends ConditionGroupValue {}
|
||||||
interface BaseField {
|
interface BaseField {
|
||||||
type: FieldTypes;
|
type: FieldTypes;
|
||||||
label: string;
|
label: string;
|
||||||
valueTypes?: Array<'value' | 'field' | 'func' | 'expression'>;
|
valueTypes?: Array<'value' | 'field' | 'func' | 'raw'>;
|
||||||
operators?: Array<string>;
|
operators?: Array<string>;
|
||||||
|
|
||||||
// valueTypes 里面配置 func 才有效。
|
// valueTypes 里面配置 func 才有效。
|
||||||
|
@ -81,6 +85,7 @@ type FieldGroup = {
|
||||||
interface TextField extends BaseField {
|
interface TextField extends BaseField {
|
||||||
name: string;
|
name: string;
|
||||||
type: 'text';
|
type: 'text';
|
||||||
|
placeholder?: string;
|
||||||
minLength?: number;
|
minLength?: number;
|
||||||
maxLength?: number;
|
maxLength?: number;
|
||||||
}
|
}
|
||||||
|
@ -160,4 +165,5 @@ export type Fields = Array<Field>;
|
||||||
|
|
||||||
export type Type = {
|
export type Type = {
|
||||||
operators: Array<string>;
|
operators: Array<string>;
|
||||||
|
placeholder?: string;
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,379 @@
|
||||||
|
// 基本上都是从 sortable 那抄的,让拖拽看起来更流畅。
|
||||||
|
|
||||||
|
interface Rect {
|
||||||
|
top: number;
|
||||||
|
left: number;
|
||||||
|
bottom: number;
|
||||||
|
right: number;
|
||||||
|
width: number;
|
||||||
|
height: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface AnimationState {
|
||||||
|
rect: Rect;
|
||||||
|
target: HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
function userAgent(pattern: RegExp): any {
|
||||||
|
if (typeof window !== 'undefined' && window.navigator) {
|
||||||
|
return !!(/*@__PURE__*/ navigator.userAgent.match(pattern));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const IE11OrLess = userAgent(
|
||||||
|
/(?:Trident.*rv[ :]?11\.|msie|iemobile|Windows Phone)/i
|
||||||
|
);
|
||||||
|
// const Edge = userAgent(/Edge/i);
|
||||||
|
// const FireFox = userAgent(/firefox/i);
|
||||||
|
// const Safari =
|
||||||
|
// userAgent(/safari/i) && !userAgent(/chrome/i) && !userAgent(/android/i);
|
||||||
|
// const IOS = userAgent(/iP(ad|od|hone)/i);
|
||||||
|
// const ChromeForAndroid = userAgent(/chrome/i) && userAgent(/android/i);
|
||||||
|
|
||||||
|
const AnimationDurtation = 150;
|
||||||
|
const AnimationEasing = 'cubic-bezier(1, 0, 0, 1)';
|
||||||
|
|
||||||
|
export class AnimationManager {
|
||||||
|
animating: boolean = false;
|
||||||
|
animationCallbackId: any;
|
||||||
|
states: Array<AnimationState> = [];
|
||||||
|
|
||||||
|
capture(el: HTMLElement) {
|
||||||
|
// 清空,重复调用,旧的不管了
|
||||||
|
this.states = [];
|
||||||
|
|
||||||
|
let children: Array<HTMLElement> = [].slice.call(el.children);
|
||||||
|
children.forEach(child => {
|
||||||
|
// 如果是 ghost
|
||||||
|
if (child.classList.contains('is-ghost')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const rect = getRect(child)!;
|
||||||
|
|
||||||
|
// 通常是隐藏节点
|
||||||
|
if (!rect.width) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let fromRect = {...rect};
|
||||||
|
|
||||||
|
const state: AnimationState = {
|
||||||
|
target: child,
|
||||||
|
rect
|
||||||
|
};
|
||||||
|
|
||||||
|
// 还在动画中
|
||||||
|
if ((child as any).thisAnimationDuration) {
|
||||||
|
let childMatrix = matrix(child);
|
||||||
|
|
||||||
|
if (childMatrix) {
|
||||||
|
fromRect.top -= childMatrix.f;
|
||||||
|
fromRect.left -= childMatrix.e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(child as any).fromRect = fromRect;
|
||||||
|
this.states.push(state);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
animateAll(callback?: () => void) {
|
||||||
|
this.animating = false;
|
||||||
|
let animationTime = 0;
|
||||||
|
|
||||||
|
this.states.forEach(state => {
|
||||||
|
let time = 0,
|
||||||
|
target = state.target,
|
||||||
|
fromRect = (target as any).fromRect,
|
||||||
|
toRect = {
|
||||||
|
...getRect(target)!
|
||||||
|
},
|
||||||
|
prevFromRect = (target as any).prevFromRect,
|
||||||
|
prevToRect = (target as any).prevToRect,
|
||||||
|
animatingRect = state.rect,
|
||||||
|
targetMatrix = matrix(target);
|
||||||
|
|
||||||
|
if (targetMatrix) {
|
||||||
|
// Compensate for current animation
|
||||||
|
toRect.top -= targetMatrix.f;
|
||||||
|
toRect.left -= targetMatrix.e;
|
||||||
|
}
|
||||||
|
|
||||||
|
(target as any).toRect = toRect;
|
||||||
|
|
||||||
|
if ((target as any).thisAnimationDuration) {
|
||||||
|
// Could also check if animatingRect is between fromRect and toRect
|
||||||
|
if (
|
||||||
|
isRectEqual(prevFromRect, toRect) &&
|
||||||
|
!isRectEqual(fromRect, toRect) &&
|
||||||
|
// Make sure animatingRect is on line between toRect & fromRect
|
||||||
|
(animatingRect.top - toRect.top) /
|
||||||
|
(animatingRect.left - toRect.left) ===
|
||||||
|
(fromRect.top - toRect.top) / (fromRect.left - toRect.left)
|
||||||
|
) {
|
||||||
|
// If returning to same place as started from animation and on same axis
|
||||||
|
time = calculateRealTime(animatingRect, prevFromRect, prevToRect);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if fromRect != toRect: animate
|
||||||
|
if (!isRectEqual(toRect, fromRect)) {
|
||||||
|
(target as any).prevFromRect = fromRect;
|
||||||
|
(target as any).prevToRect = toRect;
|
||||||
|
|
||||||
|
if (!time) {
|
||||||
|
time = AnimationDurtation;
|
||||||
|
}
|
||||||
|
this.animate(target, animatingRect, toRect, time);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (time) {
|
||||||
|
this.animating = true;
|
||||||
|
animationTime = Math.max(animationTime, time);
|
||||||
|
clearTimeout((target as any).animationResetTimer);
|
||||||
|
(target as any).animationResetTimer = setTimeout(function () {
|
||||||
|
(target as any).animationTime = 0;
|
||||||
|
(target as any).prevFromRect = null;
|
||||||
|
(target as any).fromRect = null;
|
||||||
|
(target as any).prevToRect = null;
|
||||||
|
(target as any).thisAnimationDuration = null;
|
||||||
|
}, time);
|
||||||
|
(target as any).thisAnimationDuration = time;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
clearTimeout(this.animationCallbackId);
|
||||||
|
if (!this.animating) {
|
||||||
|
if (typeof callback === 'function') callback();
|
||||||
|
} else {
|
||||||
|
this.animationCallbackId = setTimeout(() => {
|
||||||
|
this.animating = false;
|
||||||
|
if (typeof callback === 'function') callback();
|
||||||
|
}, animationTime);
|
||||||
|
}
|
||||||
|
this.states = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
animate(
|
||||||
|
target: HTMLElement,
|
||||||
|
currentRect: Rect,
|
||||||
|
toRect: Rect,
|
||||||
|
duration: number
|
||||||
|
) {
|
||||||
|
if (duration) {
|
||||||
|
let affectDisplay = false;
|
||||||
|
css(target, 'transition', '');
|
||||||
|
css(target, 'transform', '');
|
||||||
|
let translateX = currentRect.left - toRect.left,
|
||||||
|
translateY = currentRect.top - toRect.top;
|
||||||
|
|
||||||
|
(target as any).animatingX = !!translateX;
|
||||||
|
(target as any).animatingY = !!translateY;
|
||||||
|
|
||||||
|
css(
|
||||||
|
target,
|
||||||
|
'transform',
|
||||||
|
'translate3d(' + translateX + 'px,' + translateY + 'px,0)'
|
||||||
|
);
|
||||||
|
|
||||||
|
if (css(target, 'display') === 'inline') {
|
||||||
|
affectDisplay = true;
|
||||||
|
css(target, 'display', 'inline-block');
|
||||||
|
}
|
||||||
|
|
||||||
|
target.offsetWidth; // repaint
|
||||||
|
|
||||||
|
css(
|
||||||
|
target,
|
||||||
|
'transition',
|
||||||
|
'transform ' +
|
||||||
|
duration +
|
||||||
|
'ms' +
|
||||||
|
(AnimationEasing ? ' ' + AnimationEasing : '')
|
||||||
|
);
|
||||||
|
css(target, 'transform', 'translate3d(0,0,0)');
|
||||||
|
typeof (target as any).animated === 'number' &&
|
||||||
|
clearTimeout((target as any).animated);
|
||||||
|
(target as any).animated = setTimeout(function () {
|
||||||
|
css(target, 'transition', '');
|
||||||
|
css(target, 'transform', '');
|
||||||
|
affectDisplay && css(target, 'display', '');
|
||||||
|
(target as any).animated = false;
|
||||||
|
|
||||||
|
(target as any).animatingX = false;
|
||||||
|
(target as any).animatingY = false;
|
||||||
|
}, duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function matrix(el: HTMLElement) {
|
||||||
|
let appliedTransforms = '';
|
||||||
|
if (typeof el === 'string') {
|
||||||
|
appliedTransforms = el;
|
||||||
|
} else {
|
||||||
|
let transform = css(el, 'transform');
|
||||||
|
|
||||||
|
if (transform && transform !== 'none') {
|
||||||
|
appliedTransforms = transform + ' ' + appliedTransforms;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const matrixFn =
|
||||||
|
window.DOMMatrix ||
|
||||||
|
window.WebKitCSSMatrix ||
|
||||||
|
(window as any).CSSMatrix ||
|
||||||
|
(window as any).MSCSSMatrix;
|
||||||
|
|
||||||
|
/*jshint -W056 */
|
||||||
|
return matrixFn && new matrixFn(appliedTransforms);
|
||||||
|
}
|
||||||
|
|
||||||
|
function css(el: HTMLElement, prop: string, val?: any) {
|
||||||
|
let style = el && el.style;
|
||||||
|
|
||||||
|
if (style) {
|
||||||
|
if (val === void 0) {
|
||||||
|
if (document.defaultView && document.defaultView.getComputedStyle) {
|
||||||
|
val = document.defaultView.getComputedStyle(el, '');
|
||||||
|
} else if ((el as any).currentStyle) {
|
||||||
|
val = (el as any).currentStyle;
|
||||||
|
}
|
||||||
|
|
||||||
|
return prop === void 0 ? val : val[prop];
|
||||||
|
} else {
|
||||||
|
if (!(prop in style) && prop.indexOf('webkit') === -1) {
|
||||||
|
prop = '-webkit-' + prop;
|
||||||
|
}
|
||||||
|
|
||||||
|
(style as any)[prop] = val + (typeof val === 'string' ? '' : 'px');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRectEqual(rect1: Rect, rect2: Rect) {
|
||||||
|
return (
|
||||||
|
Math.round(rect1.top) === Math.round(rect2.top) &&
|
||||||
|
Math.round(rect1.left) === Math.round(rect2.left) &&
|
||||||
|
Math.round(rect1.height) === Math.round(rect2.height) &&
|
||||||
|
Math.round(rect1.width) === Math.round(rect2.width)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function calculateRealTime(animatingRect: Rect, fromRect: Rect, toRect: Rect) {
|
||||||
|
return (
|
||||||
|
(Math.sqrt(
|
||||||
|
Math.pow(fromRect.top - animatingRect.top, 2) +
|
||||||
|
Math.pow(fromRect.left - animatingRect.left, 2)
|
||||||
|
) /
|
||||||
|
Math.sqrt(
|
||||||
|
Math.pow(fromRect.top - toRect.top, 2) +
|
||||||
|
Math.pow(fromRect.left - toRect.left, 2)
|
||||||
|
)) *
|
||||||
|
AnimationDurtation
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getWindowScrollingElement() {
|
||||||
|
let scrollingElement = document.scrollingElement;
|
||||||
|
|
||||||
|
if (scrollingElement) {
|
||||||
|
return scrollingElement;
|
||||||
|
} else {
|
||||||
|
return document.documentElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRect(
|
||||||
|
el: HTMLElement,
|
||||||
|
relativeToContainingBlock?: boolean,
|
||||||
|
relativeToNonStaticParent?: boolean,
|
||||||
|
undoScale?: boolean,
|
||||||
|
container?: HTMLElement
|
||||||
|
) {
|
||||||
|
if (!el.getBoundingClientRect && (el as any) !== window) return;
|
||||||
|
|
||||||
|
let elRect, top, left, bottom, right, height, width;
|
||||||
|
|
||||||
|
if ((el as any) !== window && el !== getWindowScrollingElement()) {
|
||||||
|
elRect = el.getBoundingClientRect();
|
||||||
|
top = elRect.top;
|
||||||
|
left = elRect.left;
|
||||||
|
bottom = elRect.bottom;
|
||||||
|
right = elRect.right;
|
||||||
|
height = elRect.height;
|
||||||
|
width = elRect.width;
|
||||||
|
} else {
|
||||||
|
top = 0;
|
||||||
|
left = 0;
|
||||||
|
bottom = window.innerHeight;
|
||||||
|
right = window.innerWidth;
|
||||||
|
height = window.innerHeight;
|
||||||
|
width = window.innerWidth;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(relativeToContainingBlock || relativeToNonStaticParent) &&
|
||||||
|
(el as any) !== window
|
||||||
|
) {
|
||||||
|
// Adjust for translate()
|
||||||
|
container = container || (el.parentNode as HTMLElement);
|
||||||
|
|
||||||
|
// solves #1123 (see: https://stackoverflow.com/a/37953806/6088312)
|
||||||
|
// Not needed on <= IE11
|
||||||
|
if (!IE11OrLess) {
|
||||||
|
do {
|
||||||
|
if (
|
||||||
|
container &&
|
||||||
|
container.getBoundingClientRect &&
|
||||||
|
(css(container, 'transform') !== 'none' ||
|
||||||
|
(relativeToNonStaticParent &&
|
||||||
|
css(container, 'position') !== 'static'))
|
||||||
|
) {
|
||||||
|
let containerRect = container.getBoundingClientRect();
|
||||||
|
|
||||||
|
// Set relative to edges of padding box of container
|
||||||
|
top -=
|
||||||
|
containerRect.top + parseInt(css(container, 'border-top-width'));
|
||||||
|
left -=
|
||||||
|
containerRect.left + parseInt(css(container, 'border-left-width'));
|
||||||
|
bottom = top + (elRect as any).height;
|
||||||
|
right = left + (elRect as any).width;
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* jshint boss:true */
|
||||||
|
} while ((container = container.parentNode as HTMLElement));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (undoScale && (el as any) !== window) {
|
||||||
|
// Adjust for scale()
|
||||||
|
let elMatrix = matrix(container || el),
|
||||||
|
scaleX = elMatrix && elMatrix.a,
|
||||||
|
scaleY = elMatrix && elMatrix.d;
|
||||||
|
|
||||||
|
if (elMatrix) {
|
||||||
|
top /= scaleY;
|
||||||
|
left /= scaleX;
|
||||||
|
|
||||||
|
width /= scaleX;
|
||||||
|
height /= scaleY;
|
||||||
|
|
||||||
|
bottom = top + height;
|
||||||
|
right = left + width;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
top: top,
|
||||||
|
left: left,
|
||||||
|
bottom: bottom,
|
||||||
|
right: right,
|
||||||
|
width: width,
|
||||||
|
height: height
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default new AnimationManager();
|
Loading…
Reference in New Issue