Skip to content

Commit 4f9aba6

Browse files
committed
First implementation of floating resize handle with a larger touch handle to allow easier touch resizing
2 parents 55364d3 + 3865a3e commit 4f9aba6

File tree

6 files changed

+152
-20
lines changed

6 files changed

+152
-20
lines changed

src/components/Space.tsx

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,12 @@ export const Space: React.FC<ISpaceProps> = (props) => {
5858

5959
return (
6060
<>
61+
{resizeHandles.touchHandles.map((r) => (
62+
<div {...r} />
63+
))}
64+
{resizeHandles.mouseHandles.map((r) => (
65+
<div {...r} />
66+
))}
6167
{React.createElement(
6268
props.as || "div",
6369
{
@@ -70,9 +76,6 @@ export const Space: React.FC<ISpaceProps> = (props) => {
7076
...events,
7177
},
7278
<>
73-
{resizeHandles.map((r) => (
74-
<div {...r} />
75-
))}
7679
<ParentContext.Provider value={space.id}>
7780
<LayerContext.Provider value={undefined}>
7881
<DOMRectContext.Provider value={domRect}>{centeredContent}</DOMRectContext.Provider>

src/core-react.ts

Lines changed: 50 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -119,56 +119,97 @@ export function useSpace(props: ISpaceProps) {
119119
}
120120

121121
interface IResizeHandleProps {
122+
id?: string;
122123
key: string | number;
123124
style: CSSProperties;
124-
className: string;
125+
className?: string;
125126
onMouseDown: (e: React.MouseEvent<HTMLElement, MouseEvent>) => void;
126127
onTouchStart: (e: React.TouchEvent<HTMLElement>) => void;
127128
}
128129

129130
export function useSpaceResizeHandles(store: ISpaceStore, space: ISpaceDefinition, position: IPositionalProps | undefined, handleSize?: number) {
130-
const resizeHandles: IResizeHandleProps[] = [];
131-
const resizeHandleSize = coalesce(handleSize, 5);
131+
const mouseHandles: IResizeHandleProps[] = [];
132+
const touchHandles: IResizeHandleProps[] = [];
133+
const resizeHandleSize = coalesce(handleSize, 2);
132134

133135
if (position && position.rightResizable) {
134-
resizeHandles.push({
136+
mouseHandles.push({
137+
id: `${space.id}-m`,
135138
key: "right",
136139
style: { width: resizeHandleSize },
137140
className: `spaces-resize-handle resize-right`,
138141
onMouseDown: (event) => store.startMouseResize(ResizeType.Right, space, space.width, event),
139142
onTouchStart: (event) => store.startTouchResize(ResizeType.Right, space, space.width, event),
140143
});
144+
touchHandles.push({
145+
id: `${space.id}-t`,
146+
key: "right",
147+
style: { width: 30 },
148+
className: `spaces-touch-handle resize-right`,
149+
onMouseDown: (event) => event.preventDefault(),
150+
onTouchStart: (event) => store.startTouchResize(ResizeType.Right, space, space.width, event),
151+
});
141152
}
142153

143154
if (position && position.leftResizable) {
144-
resizeHandles.push({
155+
mouseHandles.push({
156+
id: `${space.id}-m`,
145157
key: "left",
146158
style: { width: resizeHandleSize },
147159
className: `spaces-resize-handle resize-left`,
148160
onMouseDown: (event) => store.startMouseResize(ResizeType.Left, space, space.width, event),
149161
onTouchStart: (event) => store.startTouchResize(ResizeType.Left, space, space.width, event),
150162
});
163+
touchHandles.push({
164+
id: `${space.id}-t`,
165+
key: "left",
166+
style: { width: 30 },
167+
className: `spaces-touch-handle resize-left`,
168+
onMouseDown: (event) => event.preventDefault(),
169+
onTouchStart: (event) => store.startTouchResize(ResizeType.Left, space, space.width, event),
170+
});
151171
}
152172

153173
if (position && position.topResizable) {
154-
resizeHandles.push({
174+
mouseHandles.push({
175+
id: `${space.id}-m`,
155176
key: "top",
156177
style: { height: resizeHandleSize },
157178
className: `spaces-resize-handle resize-top`,
158179
onMouseDown: (event) => store.startMouseResize(ResizeType.Top, space, space.height, event),
159180
onTouchStart: (event) => store.startTouchResize(ResizeType.Top, space, space.height, event),
160181
});
182+
touchHandles.push({
183+
id: `${space.id}-t`,
184+
key: "top",
185+
style: { height: 30 },
186+
className: `spaces-touch-handle resize-top`,
187+
onMouseDown: (event) => event.preventDefault(),
188+
onTouchStart: (event) => store.startTouchResize(ResizeType.Top, space, space.height, event),
189+
});
161190
}
162191

163192
if (position && position.bottomResizable) {
164-
resizeHandles.push({
193+
mouseHandles.push({
194+
id: `${space.id}-m`,
165195
key: "bottom",
166-
style: { height: resizeHandleSize },
167196
className: `spaces-resize-handle resize-bottom`,
197+
style: { height: resizeHandleSize },
168198
onMouseDown: (event) => store.startMouseResize(ResizeType.Bottom, space, space.height, event),
169199
onTouchStart: (event) => store.startTouchResize(ResizeType.Bottom, space, space.height, event),
170200
});
201+
touchHandles.push({
202+
id: `${space.id}-t`,
203+
key: "bottom",
204+
style: { height: 30 },
205+
className: `spaces-touch-handle resize-bottom`,
206+
onMouseDown: (event) => event.preventDefault(),
207+
onTouchStart: (event) => store.startTouchResize(ResizeType.Bottom, space, space.height, event),
208+
});
171209
}
172210

173-
return resizeHandles;
211+
return {
212+
mouseHandles,
213+
touchHandles,
214+
};
174215
}

src/core-types.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,4 +142,8 @@ export interface ISpaceDefinition {
142142
resizing: boolean;
143143
minimumSize?: number;
144144
maximumSize?: number;
145+
canResizeTop: boolean;
146+
canResizeLeft: boolean;
147+
canResizeRight: boolean;
148+
canResizeBottom: boolean;
145149
}

src/core-utils.ts

Lines changed: 34 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { ISpaceDefinition, SizeUnit, ISize } from "./core-types";
1+
import { ISpaceDefinition, SizeUnit, ISize, Type } from "./core-types";
22

33
export function shortuuid() {
44
let firstPart = (Math.random() * 46656) | 0;
@@ -10,9 +10,9 @@ export function getSizeString(size: SizeUnit) {
1010
return typeof size === "string" ? size : `${size}px`;
1111
}
1212

13-
export function css(size: ISize) {
13+
export function css(size: ISize, dontAddCalc?: boolean) {
1414
if (size.size === 0 && size.adjusted.length === 0 && size.resized === 0) {
15-
return `0`;
15+
return `0px`;
1616
}
1717

1818
const parts: string[] = [];
@@ -34,6 +34,10 @@ export function css(size: ISize) {
3434
return parts[0];
3535
}
3636

37+
if (dontAddCalc) {
38+
return parts.join(" + ");
39+
}
40+
3741
return `calc(${parts.join(" + ")})`;
3842
}
3943

@@ -72,6 +76,8 @@ export function throttle<F extends (...args: any) => any>(callback: F, limit: nu
7276
}
7377

7478
export function styleDefinition(space: ISpaceDefinition) {
79+
const cssElements: string[] = [];
80+
7581
const style: React.CSSProperties = {
7682
position: space.position,
7783
left: css(space.left),
@@ -114,7 +120,31 @@ export function styleDefinition(space: ISpaceDefinition) {
114120
cssString.push(`z-index: ${style.zIndex};`);
115121
}
116122

117-
return `#${space.id} { ${cssString.join(" ")} }`;
123+
if (cssString.length > 0) {
124+
cssElements.push(`#${space.id} { ${cssString.join(" ")} }`);
125+
}
126+
127+
if (space.canResizeLeft) {
128+
cssElements.push(`#${space.id}-t { left: calc(${css(space.left, true)} + ${css(space.width, true)} - 15px); }`);
129+
cssElements.push(`#${space.id}-m { left: calc(${css(space.left, true)} + ${css(space.width, true)} - 1px); }`);
130+
}
131+
132+
if (space.canResizeTop) {
133+
cssElements.push(`#${space.id}-t { top: calc(${css(space.top, true)} + ${css(space.height, true)} - 15px); }`);
134+
cssElements.push(`#${space.id}-m { top: calc(${css(space.top, true)} + ${css(space.height, true)} - 1px); }`);
135+
}
136+
137+
if (space.canResizeRight) {
138+
cssElements.push(`#${space.id}-t { right: calc(${css(space.right, true)} + ${css(space.width, true)} - 15px); }`);
139+
cssElements.push(`#${space.id}-m { right: calc(${css(space.right, true)} + ${css(space.width, true)} - 1px); }`);
140+
}
141+
142+
if (space.canResizeBottom) {
143+
cssElements.push(`#${space.id}-t { bottom: calc(${css(space.bottom, true)} + ${css(space.height, true)} - 15px); }`);
144+
cssElements.push(`#${space.id}-m { bottom: calc(${css(space.bottom, true)} + ${css(space.height, true)} - 1px); }`);
145+
}
146+
147+
return cssElements.join(" ");
118148
}
119149

120150
export function updateStyleDefinition(space: ISpaceDefinition) {

src/core.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,11 @@ export function createStore(): ISpaceStore {
177177
},
178178
updateSpace: (space, props) => {
179179
const { type, anchor, order, zIndex, scrollable, position, centerContent, minimumSize, maximumSize } = props;
180+
const canResizeLeft = (position && position.rightResizable) || false;
181+
const canResizeRight = (position && position.leftResizable) || false;
182+
const canResizeTop = (position && position.bottomResizable) || false;
183+
const canResizeBottom = (position && position.topResizable) || false;
184+
180185
let changed = false;
181186

182187
if (space.type !== type) {
@@ -269,6 +274,26 @@ export function createStore(): ISpaceStore {
269274
changed = true;
270275
}
271276

277+
if (space.canResizeBottom !== canResizeBottom) {
278+
space.canResizeBottom = canResizeBottom;
279+
changed = true;
280+
}
281+
282+
if (space.canResizeTop !== canResizeTop) {
283+
space.canResizeTop = canResizeTop;
284+
changed = true;
285+
}
286+
287+
if (space.canResizeLeft !== canResizeLeft) {
288+
space.canResizeLeft = canResizeLeft;
289+
changed = true;
290+
}
291+
292+
if (space.canResizeRight !== canResizeRight) {
293+
space.canResizeRight = canResizeRight;
294+
changed = true;
295+
}
296+
272297
if (changed) {
273298
if (space.parentId) {
274299
const parentSpace = getSpace(space.parentId);
@@ -288,6 +313,10 @@ export function createStore(): ISpaceStore {
288313

289314
store.createSpace = (parentId: string | undefined, props: ISpaceProps, update: () => void) => {
290315
const { position, anchor, type, ...commonProps } = props;
316+
const canResizeLeft = (position && position.rightResizable) || false;
317+
const canResizeRight = (position && position.leftResizable) || false;
318+
const canResizeTop = (position && position.bottomResizable) || false;
319+
const canResizeBottom = (position && position.topResizable) || false;
291320

292321
const newSpace: ISpaceDefinition = {
293322
...spaceDefaults,
@@ -315,6 +344,10 @@ export function createStore(): ISpaceStore {
315344
bottom: sizeInfoDefault(position && position.bottom),
316345
width: sizeInfoDefault(position && position.width),
317346
height: sizeInfoDefault(position && position.height),
347+
canResizeLeft: canResizeLeft,
348+
canResizeRight: canResizeRight,
349+
canResizeTop: canResizeTop,
350+
canResizeBottom: canResizeBottom,
318351
},
319352
} as ISpaceDefinition;
320353

src/styles.css

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -33,34 +33,55 @@
3333
touch-action: none;
3434
}
3535

36+
.spaces-touch-handle {
37+
position: absolute;
38+
z-index: 9998;
39+
}
40+
3641
.spaces-resize-handle.resize-left {
3742
top: 0;
3843
bottom: 0;
39-
left: 0;
4044
cursor: w-resize;
4145
}
4246

47+
.spaces-touch-handle.resize-left {
48+
top: 0;
49+
bottom: 0;
50+
}
51+
4352
.spaces-resize-handle.resize-right {
4453
top: 0;
4554
bottom: 0;
46-
right: 0;
4755
cursor: e-resize;
4856
}
4957

50-
.spaces-resize-handle.resize-top {
58+
.spaces-touch-handle.resize-right {
5159
top: 0;
60+
bottom: 0;
61+
}
62+
63+
.spaces-resize-handle.resize-top {
5264
left: 0;
5365
right: 0;
5466
cursor: n-resize;
5567
}
5668

69+
.spaces-touch-handle.resize-top {
70+
left: 0;
71+
right: 0;
72+
}
73+
5774
.spaces-resize-handle.resize-bottom {
58-
bottom: 0;
5975
left: 0;
6076
right: 0;
6177
cursor: s-resize;
6278
}
6379

80+
.spaces-touch-handle.resize-bottom {
81+
left: 0;
82+
right: 0;
83+
}
84+
6485
.spaces-space {
6586
position: absolute;
6687
overflow: hidden;

0 commit comments

Comments
 (0)