Skip to content
Open
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
12 changes: 8 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ yarn add react-rnd

## Props

#### `default: { x: number; y: number; width?: number | string; height?: number | string; };`
#### `default: { x: number | string; y: number | string; width?: number | string; height?: number | string; };`

The `width` and `height` property is used to set the default size of the component.
For example, you can set `300`, `'300px'`, `50%`.
Expand All @@ -121,10 +121,14 @@ For example, you can set 300, '300px', 50%.

Use `size` if you need to control size state by yourself.

#### `position?: { x: number, y: number };`
#### `position?: { x: (number | string), y: (number | string) };`

The `position` property is used to set position of the component.
Use `position` if you need to control size state by yourself.
Use `position` if you need to control position state by yourself.
You can pass:
- numbers (treated as pixels), e.g. `x: 100`
- strings with `'px'`, e.g. `'300px'`
- strings with `'%'`, e.g. `'50%'` (percentage of the parent size)

see, following example.

Expand Down Expand Up @@ -464,7 +468,7 @@ class YourComponent extends Component {
}
```

#### `updatePosition({ x: number, y: number }): void`
#### `updatePosition({ x: number | string, y: number | string }): void`

Update component position.
`grid` `bounds` props is ignored, when this method called.
Expand Down
3 changes: 3 additions & 0 deletions src/index.js.flow
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,8 @@ export type HandleComponent = {
topLeft?: React.ReactElement<any>;
}

export type PositionUnit = 'px' | '%';

export type Props = {
dragGrid?: Grid,
default?: {
Expand All @@ -114,6 +116,7 @@ export type Props = {
x: number,
y: number,
},
positionUnit?: PositionUnit,
size?: Size,
resizeGrid?: Grid,
bounds?: string,
Expand Down
154 changes: 128 additions & 26 deletions src/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ type State = {
};
maxWidth?: number | string;
maxHeight?: number | string;
parentSize: { width: number; height: number } | null;
};

type MaxSize = {
Expand Down Expand Up @@ -116,12 +117,12 @@ export type HandleComponent = {
export interface Props {
dragGrid?: Grid;
default?: {
x: number;
y: number;
x: number | string;
y: number | string;
} & Size;
position?: {
x: number;
y: number;
x: number | string;
y: number | string;
};
size?: Size;
resizeGrid?: Grid;
Expand Down Expand Up @@ -181,6 +182,30 @@ const getEnableResizingByFlag = (flag: boolean): Enable => ({
topRight: flag,
});

function parsePositionValue(
value: number | string,
axisSize: number | null,
): number {
if (typeof value === "number") {
return value;
}
const str = value.toString().trim();
if (str.endsWith("%")) {
const n = Number(str.slice(0, -1));
if (Number.isNaN(n) || axisSize == null) return 0;
return (n / 100) * axisSize;
}
if (str.endsWith("px")) {
const n = Number(str.slice(0, -2));
return Number.isNaN(n) ? 0 : n;
}
const n = Number(str);
if (!Number.isNaN(n)) {
return n;
}
return 0;
}

interface DefaultProps {
maxWidth: number;
maxHeight: number;
Expand Down Expand Up @@ -224,6 +249,7 @@ export class Rnd extends React.PureComponent<Props, State> {
},
maxWidth: props.maxWidth,
maxHeight: props.maxHeight,
parentSize: null,
};

this.onResizeStart = this.onResizeStart.bind(this);
Expand All @@ -236,17 +262,55 @@ export class Rnd extends React.PureComponent<Props, State> {
}

componentDidMount() {
this.updateParentSize();
this.updateOffsetFromParent();
const { left, top } = this.offsetFromParent;
const { x, y } = this.getDraggablePosition();
this.draggable.setState({
x: x - left,
y: y - top,
});
const defaultValue = this.props.default;
let parentSize: { width: number; height: number } | null = null;
if (this.resizable) {
try {
parentSize = this.getParentSize();
} catch {
// refs may not be ready
}
}

if (defaultValue && parentSize) {
const x = parsePositionValue(defaultValue.x, parentSize.width);
const y = parsePositionValue(defaultValue.y, parentSize.height);
this.draggable.setState({
x: x - left,
y: y - top,
});
} else {
const { x, y } = this.getDraggablePosition();
this.draggable.setState({
x: x - left,
y: y - top,
});
}
// HACK: Apply position adjustment
this.forceUpdate();
}

componentDidUpdate() {
this.updateParentSize();
}

updateParentSize() {
const parent = this.getParent();
if (!parent || !this.resizable) return;
try {
const { width, height } = this.getParentSize();
const prev = this.state.parentSize;
if (!prev || prev.width !== width || prev.height !== height) {
this.setState({ parentSize: { width, height } });
}
} catch {
// getParentSize may throw before refs are ready
}
}

// HACK: To get `react-draggable` state x and y.
getDraggablePosition(): { x: number; y: number } {
const { x, y } = (this.draggable as any).state;
Expand Down Expand Up @@ -359,24 +423,42 @@ export class Rnd extends React.PureComponent<Props, State> {
onDrag(e: RndDragEvent, data: DraggableData) {
if (!this.props.onDrag) return;
const { left, top } = this.offsetFromParent;
let pos: Position;
if (!this.props.dragAxis || this.props.dragAxis === "both") {
pos = { x: data.x + left, y: data.y + top };
} else if (this.props.dragAxis === "x") {
pos = { x: data.x + left, y: this.originalPosition.y + top };
} else {
pos = { x: this.originalPosition.x + left, y: data.y + top };
}
const position = pos;
if (!this.props.dragAxis || this.props.dragAxis === "both") {
return this.props.onDrag(e, { ...data, x: data.x + left, y: data.y + top });
return this.props.onDrag(e, { ...data, ...position });
} else if (this.props.dragAxis === "x") {
return this.props.onDrag(e, { ...data, x: data.x + left, y: this.originalPosition.y + top, deltaY: 0 });
return this.props.onDrag(e, { ...data, ...position, deltaY: 0 });
} else if (this.props.dragAxis === "y") {
return this.props.onDrag(e, { ...data, x: this.originalPosition.x + left, y: data.y + top, deltaX: 0 });
return this.props.onDrag(e, { ...data, ...position, deltaX: 0 });
}
}

onDragStop(e: RndDragEvent, data: DraggableData) {
if (!this.props.onDragStop) return;
const { left, top } = this.offsetFromParent;
let pos: Position;
if (!this.props.dragAxis || this.props.dragAxis === "both") {
return this.props.onDragStop(e, { ...data, x: data.x + left, y: data.y + top });
pos = { x: data.x + left, y: data.y + top };
} else if (this.props.dragAxis === "x") {
return this.props.onDragStop(e, { ...data, x: data.x + left, y: this.originalPosition.y + top, deltaY: 0 });
pos = { x: data.x + left, y: this.originalPosition.y + top };
} else {
pos = { x: this.originalPosition.x + left, y: data.y + top };
}
const position = pos;
if (!this.props.dragAxis || this.props.dragAxis === "both") {
return this.props.onDragStop(e, { ...data, ...position });
} else if (this.props.dragAxis === "x") {
return this.props.onDragStop(e, { ...data, ...position, deltaY: 0 });
} else if (this.props.dragAxis === "y") {
return this.props.onDragStop(e, { ...data, x: this.originalPosition.x + left, y: data.y + top, deltaX: 0 });
return this.props.onDragStop(e, { ...data, ...position, deltaX: 0 });
}
}

Expand Down Expand Up @@ -518,10 +600,8 @@ export class Rnd extends React.PureComponent<Props, State> {

this.resizingPosition = { x, y };
if (!this.props.onResize) return;
this.props.onResize(e, direction, elementRef, delta, {
x,
y,
});
const position = { x, y };
this.props.onResize(e, direction, elementRef, delta, position);
}

onResizeStop(
Expand All @@ -536,7 +616,8 @@ export class Rnd extends React.PureComponent<Props, State> {
const { maxWidth, maxHeight } = this.getMaxSizesFromProps();
this.setState({ maxWidth, maxHeight });
if (this.props.onResizeStop) {
this.props.onResizeStop(e, direction, elementRef, delta, this.resizingPosition);
const position = this.resizingPosition;
this.props.onResizeStop(e, direction, elementRef, delta, position);
}
}

Expand All @@ -545,8 +626,19 @@ export class Rnd extends React.PureComponent<Props, State> {
this.resizable.updateSize({ width: size.width, height: size.height });
}

updatePosition(position: Position) {
this.draggable.setState(position);
updatePosition(position: { x: number | string; y: number | string }) {
let parentSize: { width: number; height: number } | null = null;
try {
parentSize = this.getParentSize();
} catch {
parentSize = null;
}
const axisWidth = parentSize ? parentSize.width : null;
const axisHeight = parentSize ? parentSize.height : null;
const { left, top } = this.offsetFromParent;
const x = parsePositionValue(position.x, axisWidth);
const y = parsePositionValue(position.y, axisHeight);
this.draggable.setState({ x: x - left, y: y - top });
}

updateOffsetFromParent() {
Expand Down Expand Up @@ -615,13 +707,23 @@ export class Rnd extends React.PureComponent<Props, State> {
...style,
};
const { left, top } = this.offsetFromParent;
let draggablePosition;
let draggablePosition: { x: number; y: number } | undefined;
if (position) {
const parentSize = this.state.parentSize;
const width = parentSize ? parentSize.width : null;
const height = parentSize ? parentSize.height : null;
const xPx = parsePositionValue(position.x, width);
const yPx = parsePositionValue(position.y, height);
draggablePosition = {
x: position.x - left,
y: position.y - top,
x: xPx - left,
y: yPx - top,
};
}

const defaultPositionForDraggable =
defaultValue && typeof defaultValue.x === "number" && typeof defaultValue.y === "number"
? { x: defaultValue.x, y: defaultValue.y }
: undefined;
// INFO: Make uncontorolled component when resizing to control position by setPostion.
const pos = this.state.resizing ? undefined : draggablePosition;
const dragAxisOrUndefined = this.state.resizing ? "both" : dragAxis;
Expand All @@ -633,7 +735,7 @@ export class Rnd extends React.PureComponent<Props, State> {
this.draggable = c;
}}
handle={dragHandleClassName ? `.${dragHandleClassName}` : undefined}
defaultPosition={defaultValue}
defaultPosition={defaultPositionForDraggable}
onMouseDown={onMouseDown}
// @ts-expect-error
onMouseUp={onMouseUp}
Expand Down
7 changes: 7 additions & 0 deletions stories/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ import BoundsElementUncontrolled from "./bounds/element-uncontrolled";
import SizePercentUncontrolled from "./size/size-percent-uncontrolled";
import SizePercentControlled from "./size/size-percent-controlled";

import PositionPercentUncontrolled from "./position/position-percent-uncontrolled";
import PositionPercentControlled from "./position/position-percent-controlled";

import Callbacks from "./callback/callbacks";

import Cancel from "./cancel/cancel";
Expand Down Expand Up @@ -82,6 +85,10 @@ storiesOf("size", module)
.add("percent uncontrolled", () => <SizePercentUncontrolled />)
.add("percent controlled", () => <SizePercentControlled />);

storiesOf("position", module)
.add("percent uncontrolled", () => <PositionPercentUncontrolled />)
.add("percent controlled", () => <PositionPercentControlled />);

storiesOf("callbacks", module).add("callback", () => <Callbacks />);

storiesOf("cancel", module).add("cancel", () => <Cancel />);
Expand Down
69 changes: 69 additions & 0 deletions stories/position/position-percent-controlled.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import React from "react";
import { Rnd } from "../../src";
import { style } from "../styles";

type State = {
x: number; // percentage of container width
y: number; // percentage of container height
width: number;
height: number;
};

const containerStyle: React.CSSProperties = {
width: 400,
height: 300,
position: "relative",
background: "#e8e8e8",
border: "1px solid #ccc",
};

export default class Example extends React.Component<{}, State> {
constructor(props: {}) {
super(props);
this.state = {
width: 120,
height: 80,
x: 10,
y: 20,
};
}

render() {
return (
<div style={containerStyle}>
<Rnd
style={style}
size={{
width: this.state.width,
height: this.state.height,
}}
position={{
x: `${this.state.x}%`,
y: `${this.state.y}%`,
}}
onDragStop={(e, d) => {
const containerWidth = containerStyle.width as number;
const containerHeight = containerStyle.height as number;
const x = (d.x / containerWidth) * 100;
const y = (d.y / containerHeight) * 100;
this.setState({ x, y });
}}
onResizeStop={(e, direction, ref, delta, position) => {
const containerWidth = containerStyle.width as number;
const containerHeight = containerStyle.height as number;
const x = (position.x / containerWidth) * 100;
const y = (position.y / containerHeight) * 100;
this.setState({
width: ref.offsetWidth,
height: ref.offsetHeight,
x,
y,
});
}}
>
Position in % (x: {this.state.x.toFixed(1)}, y: {this.state.y.toFixed(1)})
</Rnd>
</div>
);
}
}
Loading