/** @jsxImportSource react */ import { Widget, VDOM } from "../../ui/Widget"; import { ContainerBase, ContainerConfig, StyledContainerBase, StyledContainerConfig } from "../../ui/Container"; import { ddMouseDown, ddDetect, ddMouseUp, initiateDragDrop, isDragHandleEvent } from "./ops"; import { preventFocus } from "../../ui/FocusManager"; import { parseStyle } from "../../util/parseStyle"; import { Instance } from "../../ui/Instance"; import { StringProp, StyleProp, ClassProp, Config } from "../../ui/Prop"; import { RenderingContext } from "../../ui/RenderingContext"; export interface DragSourceConfig extends StyledContainerConfig { /** * Data about the drag source that can be used by drop zones to test if * drag source is acceptable and to perform drop operations. */ data?: any; /** * Set to true to hide the element while being dragged. * Use if drop zones are configured to expand to indicate where drop will occur. */ hideOnDrag?: boolean; /** Set to true to indicate that this drag source can be dragged only by using an inner DragHandle. */ handled?: boolean; /** Base CSS class to be applied to the element. Defaults to 'dragsource'. */ baseClass?: string; onDragStart?: (e: React.MouseEvent | React.TouchEvent, instance: Instance) => any; onDragEnd?: (e: React.MouseEvent | React.TouchEvent, instance: Instance) => void; id?: StringProp; /** Custom contents to be displayed during drag & drop operation. */ clone?: Config; /** CSS styles to be applied to the clone of the element being dragged. */ cloneStyle?: StyleProp; /** CSS styles to be applied to the element being dragged. */ draggedStyle?: StyleProp; /** Additional CSS class to be applied to the clone of the element being dragged. */ cloneClass?: ClassProp; /** Additional CSS class to be applied to the element being dragged. */ draggedClass?: ClassProp; } export interface DragSourceInstance extends Instance { dragHandles: any[]; } export class DragSource extends StyledContainerBase { declare styled: boolean; declare baseClass: string; declare hideOnDrag: boolean; declare handled: boolean; declare data: any; declare clone?: Config; declare cloneStyle: any; declare draggedStyle: any; declare onDragStart?: (e: React.MouseEvent | React.TouchEvent, instance: DragSourceInstance) => any; declare onDragEnd?: (e: React.MouseEvent | React.TouchEvent, instance: DragSourceInstance) => void; constructor(config?: DragSourceConfig) { super(config); } init() { this.cloneStyle = parseStyle(this.cloneStyle); this.draggedStyle = parseStyle(this.draggedStyle); super.init(); } declareData() { super.declareData(...arguments, { id: undefined, data: { structured: true }, cloneStyle: { structured: true }, cloneClass: { structured: true }, draggedClass: { structured: true }, draggedStyle: { structured: true }, }); } explore(context: RenderingContext, instance: DragSourceInstance) { context.push("dragHandles", (instance.dragHandles = [])); super.explore(context, instance); } exploreCleanup(context: RenderingContext, instance: DragSourceInstance) { context.pop("dragHandles"); } render(context: RenderingContext, instance: DragSourceInstance, key: string) { return ( 0}> {this.renderChildren(context, instance)} ); } } DragSource.prototype.baseClass = "dragsource"; DragSource.prototype.hideOnDrag = false; DragSource.prototype.handled = false; Widget.alias("dragsource", DragSource); interface DragSourceComponentProps { instance: DragSourceInstance; children?: any; handled: boolean; } interface DragSourceComponentState { dragged: boolean; } class DragSourceComponent extends VDOM.Component { declare el: HTMLElement | null; constructor(props: DragSourceComponentProps) { super(props); this.state = { dragged: false }; this.beginDragDrop = this.beginDragDrop.bind(this); this.onMouseMove = this.onMouseMove.bind(this); this.onMouseDown = this.onMouseDown.bind(this); } setRef = (el: HTMLElement | null) => { this.el = el; }; render() { let { instance, children, handled } = this.props; let { data, widget } = instance; let { CSS } = widget; if (this.state.dragged && widget.hideOnDrag) return null; let classes = [ data.classNames, CSS.state({ dragged: this.state.dragged, draggable: !handled, }), ]; let style = data.style; if (this.state.dragged) { if (data.draggedClass) classes.push(data.draggedClass); if (data.draggedStyle) style = { ...style, ...data.draggedStyle, }; } let eventHandlers: any = { ...instance.getJsxEventProps(), onTouchStart: this.onMouseDown, onMouseDown: this.onMouseDown, onTouchMove: this.onMouseMove, onMouseMove: this.onMouseMove, onTouchEnd: ddMouseUp, onMouseUp: ddMouseUp, }; delete eventHandlers.onDragStart; delete eventHandlers.onDragEnd; return (
{children}
); } onMouseDown(e: React.MouseEvent) { ddMouseDown(e); if (isDragHandleEvent(e) || !this.props.handled) { preventFocus(e); //disables text selection in Firefox e.stopPropagation(); } } onMouseMove(e: React.MouseEvent) { if (ddDetect(e)) { if (isDragHandleEvent(e) || !this.props.handled) { this.beginDragDrop(e); } } } beginDragDrop(e: React.MouseEvent) { let { instance } = this.props; let { data, widget, store } = instance; if (widget.onDragStart && instance.invoke("onDragStart", e, instance) === false) return; initiateDragDrop( e, { sourceEl: this.el!, source: { store: store, data: data.data, }, clone: { widget: widget.clone || widget, store, class: data.cloneClass, style: data.cloneStyle, cloneContent: !widget.clone, matchSize: !widget.clone, matchCursorOffset: !widget.clone, }, }, (e: any) => { this.setState({ dragged: false, }); if (widget.onDragEnd) instance.invoke("onDragEnd", e, instance); }, ); this.setState({ dragged: true, }); } }