mirror of https://github.com/artf/grapesjs.git
Browse Source
* Move resize options from SelectComponent to Resize command * Add updateStyle * Add convertPxToUnit * Add convertPxToUnit to options * Use pointer capture * Cleanup * TS fixesrelease-v0.22.10
committed by
GitHub
6 changed files with 470 additions and 179 deletions
@ -1,33 +1,373 @@ |
|||||
import Resizer, { ResizerOptions } from '../../utils/Resizer'; |
import { LiteralUnion, Position } from '../../common'; |
||||
|
import Component from '../../dom_components/model/Component'; |
||||
|
import { ComponentsEvents } from '../../dom_components/types'; |
||||
|
import ComponentView from '../../dom_components/view/ComponentView'; |
||||
|
import StyleableModel, { StyleProps } from '../../domain_abstract/model/StyleableModel'; |
||||
|
import { getUnitFromValue } from '../../utils/mixins'; |
||||
|
import Resizer, { RectDim, ResizerOptions } from '../../utils/Resizer'; |
||||
import { CommandObject } from './CommandAbstract'; |
import { CommandObject } from './CommandAbstract'; |
||||
|
|
||||
|
export interface ComponentResizeOptions extends ResizerOptions { |
||||
|
component: Component; |
||||
|
componentView?: ComponentView; |
||||
|
el?: HTMLElement; |
||||
|
afterStart?: () => void; |
||||
|
afterEnd?: () => void; |
||||
|
/** |
||||
|
* When the element is using an absolute position, the resizer, by default, will try to |
||||
|
* update position values (eg. 'top'/'left') |
||||
|
*/ |
||||
|
skipPositionUpdate?: boolean; |
||||
|
/** |
||||
|
* @deprecated |
||||
|
*/ |
||||
|
options?: ResizerOptions; |
||||
|
} |
||||
|
|
||||
|
export interface ComponentResizeModelProperty { |
||||
|
value: string; |
||||
|
property: string; |
||||
|
number: number; |
||||
|
unit: string; |
||||
|
} |
||||
|
|
||||
|
export interface ComponentResizeEventProps { |
||||
|
component: Component; |
||||
|
event: PointerEvent; |
||||
|
el: HTMLElement; |
||||
|
rect: RectDim; |
||||
|
} |
||||
|
|
||||
|
export interface ComponentResizeEventStartProps extends ComponentResizeEventProps { |
||||
|
model: StyleableModel; |
||||
|
modelWidth: ComponentResizeModelProperty; |
||||
|
modelHeight: ComponentResizeModelProperty; |
||||
|
} |
||||
|
|
||||
|
export interface ComponentResizeEventMoveProps extends ComponentResizeEventProps { |
||||
|
delta: Position; |
||||
|
pointer: Position; |
||||
|
} |
||||
|
|
||||
|
export interface ComponentResizeEventEndProps extends ComponentResizeEventProps { |
||||
|
moved: boolean; |
||||
|
} |
||||
|
|
||||
|
export interface ComponentResizeEventUpdateProps extends ComponentResizeEventProps { |
||||
|
partial: boolean; |
||||
|
delta: Position; |
||||
|
pointer: Position; |
||||
|
style: StyleProps; |
||||
|
updateStyle: (styles?: StyleProps) => void; |
||||
|
convertPxToUnit: (props: ConvertPxToUnitProps) => string; |
||||
|
} |
||||
|
|
||||
|
export interface ConvertPxToUnitProps { |
||||
|
el: HTMLElement; |
||||
|
valuePx: number; |
||||
|
unit?: LiteralUnion<ConvertUnitsToPx, string>; |
||||
|
/** |
||||
|
* @default 3 |
||||
|
*/ |
||||
|
roundDecimals?: number; |
||||
|
/** |
||||
|
* DPI (Dots Per Inch) value to use for conversion. |
||||
|
* @default 96 |
||||
|
*/ |
||||
|
dpi?: number; |
||||
|
} |
||||
|
|
||||
|
export enum ConvertUnitsToPx { |
||||
|
pt = 'pt', |
||||
|
pc = 'pc', |
||||
|
in = 'in', |
||||
|
cm = 'cm', |
||||
|
mm = 'mm', |
||||
|
vw = 'vw', |
||||
|
vh = 'vh', |
||||
|
vmin = 'vmin', |
||||
|
vmax = 'vmax', |
||||
|
svw = 'svw', |
||||
|
lvw = 'lvw', |
||||
|
dvw = 'dvw', |
||||
|
svh = 'svh', |
||||
|
lvh = 'lvh', |
||||
|
dvh = 'dvh', |
||||
|
perc = '%', |
||||
|
} |
||||
|
|
||||
export default { |
export default { |
||||
run(editor, sender, opts) { |
run(editor, _, options: ComponentResizeOptions) { |
||||
const opt = opts || {}; |
const { Canvas, Utils, em } = editor; |
||||
const canvas = editor.Canvas; |
const canvasView = Canvas.getCanvasView(); |
||||
const canvasView = canvas.getCanvasView(); |
const pfx = em.config.stylePrefix || ''; |
||||
const options: ResizerOptions = { |
const resizeClass = `${pfx}resizing`; |
||||
appendTo: canvas.getResizerEl(), |
const { |
||||
|
onStart = () => {}, |
||||
|
onMove = () => {}, |
||||
|
onEnd = () => {}, |
||||
|
updateTarget = () => {}, |
||||
|
el: elOpts, |
||||
|
componentView, |
||||
|
component, |
||||
|
skipPositionUpdate, |
||||
|
...resizableOpts |
||||
|
} = options; |
||||
|
const el = elOpts || componentView?.el || component.getEl()!; |
||||
|
const resizeEventOpts = { component, el }; |
||||
|
let modelToStyle: StyleableModel; |
||||
|
|
||||
|
const toggleBodyClass = (method: string, e: any, opts: any) => { |
||||
|
const docs = opts.docs; |
||||
|
docs && |
||||
|
docs.forEach((doc: Document) => { |
||||
|
const body = doc.body; |
||||
|
const cls = body.className || ''; |
||||
|
body.className = (method == 'add' ? `${cls} ${resizeClass}` : cls.replace(resizeClass, '')).trim(); |
||||
|
}); |
||||
|
}; |
||||
|
|
||||
|
const resizeOptions: ResizerOptions = { |
||||
|
appendTo: Canvas.getResizerEl(), |
||||
prefix: editor.getConfig().stylePrefix, |
prefix: editor.getConfig().stylePrefix, |
||||
posFetcher: canvasView.getElementPos.bind(canvasView), |
posFetcher: canvasView.getElementPos.bind(canvasView), |
||||
mousePosFetcher: canvas.getMouseRelativePos.bind(canvas), |
mousePosFetcher: Canvas.getMouseRelativePos.bind(Canvas), |
||||
...(opt.options || {}), |
docs: [document], |
||||
|
onStart(ev, opts) { |
||||
|
onStart(ev, opts); |
||||
|
const { el, config, resizer } = opts; |
||||
|
const { keyHeight, keyWidth, currentUnit, keepAutoHeight, keepAutoWidth } = config; |
||||
|
toggleBodyClass('add', ev, opts); |
||||
|
modelToStyle = em.Styles.getModelToStyle(component); |
||||
|
const computedStyle = getComputedStyle(el); |
||||
|
const modelStyle = modelToStyle.getStyle(); |
||||
|
const rectStart = { ...resizer.startDim! }; |
||||
|
|
||||
|
let currentWidth = modelStyle[keyWidth!] as string; |
||||
|
config.autoWidth = keepAutoWidth && currentWidth === 'auto'; |
||||
|
if (isNaN(parseFloat(currentWidth))) { |
||||
|
currentWidth = computedStyle[keyWidth as any]; |
||||
|
} |
||||
|
|
||||
|
let currentHeight = modelStyle[keyHeight!] as string; |
||||
|
config.autoHeight = keepAutoHeight && currentHeight === 'auto'; |
||||
|
if (isNaN(parseFloat(currentHeight))) { |
||||
|
currentHeight = computedStyle[keyHeight as any]; |
||||
|
} |
||||
|
|
||||
|
const valueWidth = parseFloat(currentWidth); |
||||
|
const valueHeight = parseFloat(currentHeight); |
||||
|
const unitWidth = getUnitFromValue(currentWidth); |
||||
|
const unitHeight = getUnitFromValue(currentHeight); |
||||
|
|
||||
|
if (currentUnit) { |
||||
|
config.unitWidth = unitWidth; |
||||
|
config.unitHeight = unitHeight; |
||||
|
} |
||||
|
|
||||
|
const eventProps: ComponentResizeEventStartProps = { |
||||
|
...resizeEventOpts, |
||||
|
event: ev, |
||||
|
rect: rectStart, |
||||
|
model: modelToStyle, |
||||
|
modelWidth: { |
||||
|
value: currentWidth, |
||||
|
property: keyWidth!, |
||||
|
number: valueWidth, |
||||
|
unit: unitWidth, |
||||
|
}, |
||||
|
modelHeight: { |
||||
|
value: currentHeight, |
||||
|
property: keyHeight!, |
||||
|
number: valueHeight, |
||||
|
unit: unitHeight, |
||||
|
}, |
||||
|
}; |
||||
|
editor.trigger(ComponentsEvents.resizeStart, eventProps); |
||||
|
editor.trigger(ComponentsEvents.resize, { ...eventProps, type: 'start' }); |
||||
|
options.afterStart?.(); |
||||
|
}, |
||||
|
|
||||
|
// Update all positioned elements (eg. component toolbar)
|
||||
|
onMove(event, opts) { |
||||
|
onMove(event, opts); |
||||
|
const { resizer } = opts; |
||||
|
const eventProps: ComponentResizeEventMoveProps = { |
||||
|
...resizeEventOpts, |
||||
|
event, |
||||
|
delta: resizer.delta!, |
||||
|
pointer: resizer.currentPos!, |
||||
|
rect: resizer.rectDim!, |
||||
|
}; |
||||
|
editor.trigger(ComponentsEvents.resizeStart, eventProps); |
||||
|
editor.trigger(ComponentsEvents.resize, { ...eventProps, type: 'move' }); |
||||
|
}, |
||||
|
|
||||
|
onEnd(event, opts) { |
||||
|
onEnd(event, opts); |
||||
|
toggleBodyClass('remove', event, opts); |
||||
|
const { resizer } = opts; |
||||
|
const eventProps: ComponentResizeEventEndProps = { |
||||
|
...resizeEventOpts, |
||||
|
event, |
||||
|
rect: resizer.rectDim!, |
||||
|
moved: resizer.moved, |
||||
|
}; |
||||
|
editor.trigger(ComponentsEvents.resizeEnd, eventProps); |
||||
|
editor.trigger(ComponentsEvents.resize, { ...resizeEventOpts, type: 'end' }); |
||||
|
options.afterEnd?.(); |
||||
|
}, |
||||
|
|
||||
|
updateTarget: (_el, rect, options) => { |
||||
|
updateTarget(_el, rect, options); |
||||
|
if (!modelToStyle) { |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
const { store, selectedHandler, config, resizer, event } = options; |
||||
|
const { keyHeight, keyWidth, autoHeight, autoWidth, unitWidth, unitHeight } = config; |
||||
|
const onlyHeight = ['tc', 'bc'].indexOf(selectedHandler!) >= 0; |
||||
|
const onlyWidth = ['cl', 'cr'].indexOf(selectedHandler!) >= 0; |
||||
|
const partial = !store; |
||||
|
const style: StyleProps = {}; |
||||
|
|
||||
|
if (!onlyHeight) { |
||||
|
const bodyw = Canvas.getBody()?.offsetWidth || 0; |
||||
|
const width = rect.w < bodyw ? rect.w : bodyw; |
||||
|
style[keyWidth!] = autoWidth |
||||
|
? 'auto' |
||||
|
: this.convertPxToUnit({ |
||||
|
el, |
||||
|
valuePx: width, |
||||
|
unit: unitWidth, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if (!onlyWidth) { |
||||
|
style[keyHeight!] = autoHeight |
||||
|
? 'auto' |
||||
|
: this.convertPxToUnit({ |
||||
|
el, |
||||
|
valuePx: rect.h, |
||||
|
unit: unitHeight, |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
if (!skipPositionUpdate && em.getDragMode(component)) { |
||||
|
style.top = `${rect.t}px`; |
||||
|
style.left = `${rect.l}px`; |
||||
|
} |
||||
|
|
||||
|
let styleUpdated = false; |
||||
|
|
||||
|
const updateStyle = (customStyle?: StyleProps) => { |
||||
|
styleUpdated = true; |
||||
|
const finalStyle = { ...(customStyle || style), __p: partial }; |
||||
|
modelToStyle.addStyle(finalStyle, { avoidStore: partial }); |
||||
|
em.Styles.__emitCmpStyleUpdate(finalStyle as any, { components: component }); |
||||
|
}; |
||||
|
|
||||
|
const eventProps: ComponentResizeEventUpdateProps = { |
||||
|
...resizeEventOpts, |
||||
|
rect, |
||||
|
partial, |
||||
|
event, |
||||
|
style, |
||||
|
updateStyle, |
||||
|
convertPxToUnit: (props: Omit<ConvertPxToUnitProps, 'el'>) => this.convertPxToUnit({ el, ...props }), |
||||
|
delta: resizer.delta!, |
||||
|
pointer: resizer.currentPos!, |
||||
|
}; |
||||
|
editor.trigger(ComponentsEvents.resizeUpdate, eventProps); |
||||
|
!styleUpdated && updateStyle(); |
||||
|
}, |
||||
|
...resizableOpts, |
||||
|
...options.options, |
||||
}; |
}; |
||||
|
|
||||
let { canvasResizer } = this; |
let { canvasResizer } = this; |
||||
|
|
||||
// Create the resizer for the canvas if not yet created
|
// Create the resizer for the canvas if not yet created
|
||||
if (!canvasResizer || opt.forceNew) { |
if (!canvasResizer) { |
||||
this.canvasResizer = new editor.Utils.Resizer(options); |
this.canvasResizer = new Utils.Resizer(resizeOptions); |
||||
canvasResizer = this.canvasResizer; |
canvasResizer = this.canvasResizer; |
||||
} |
} |
||||
|
|
||||
canvasResizer.setOptions(options, true); |
canvasResizer.setOptions(resizeOptions, true); |
||||
canvasResizer.blur(); |
canvasResizer.blur(); |
||||
canvasResizer.focus(opt.el); |
canvasResizer.focus(el); |
||||
return canvasResizer; |
return canvasResizer; |
||||
}, |
}, |
||||
|
|
||||
stop() { |
stop() { |
||||
this.canvasResizer?.blur(); |
this.canvasResizer?.blur(); |
||||
}, |
}, |
||||
} as CommandObject<{ options?: {}; forceNew?: boolean; el: HTMLElement }, { canvasResizer?: Resizer }>; |
|
||||
|
convertPxToUnit(props: ConvertPxToUnitProps): string { |
||||
|
const { el, valuePx, unit, dpi = 96, roundDecimals = 3 } = props; |
||||
|
const win = el.ownerDocument.defaultView; |
||||
|
const winWidth = win?.innerWidth || 1; |
||||
|
const winHeight = window.innerHeight || 1; |
||||
|
let valueResult = valuePx; |
||||
|
let untiResult = unit; |
||||
|
|
||||
|
switch (unit) { |
||||
|
case ConvertUnitsToPx.pt: |
||||
|
valueResult = valuePx * (72 / dpi); |
||||
|
break; |
||||
|
case ConvertUnitsToPx.pc: |
||||
|
valueResult = valuePx * (6 / dpi); |
||||
|
break; |
||||
|
case ConvertUnitsToPx.in: |
||||
|
valueResult = valuePx / dpi; |
||||
|
break; |
||||
|
case ConvertUnitsToPx.cm: |
||||
|
valueResult = valuePx / (dpi / 2.54); |
||||
|
break; |
||||
|
case ConvertUnitsToPx.mm: |
||||
|
valueResult = valuePx / (dpi / 25.4); |
||||
|
break; |
||||
|
case ConvertUnitsToPx.vw: |
||||
|
valueResult = (valuePx / winWidth) * 100; |
||||
|
break; |
||||
|
case ConvertUnitsToPx.vh: |
||||
|
valueResult = (valuePx / winHeight) * 100; |
||||
|
break; |
||||
|
case ConvertUnitsToPx.vmin: { |
||||
|
const vmin = Math.min(winWidth, winHeight); |
||||
|
valueResult = (valuePx / vmin) * 100; |
||||
|
break; |
||||
|
} |
||||
|
case ConvertUnitsToPx.vmax: { |
||||
|
const vmax = Math.max(winWidth, winHeight); |
||||
|
valueResult = (valuePx / vmax) * 100; |
||||
|
break; |
||||
|
} |
||||
|
case ConvertUnitsToPx.perc: { |
||||
|
const parentSize = el.parentElement?.offsetWidth || 1; |
||||
|
valueResult = (valuePx / parentSize) * 100; |
||||
|
break; |
||||
|
} |
||||
|
case ConvertUnitsToPx.svw: |
||||
|
case ConvertUnitsToPx.lvw: |
||||
|
case ConvertUnitsToPx.dvw: |
||||
|
valueResult = (valuePx / winWidth) * 100; |
||||
|
break; |
||||
|
case ConvertUnitsToPx.svh: |
||||
|
case ConvertUnitsToPx.lvh: |
||||
|
case ConvertUnitsToPx.dvh: |
||||
|
valueResult = (valuePx / winHeight) * 100; |
||||
|
break; |
||||
|
default: |
||||
|
untiResult = 'px'; |
||||
|
} |
||||
|
|
||||
|
return `${+valueResult.toFixed(roundDecimals)}${untiResult}`; |
||||
|
}, |
||||
|
} as CommandObject< |
||||
|
ComponentResizeOptions, |
||||
|
{ |
||||
|
canvasResizer?: Resizer; |
||||
|
convertPxToUnit: (props: ConvertPxToUnitProps) => string; |
||||
|
} |
||||
|
>; |
||||
|
|||||
Loading…
Reference in new issue