Skip to content

Commit ac76e19

Browse files
authored
Enhance component resize (#6566)
* Move resize options from SelectComponent to Resize command * Add updateStyle * Add convertPxToUnit * Add convertPxToUnit to options * Use pointer capture * Cleanup * TS fixes
1 parent a0022da commit ac76e19

File tree

6 files changed

+470
-179
lines changed

6 files changed

+470
-179
lines changed
Lines changed: 354 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,373 @@
1-
import Resizer, { ResizerOptions } from '../../utils/Resizer';
1+
import { LiteralUnion, Position } from '../../common';
2+
import Component from '../../dom_components/model/Component';
3+
import { ComponentsEvents } from '../../dom_components/types';
4+
import ComponentView from '../../dom_components/view/ComponentView';
5+
import StyleableModel, { StyleProps } from '../../domain_abstract/model/StyleableModel';
6+
import { getUnitFromValue } from '../../utils/mixins';
7+
import Resizer, { RectDim, ResizerOptions } from '../../utils/Resizer';
28
import { CommandObject } from './CommandAbstract';
39

10+
export interface ComponentResizeOptions extends ResizerOptions {
11+
component: Component;
12+
componentView?: ComponentView;
13+
el?: HTMLElement;
14+
afterStart?: () => void;
15+
afterEnd?: () => void;
16+
/**
17+
* When the element is using an absolute position, the resizer, by default, will try to
18+
* update position values (eg. 'top'/'left')
19+
*/
20+
skipPositionUpdate?: boolean;
21+
/**
22+
* @deprecated
23+
*/
24+
options?: ResizerOptions;
25+
}
26+
27+
export interface ComponentResizeModelProperty {
28+
value: string;
29+
property: string;
30+
number: number;
31+
unit: string;
32+
}
33+
34+
export interface ComponentResizeEventProps {
35+
component: Component;
36+
event: PointerEvent;
37+
el: HTMLElement;
38+
rect: RectDim;
39+
}
40+
41+
export interface ComponentResizeEventStartProps extends ComponentResizeEventProps {
42+
model: StyleableModel;
43+
modelWidth: ComponentResizeModelProperty;
44+
modelHeight: ComponentResizeModelProperty;
45+
}
46+
47+
export interface ComponentResizeEventMoveProps extends ComponentResizeEventProps {
48+
delta: Position;
49+
pointer: Position;
50+
}
51+
52+
export interface ComponentResizeEventEndProps extends ComponentResizeEventProps {
53+
moved: boolean;
54+
}
55+
56+
export interface ComponentResizeEventUpdateProps extends ComponentResizeEventProps {
57+
partial: boolean;
58+
delta: Position;
59+
pointer: Position;
60+
style: StyleProps;
61+
updateStyle: (styles?: StyleProps) => void;
62+
convertPxToUnit: (props: ConvertPxToUnitProps) => string;
63+
}
64+
65+
export interface ConvertPxToUnitProps {
66+
el: HTMLElement;
67+
valuePx: number;
68+
unit?: LiteralUnion<ConvertUnitsToPx, string>;
69+
/**
70+
* @default 3
71+
*/
72+
roundDecimals?: number;
73+
/**
74+
* DPI (Dots Per Inch) value to use for conversion.
75+
* @default 96
76+
*/
77+
dpi?: number;
78+
}
79+
80+
export enum ConvertUnitsToPx {
81+
pt = 'pt',
82+
pc = 'pc',
83+
in = 'in',
84+
cm = 'cm',
85+
mm = 'mm',
86+
vw = 'vw',
87+
vh = 'vh',
88+
vmin = 'vmin',
89+
vmax = 'vmax',
90+
svw = 'svw',
91+
lvw = 'lvw',
92+
dvw = 'dvw',
93+
svh = 'svh',
94+
lvh = 'lvh',
95+
dvh = 'dvh',
96+
perc = '%',
97+
}
98+
499
export default {
5-
run(editor, sender, opts) {
6-
const opt = opts || {};
7-
const canvas = editor.Canvas;
8-
const canvasView = canvas.getCanvasView();
9-
const options: ResizerOptions = {
10-
appendTo: canvas.getResizerEl(),
100+
run(editor, _, options: ComponentResizeOptions) {
101+
const { Canvas, Utils, em } = editor;
102+
const canvasView = Canvas.getCanvasView();
103+
const pfx = em.config.stylePrefix || '';
104+
const resizeClass = `${pfx}resizing`;
105+
const {
106+
onStart = () => {},
107+
onMove = () => {},
108+
onEnd = () => {},
109+
updateTarget = () => {},
110+
el: elOpts,
111+
componentView,
112+
component,
113+
skipPositionUpdate,
114+
...resizableOpts
115+
} = options;
116+
const el = elOpts || componentView?.el || component.getEl()!;
117+
const resizeEventOpts = { component, el };
118+
let modelToStyle: StyleableModel;
119+
120+
const toggleBodyClass = (method: string, e: any, opts: any) => {
121+
const docs = opts.docs;
122+
docs &&
123+
docs.forEach((doc: Document) => {
124+
const body = doc.body;
125+
const cls = body.className || '';
126+
body.className = (method == 'add' ? `${cls} ${resizeClass}` : cls.replace(resizeClass, '')).trim();
127+
});
128+
};
129+
130+
const resizeOptions: ResizerOptions = {
131+
appendTo: Canvas.getResizerEl(),
11132
prefix: editor.getConfig().stylePrefix,
12133
posFetcher: canvasView.getElementPos.bind(canvasView),
13-
mousePosFetcher: canvas.getMouseRelativePos.bind(canvas),
14-
...(opt.options || {}),
134+
mousePosFetcher: Canvas.getMouseRelativePos.bind(Canvas),
135+
docs: [document],
136+
onStart(ev, opts) {
137+
onStart(ev, opts);
138+
const { el, config, resizer } = opts;
139+
const { keyHeight, keyWidth, currentUnit, keepAutoHeight, keepAutoWidth } = config;
140+
toggleBodyClass('add', ev, opts);
141+
modelToStyle = em.Styles.getModelToStyle(component);
142+
const computedStyle = getComputedStyle(el);
143+
const modelStyle = modelToStyle.getStyle();
144+
const rectStart = { ...resizer.startDim! };
145+
146+
let currentWidth = modelStyle[keyWidth!] as string;
147+
config.autoWidth = keepAutoWidth && currentWidth === 'auto';
148+
if (isNaN(parseFloat(currentWidth))) {
149+
currentWidth = computedStyle[keyWidth as any];
150+
}
151+
152+
let currentHeight = modelStyle[keyHeight!] as string;
153+
config.autoHeight = keepAutoHeight && currentHeight === 'auto';
154+
if (isNaN(parseFloat(currentHeight))) {
155+
currentHeight = computedStyle[keyHeight as any];
156+
}
157+
158+
const valueWidth = parseFloat(currentWidth);
159+
const valueHeight = parseFloat(currentHeight);
160+
const unitWidth = getUnitFromValue(currentWidth);
161+
const unitHeight = getUnitFromValue(currentHeight);
162+
163+
if (currentUnit) {
164+
config.unitWidth = unitWidth;
165+
config.unitHeight = unitHeight;
166+
}
167+
168+
const eventProps: ComponentResizeEventStartProps = {
169+
...resizeEventOpts,
170+
event: ev,
171+
rect: rectStart,
172+
model: modelToStyle,
173+
modelWidth: {
174+
value: currentWidth,
175+
property: keyWidth!,
176+
number: valueWidth,
177+
unit: unitWidth,
178+
},
179+
modelHeight: {
180+
value: currentHeight,
181+
property: keyHeight!,
182+
number: valueHeight,
183+
unit: unitHeight,
184+
},
185+
};
186+
editor.trigger(ComponentsEvents.resizeStart, eventProps);
187+
editor.trigger(ComponentsEvents.resize, { ...eventProps, type: 'start' });
188+
options.afterStart?.();
189+
},
190+
191+
// Update all positioned elements (eg. component toolbar)
192+
onMove(event, opts) {
193+
onMove(event, opts);
194+
const { resizer } = opts;
195+
const eventProps: ComponentResizeEventMoveProps = {
196+
...resizeEventOpts,
197+
event,
198+
delta: resizer.delta!,
199+
pointer: resizer.currentPos!,
200+
rect: resizer.rectDim!,
201+
};
202+
editor.trigger(ComponentsEvents.resizeStart, eventProps);
203+
editor.trigger(ComponentsEvents.resize, { ...eventProps, type: 'move' });
204+
},
205+
206+
onEnd(event, opts) {
207+
onEnd(event, opts);
208+
toggleBodyClass('remove', event, opts);
209+
const { resizer } = opts;
210+
const eventProps: ComponentResizeEventEndProps = {
211+
...resizeEventOpts,
212+
event,
213+
rect: resizer.rectDim!,
214+
moved: resizer.moved,
215+
};
216+
editor.trigger(ComponentsEvents.resizeEnd, eventProps);
217+
editor.trigger(ComponentsEvents.resize, { ...resizeEventOpts, type: 'end' });
218+
options.afterEnd?.();
219+
},
220+
221+
updateTarget: (_el, rect, options) => {
222+
updateTarget(_el, rect, options);
223+
if (!modelToStyle) {
224+
return;
225+
}
226+
227+
const { store, selectedHandler, config, resizer, event } = options;
228+
const { keyHeight, keyWidth, autoHeight, autoWidth, unitWidth, unitHeight } = config;
229+
const onlyHeight = ['tc', 'bc'].indexOf(selectedHandler!) >= 0;
230+
const onlyWidth = ['cl', 'cr'].indexOf(selectedHandler!) >= 0;
231+
const partial = !store;
232+
const style: StyleProps = {};
233+
234+
if (!onlyHeight) {
235+
const bodyw = Canvas.getBody()?.offsetWidth || 0;
236+
const width = rect.w < bodyw ? rect.w : bodyw;
237+
style[keyWidth!] = autoWidth
238+
? 'auto'
239+
: this.convertPxToUnit({
240+
el,
241+
valuePx: width,
242+
unit: unitWidth,
243+
});
244+
}
245+
246+
if (!onlyWidth) {
247+
style[keyHeight!] = autoHeight
248+
? 'auto'
249+
: this.convertPxToUnit({
250+
el,
251+
valuePx: rect.h,
252+
unit: unitHeight,
253+
});
254+
}
255+
256+
if (!skipPositionUpdate && em.getDragMode(component)) {
257+
style.top = `${rect.t}px`;
258+
style.left = `${rect.l}px`;
259+
}
260+
261+
let styleUpdated = false;
262+
263+
const updateStyle = (customStyle?: StyleProps) => {
264+
styleUpdated = true;
265+
const finalStyle = { ...(customStyle || style), __p: partial };
266+
modelToStyle.addStyle(finalStyle, { avoidStore: partial });
267+
em.Styles.__emitCmpStyleUpdate(finalStyle as any, { components: component });
268+
};
269+
270+
const eventProps: ComponentResizeEventUpdateProps = {
271+
...resizeEventOpts,
272+
rect,
273+
partial,
274+
event,
275+
style,
276+
updateStyle,
277+
convertPxToUnit: (props: Omit<ConvertPxToUnitProps, 'el'>) => this.convertPxToUnit({ el, ...props }),
278+
delta: resizer.delta!,
279+
pointer: resizer.currentPos!,
280+
};
281+
editor.trigger(ComponentsEvents.resizeUpdate, eventProps);
282+
!styleUpdated && updateStyle();
283+
},
284+
...resizableOpts,
285+
...options.options,
15286
};
287+
16288
let { canvasResizer } = this;
17289

18290
// Create the resizer for the canvas if not yet created
19-
if (!canvasResizer || opt.forceNew) {
20-
this.canvasResizer = new editor.Utils.Resizer(options);
291+
if (!canvasResizer) {
292+
this.canvasResizer = new Utils.Resizer(resizeOptions);
21293
canvasResizer = this.canvasResizer;
22294
}
23295

24-
canvasResizer.setOptions(options, true);
296+
canvasResizer.setOptions(resizeOptions, true);
25297
canvasResizer.blur();
26-
canvasResizer.focus(opt.el);
298+
canvasResizer.focus(el);
27299
return canvasResizer;
28300
},
29301

30302
stop() {
31303
this.canvasResizer?.blur();
32304
},
33-
} as CommandObject<{ options?: {}; forceNew?: boolean; el: HTMLElement }, { canvasResizer?: Resizer }>;
305+
306+
convertPxToUnit(props: ConvertPxToUnitProps): string {
307+
const { el, valuePx, unit, dpi = 96, roundDecimals = 3 } = props;
308+
const win = el.ownerDocument.defaultView;
309+
const winWidth = win?.innerWidth || 1;
310+
const winHeight = window.innerHeight || 1;
311+
let valueResult = valuePx;
312+
let untiResult = unit;
313+
314+
switch (unit) {
315+
case ConvertUnitsToPx.pt:
316+
valueResult = valuePx * (72 / dpi);
317+
break;
318+
case ConvertUnitsToPx.pc:
319+
valueResult = valuePx * (6 / dpi);
320+
break;
321+
case ConvertUnitsToPx.in:
322+
valueResult = valuePx / dpi;
323+
break;
324+
case ConvertUnitsToPx.cm:
325+
valueResult = valuePx / (dpi / 2.54);
326+
break;
327+
case ConvertUnitsToPx.mm:
328+
valueResult = valuePx / (dpi / 25.4);
329+
break;
330+
case ConvertUnitsToPx.vw:
331+
valueResult = (valuePx / winWidth) * 100;
332+
break;
333+
case ConvertUnitsToPx.vh:
334+
valueResult = (valuePx / winHeight) * 100;
335+
break;
336+
case ConvertUnitsToPx.vmin: {
337+
const vmin = Math.min(winWidth, winHeight);
338+
valueResult = (valuePx / vmin) * 100;
339+
break;
340+
}
341+
case ConvertUnitsToPx.vmax: {
342+
const vmax = Math.max(winWidth, winHeight);
343+
valueResult = (valuePx / vmax) * 100;
344+
break;
345+
}
346+
case ConvertUnitsToPx.perc: {
347+
const parentSize = el.parentElement?.offsetWidth || 1;
348+
valueResult = (valuePx / parentSize) * 100;
349+
break;
350+
}
351+
case ConvertUnitsToPx.svw:
352+
case ConvertUnitsToPx.lvw:
353+
case ConvertUnitsToPx.dvw:
354+
valueResult = (valuePx / winWidth) * 100;
355+
break;
356+
case ConvertUnitsToPx.svh:
357+
case ConvertUnitsToPx.lvh:
358+
case ConvertUnitsToPx.dvh:
359+
valueResult = (valuePx / winHeight) * 100;
360+
break;
361+
default:
362+
untiResult = 'px';
363+
}
364+
365+
return `${+valueResult.toFixed(roundDecimals)}${untiResult}`;
366+
},
367+
} as CommandObject<
368+
ComponentResizeOptions,
369+
{
370+
canvasResizer?: Resizer;
371+
convertPxToUnit: (props: ConvertPxToUnitProps) => string;
372+
}
373+
>;

0 commit comments

Comments
 (0)