diff --git a/packages/core/src/commands/view/ComponentDrag.ts b/packages/core/src/commands/view/ComponentDrag.ts index 970515c83..b87e2ad4a 100644 --- a/packages/core/src/commands/view/ComponentDrag.ts +++ b/packages/core/src/commands/view/ComponentDrag.ts @@ -10,7 +10,7 @@ import type ComponentView from '../../dom_components/view/ComponentView'; const evName = 'dmode'; export default { - run(editor, _sender, opts = {}) { + run(editor, _sender, opts = {} as ComponentDragOpts) { bindAll( this, 'setPosition', @@ -23,8 +23,10 @@ export default { 'getGuidesTarget', ); + if (!opts.target) throw new Error('Target option is required'); + const config = { - doc: opts.target?.getEl()?.ownerDocument, + doc: opts.target.getEl()?.ownerDocument, onStart: this.onStart, onEnd: this.onEnd, onDrag: this.onDrag, @@ -54,8 +56,6 @@ export default { } opts.event && drg.start(opts.event); - // TODO: check this - opts.addStyle?.({ component: this.target, styles: {}, partial: false }); this.toggleDrag(true); this.em.trigger(`${evName}:start`, this.getEventOpts()); @@ -63,11 +63,14 @@ export default { }, getEventOpts() { + const activeGuides = this.guidesTarget?.filter((item) => item.active) ?? []; return { - mode: this.opts?.mode, - target: this.target, - guidesTarget: this.guidesTarget, - guidesStatic: this.guidesStatic, + mode: this.opts.mode, + component: this.target, + target: this.target, // deprecated + guidesTarget: this.guidesTarget, // deprecated + guidesStatic: this.guidesStatic, // deprecated + guidesMatched: this.getMatchedGuides(activeGuides).map((item) => item.matched), }; }, @@ -88,7 +91,7 @@ export default { if (!guidesEl) { const { editor, em, opts } = this; - const pfx = editor!.getConfig().stylePrefix ?? ''; + const pfx = editor.getConfig().stylePrefix ?? ''; const elInfoX = document.createElement('div'); const elInfoY = document.createElement('div'); const guideContent = `
@@ -102,7 +105,7 @@ export default { elInfoY.innerHTML = guideContent; guidesEl.appendChild(elInfoX); guidesEl.appendChild(elInfoY); - editor!.Canvas.getGlobalToolsEl()?.appendChild(guidesEl); + editor.Canvas.getGlobalToolsEl()?.appendChild(guidesEl); this.guidesEl = guidesEl; this.elGuideInfoX = elInfoX; this.elGuideInfoY = elInfoY; @@ -113,7 +116,7 @@ export default { 'canvas:update frame:scroll', debounce(() => { this.updateGuides(); - opts?.debug && this.guides?.forEach((item) => this.renderGuide(item)); + opts.debug && this.guides?.forEach((item) => this.renderGuide(item)); }, 200), ); } @@ -123,7 +126,7 @@ export default { getGuidesStatic() { let result: Guide[] = []; - const el = this.target?.getEl(); + const el = this.target.getEl(); const parentNode = el?.parentElement; if (!parentNode) return []; each( @@ -135,7 +138,7 @@ export default { }, getGuidesTarget() { - const targetEl = this.target?.getEl(); + const targetEl = this.target.getEl(); if (!targetEl) return []; return this.getElementGuides(targetEl); }, @@ -184,7 +187,7 @@ export default { }, renderGuide(item) { - if (this.opts?.skipGuidesRender) return; + if (this.opts.skipGuidesRender) return; const el = item.guide ?? document.createElement('div'); const un = 'px'; const guideSize = item.active ? 2 : 1; @@ -214,7 +217,7 @@ export default { }, getElementPos(el) { - return this.editor!.Canvas.getElementPos(el, { noScroll: 1 }); + return this.editor.Canvas.getElementPos(el, { noScroll: 1 }); }, getElementGuides(el) { @@ -235,7 +238,7 @@ export default { ]; const guides = guidePoints.map((guidePoint) => { - const guide = opts?.debug ? this.renderGuide(guidePoint) : undefined; + const guide = opts.debug ? this.renderGuide(guidePoint) : undefined; // INFO: origin, originRect, and guide are repeated to don't introduce breaking changes return { ...guidePoint, @@ -283,11 +286,11 @@ export default { getPosition() { const { target, isTran } = this; - const targetStyle = target?.getStyle(); + const targetStyle = target.getStyle(); - const transform = targetStyle?.transform as string | undefined; - const left = targetStyle?.left as string | undefined; - const top = targetStyle?.top as string | undefined; + const transform = targetStyle.transform as string; + const left = targetStyle.left as string; + const top = targetStyle.top as string; let x = 0; let y = 0; @@ -304,7 +307,7 @@ export default { }, setPosition({ x, y, end, position, width, height }) { - const { target, isTran, em } = this; + const { target, isTran, em, opts } = this; const unit = 'px'; const __p = !end; // Indicate if partial change const left = `${parseInt(`${x}`, 10)}${unit}`; @@ -312,7 +315,7 @@ export default { let styleUp = {}; if (isTran) { - let transform = (target?.getStyle()?.transform ?? '') as string; + let transform = (target.getStyle()?.transform ?? '') as string; transform = this.setTranslate(transform, 'x', left); transform = this.setTranslate(transform, 'y', top); styleUp = { transform, __p }; @@ -326,33 +329,38 @@ export default { styleUp = style; } - target?.addStyle(styleUp, { avoidStore: !end }); - em?.Styles.__emitCmpStyleUpdate(styleUp, { components: em.getSelected() }); + if (opts.addStyle) { + opts.addStyle({ component: target, styles: styleUp, partial: !end }); + } else { + target.addStyle(styleUp, { avoidStore: !end }); + } + + em.Styles.__emitCmpStyleUpdate(styleUp, { components: em.getSelected() }); }, _getDragData() { const { target } = this; return { target, - parent: target?.parent(), - index: target?.index(), + parent: target.parent(), + index: target.index(), }; }, onStart(event) { const { target, editor, isTran, opts } = this; - const { Canvas } = editor!; - const style = target?.getStyle(); + const { Canvas } = editor; + const style = target.getStyle(); const position = 'absolute'; const relPos = [position, 'relative']; - opts?.onStart?.(this._getDragData()); + opts.onStart?.(this._getDragData()); if (isTran) return; if (style?.position !== position) { - const targetEl = target?.getEl(); + const targetEl = target.getEl(); const offset = targetEl ? Canvas.offset(targetEl) : { left: 0, top: 0, width: 0, height: 0 }; let { left, top, width, height } = offset; - let parent = target?.parent(); + let parent = target.parent(); let parentRel; // Check for the relative parent @@ -366,7 +374,7 @@ export default { } while (parent && !parentRel); // Center the target to the pointer position (used in Droppable for Blocks) - if (opts?.center) { + if (opts.center) { const { x, y } = Canvas.getMouseRelativeCanvas(event as MouseEvent); left = x; top = y; @@ -391,15 +399,17 @@ export default { const { guidesTarget, opts } = this; this.updateGuides(guidesTarget); - opts?.debug && guidesTarget?.forEach((item) => this.renderGuide(item)); - opts?.guidesInfo && this.renderGuideInfo(guidesTarget?.filter((item) => item.active) ?? []); - opts?.onDrag?.(this._getDragData()); + opts.debug && guidesTarget?.forEach((item) => this.renderGuide(item)); + opts.guidesInfo && this.renderGuideInfo(guidesTarget?.filter((item) => item.active) ?? []); + opts.onDrag?.(this._getDragData()); + + this.em.trigger(`${evName}:move`, this.getEventOpts()); }, onEnd(ev, _dragger, opt) { const { editor, opts, id } = this; - opts?.onEnd?.(ev, opt, { event: ev, ...opt, ...this._getDragData() }); - editor!.stopCommand(`${id}`); + opts.onEnd?.(ev, opt, { event: ev, ...opt, ...this._getDragData() }); + editor.stopCommand(`${id}`); this.hideGuidesInfo(); this.em.trigger(`${evName}:end`, this.getEventOpts()); @@ -418,7 +428,7 @@ export default { this.hideGuidesInfo(); matchedGuides.forEach((matchedGuide) => { // TODO: improve this - if (!this.opts?.skipGuidesRender) { + if (!this.opts.skipGuidesRender) { this.renderSingleGuideInfo(matchedGuide); } @@ -513,15 +523,21 @@ export default { const { ppfx, editor } = this; const methodCls = enable ? 'add' : 'remove'; const classes = [`${ppfx}is__grabbing`]; - const { Canvas } = editor!; + const { Canvas } = editor; const body = Canvas.getBody(); classes.forEach((cls) => body.classList[methodCls](cls)); Canvas[enable ? 'startAutoscroll' : 'stopAutoscroll'](); }, + + // These properties values are set in the run method, they need to be initialized here to avoid TS errors + editor: undefined as unknown as Editor, + em: undefined as unknown as EditorModel, + opts: undefined as unknown as ComponentDragOpts, + target: undefined as unknown as Component, } as CommandObject; interface ComponentDragProps { - editor?: Editor; + editor: Editor; em?: EditorModel; guides?: Guide[]; guidesContainer?: HTMLElement; @@ -529,15 +545,15 @@ interface ComponentDragProps { guidesStatic?: Guide[]; guidesTarget?: Guide[]; isTran?: boolean; - opts?: ComponentDragOpts; - target?: Component; + opts: ComponentDragOpts; + target: Component; elGuideInfoX?: HTMLElement; elGuideInfoY?: HTMLElement; elGuideInfoContentX?: HTMLElement; elGuideInfoContentY?: HTMLElement; dragger?: Dragger; - getEventOpts: () => { mode: string; target: Component; guidesTarget: Guide[]; guidesStatic: Guide[] }; + getEventOpts: () => ComponentDragEventProps; stop: () => void; setupGuides: () => void; getGuidesContainer: () => HTMLElement; @@ -552,7 +568,7 @@ interface ComponentDragProps { setTranslate: (transform: string, axis: string, value: string) => string; getPosition: DraggerOptions['getPosition']; setPosition: (data: any) => void; // TODO: fix any - _getDragData: () => { target?: Component; parent?: Component; index?: number }; + _getDragData: () => { target: Component; parent?: Component; index?: number }; onStart: DraggerOptions['onStart']; onDrag: DraggerOptions['onDrag']; onEnd: DraggerOptions['onEnd']; @@ -564,33 +580,108 @@ interface ComponentDragProps { } type ComponentDragOpts = { + target: Component; center?: number; debug?: boolean; dragger?: DraggerOptions; event?: Event; guidesInfo?: number; mode?: 'absolute' | 'translate'; - target?: Component; skipGuidesRender?: boolean; addStyle?: (data: { component?: Component; styles?: Record; partial?: boolean }) => void; - onDrag?: (data: any) => Editor; // TODO: fix any - onEnd?: (ev: Event, opt: any, data: any) => void; // TODO: fix any - onStart?: (data: any) => Editor; // TODO: fix any + onStart?: (data: any) => Editor; + onDrag?: (data: any) => Editor; + onEnd?: (ev: Event, opt: any, data: any) => void; +}; +/** + * Represents the properties of the drag events (eg., dmode:start, dmode:active, dmode:end). + */ +type ComponentDragEventProps = { + /** + * The mode of the drag (absolute or translate). + */ + mode: ComponentDragOpts['mode']; + /** + * The component being dragged. + * @deprecated Use `component` instead. + */ + target: Component; + /** + * The component being dragged. + */ + component: Component; + /** + * The guides of the component being dragged. + * @deprecated Use `guidesMatched` instead. + */ + guidesTarget: Guide[]; + /** + * All the guides except the ones of the component being dragged. + * @deprecated Use `guidesMatched` instead. + */ + guidesStatic: Guide[]; + /** + * The guides that are being matched. + */ + guidesMatched: Guide[]; }; +/** + * Represents a guide used during component dragging. + */ type Guide = { + /** + * The type of the guide (e.g., 't', 'b', 'l', 'r', 'x', 'y'). + */ type: string; + /** + * The vertical position of the guide. + */ y: number; + /** + * The horizontal position of the guide. + */ x: number; + /** + * The component associated with the guide. + */ component: Component; + /** + * The view of the component associated with the guide. + */ componentView: ComponentView; + /** + * The HTML element associated with the guide. + * @deprecated Use `componentEl` instead. + */ + origin: HTMLElement; + /** + * The HTML element associated with the guide. + */ componentEl: HTMLElement; - origin: HTMLElement; // @deprecated: use componentEl instead + /** + * The rectangle (position and dimensions) of the guide's element. + * @deprecated Use `componentElRect` instead. + */ + originRect: ComponentOrigRect; + /** + * The rectangle (position and dimensions) of the guide's element. + */ componentElRect: ComponentOrigRect; - originRect: ComponentOrigRect; // @deprecated: use componentElRect instead + /** + * The HTML element representing the guide. + * @deprecated Use `guideEl` instead. + */ + guide?: HTMLElement; + /** + * The HTML element representing the guide. + */ guideEl?: HTMLElement; - guide?: HTMLElement; // @deprecated: use guideEl instead - active?: boolean; // TODO: is this used? + /** + * Indicates whether the guide is active. + * @todo Check if this property is used. + */ + active?: boolean; }; type MatchedGuide = { @@ -610,13 +701,3 @@ type ComponentRect = { left: number; width: number; top: number; height: number type ComponentOrigRect = ComponentRect & { rect: ComponentRect }; type ElGuideInfoKey = 'elGuideInfoX' | 'elGuideInfoY'; type ElGuideInfoContentKey = 'elGuideInfoContentX' | 'elGuideInfoContentY'; - -// TODO: should we export this type? and if so, we should create 1 type for every event? -export type DragEventProps = { - originComponent?: Component; - originComponentView?: ComponentView; - originGuides?: MatchedGuide[]; - matchedComponent?: Component; - matchedComponentView?: ComponentView; - matchedGuides?: MatchedGuide[]; -};