Responsive Draggable

Ecosistema completo para organización y gestión de contenido con interfaces espaciales

ResponsiveDraggable Directive

Description

The ResponsiveDraggable directive implements fully responsive drag functionality for widgets in the visual editor. This directive is essential for allowing users to interact with widgets through drag and drop, respecting different breakpoints and layout systems.

Key Features

Import

import { ResponsiveDraggableDirective } from '@widgets-editor/shared-ui';

API

Inputs

Name Type Default Value Description
widget Widget - Widget to control (required)
activeBreakpoint BreakpointKey 'base' Current active breakpoint
dragEnabled boolean true Enables/disables drag functionality
dragHandle string | undefined undefined CSS selector for drag handle element
dragConstraint 'parent' | 'window' | undefined undefined Boundary constraint for dragging
dragSnapToGrid boolean false Enables/disables snap to grid
dragGridSize number 8 Grid size for snapping
dragAxis 'x' | 'y' | 'xy' 'xy' Permitted axis for dragging
dragInitialTransform boolean true Preserves initial transformation
parentLayoutType 'standard' | 'grid' | 'flex' 'standard' Parent container layout type
positionUnits 'px' | '%' | 'vw' | 'vh' | 'auto' 'px' Units for positioning
gridArea string | null null Grid area for grid layouts
flexOrder number | null null Order for flex layouts

Outputs

Name Type Description
dragStart EventEmitter<ResponsiveDragStartEvent> Emitted when dragging starts
dragMove EventEmitter<ResponsiveDragMoveEvent> Emitted during drag movement
dragEnd EventEmitter<ResponsiveDragEndEvent> Emitted when dragging ends

Public Methods

Name Parameters Return Description
setPosition (x: number, y: number): void void Sets the element's position
setPositionWithEvent (x: number, y: number, emitEvent?: boolean): void void Sets position and optionally emits an event
getPosition (): { x: number; y: number } { x: number; y: number } Gets the current element position
updateElementStyles (styles: Record<string, string>): void void Directly updates element CSS styles
reset (): void void Resets position to initial transformation

Interfaces

ResponsiveDragStartEvent

interface ResponsiveDragStartEvent {
  source: ResponsiveDraggableDirective;
  element: HTMLElement;
  event: MouseEvent | TouchEvent;
  widget: Widget;
  breakpoint: BreakpointKey;
}

ResponsiveDragMoveEvent

interface ResponsiveDragMoveEvent {
  source: ResponsiveDraggableDirective;
  element: HTMLElement;
  event: MouseEvent | TouchEvent;
  deltaX: number;
  deltaY: number;
  offsetX: number;
  offsetY: number;
  widget: Widget;
  breakpoint: BreakpointKey;
}

ResponsiveDragEndEvent

interface ResponsiveDragEndEvent {
  source: ResponsiveDraggableDirective;
  element: HTMLElement;
  event: MouseEvent | TouchEvent;
  deltaX: number;
  deltaY: number;
  widget: Widget;
  breakpoint: BreakpointKey;
}

Internal Functionality

State Management with Signals

The directive uses Angular's Signal system to manage internal state and maintain synchronization between the data model and visual presentation:

private _widgetSignal = signal<Widget | null>(null);
private _activeBreakpointSignal = signal<BreakpointKey>('base');
private _widgetPosition = computed(() => {
  const widget = this._widgetSignal();
  const breakpoint = this._activeBreakpointSignal();

  if (!widget) return { x: 0, y: 0, constrainToParent: false };

  if (this.isResponsivePosition(widget.position)) {
    const responsivePosition = widget.position as ResponsiveValue<Position>;
    return responsivePosition[breakpoint] || responsivePosition.base;
  } else {
    return widget.position as Position;
  }
});

Responsiveness Handling

The directive automatically detects whether the widget position is responsive or not, and applies the corresponding behavior:

private isResponsivePosition(position: Position | ResponsiveValue<Position>): boolean {
  return (position as ResponsiveValue<Position>).base !== undefined;
}

Bidirectional Updates

After each drag, the directive updates the widget's data model:

private updateWidgetPositionModel(deltaX: number, deltaY: number): void {
  const widget = this._widgetSignal();
  const breakpoint = this._activeBreakpointSignal();

  if (!widget) return;

  const updatedWidget = { ...widget };

  if (this.isResponsivePosition(updatedWidget.position)) {
    // Update responsive position only for current breakpoint
    // ...
  } else {
    // Update standard position
    // ...
  }

  this._widgetSignal.set(updatedWidget);
}

Usage Examples

Basic Usage

<div
  libResponsiveDraggable
  [widget]="widget"
  [activeBreakpoint]="currentBreakpoint"
  (dragEnd)="onDragEnd($event)"
>
  <!-- Widget content -->
</div>

With Constraints and Custom Handle

<div
  libResponsiveDraggable
  [widget]="widget"
  [activeBreakpoint]="currentBreakpoint"
  [dragHandle]="'.handle'"
  [dragConstraint]="'parent'"
  [dragSnapToGrid]="true"
  [dragGridSize]="10"
  (dragEnd)="onDragEnd($event)"
>
  <div class="handle">Drag here</div>
  <!-- Widget content -->
</div>

Usage in Grid Layout

<div
  libResponsiveDraggable
  [widget]="widget"
  [activeBreakpoint]="currentBreakpoint"
  [parentLayoutType]="'grid'"
  [gridArea]="'content'"
  [positionUnits]="'%'"
  (dragEnd)="onDragEnd($event)"
>
  <!-- Widget content -->
</div>

Performance Considerations

Integration with the Responsive System

The directive is designed to work closely with the editor's responsive system:

Usage with Advanced Layout Systems

Although the directive is compatible with Grid and Flex layouts, it currently uses CSS transformations for visual positioning. In future versions, a more advanced system will be implemented to directly modify Grid and Flex properties.