DFlex

A Simple, lightweight Solution for a Drag & Drop interactive App

README

    <img
    src="https://raw.githubusercontent.com/dflex-js/dflex/master/DFlex-readme.png"
    alt="DFlex is a Javascript library for modern Drag and Drop apps" />

    <img
    src="https://img.shields.io/github/issues-pr/dflex-js/dflex"
    alt="number of opened pull requests"/>
    <img
    src="https://img.shields.io/npm/v/@dflex/dnd"
    alt="DFlex last released version" />
  <img
    src="https://img.shields.io/github/issues/dflex-js/dflex"
    alt="number of opened issues"/>
   <img
   src="https://img.shields.io/badge/PRs-welcome-brightgreen.svg"
   alt="Dflex welcomes pull request" />
    <img
    src="https://img.shields.io/twitter/url?label=Follow%20%40dflex_js&style=social&url=https%3A%2F%2Ftwitter.com%2Fdflex_js"
    alt="Follow DFlex on twitter" />


DFlex


DFlex is a Javascript library for modern Drag and Drop apps. It's built
with vanilla Javascript and implemented an enhanced transformation mechanism to
manipulate DOM elements. It is by far the only Drag and Drop library on the
internet that manipulates the DOM instead of reconstructing it and has its own
scheduler and reconciler.

Features


- Dynamic architecture.
- Traverse DOM without calling browser API.
- Infinite DOM transformation instead of reconstructing the DOM tree with every interaction.
- Customized and enhanced reconciler targets only elements transformed from origin.
- Isolated from data flow with a scheduler prevents any blocking event.
- Prevent layout shift that happens with any Drag and Drop mechanism.
- Animated transformation with each interaction.
- Headless and compatible with any modern JS framework.
- Targeting each DOM element individually based on registration.
- Event driven and fully customized API.
- Extensible using its own matching algorithm instead of flat recursion algorithm(s).
- Support three different types of restrictions.
- Support four types of custom events and custom layout state emitter.

Implemented Transformation 💡


- The original input order which appears when inspecting elements stays the
  same. While the visual order happens after transformation and it's supported by the
`data-index` attribute to know the order of elements in the visual list.

  original and visual order

- To enable handling a large set of elements, the transformation is related
  to the viewport. No matter how many elements are affected, DFlex only
  transforms elements visible on the screen. Elements outside the viewport are
triggered to a new position when they are visible.

  Trigger elements visible on the screen

- Support strict transformation between containers.

  Handle orphaned container

Installation


  1. ``` sh
  2. npm install @dflex/dnd
  3. ```

API


DFlex DnD depends on three principles to achieve DOM interactivity:

- Register element in the store.
- Start dragging when mouse is down.
- End dragging to release element when mouse is up.

  1. ``` js
  2. import { store, DnD } from "@dflex/dnd";
  3. ```

Register element


Each element should be registered in DFlex DnD Store in order to be active for drag
and drop later.

  1. ```ts
  2. store.register(RegisterInputOpts): void;
  3. ```

Where RegisterInputOpts is an object with the following properties:

- id: string Targeted element-id.
- depth?: number The depth of targeted element starting from zero (The default value is zero).
- readonly?: boolean True for elements that won't be transformed during DnD
  but belongs to the same interactive container.

Create Drag and Drop Session


The responsive drag and drop session should be created when onmousedown is
fired. So it can initialize the element and its siblings before start dragging.

  1. ```ts
  2. const dflexDnD = new DnD(id, coordinate, opts);
  3. ```

- id: string registered element-id in the store.
- coordinate: AxesPoint is an object with {x: number, y: number} contains the coordinates of the
  mouse/touch click.
- opts?: DFlexDnDOpts is DnD options object. You can see [DFlex DnD options
  full documentation by clicking here.](#options)

Start responsive dragging


  1. ```ts
  2. dflexDnD.dragAt(x, y);
  3. ```

- x: number is event.clientX, the horizontal click coordinate.
- y: number is event.clientY, the vertical click coordinate.

End Drag and Drop Session


  1. ```ts
  2. dflexDnD.endDragging();
  3. ```

Cleanup element


It's necessary to cleanup the element from store when the element won't be used
or will be removed/unmounted from the DOM to prevent any potential memory leaks.

  1. ```ts
  2. store.unregister(id: string): void
  3. ```

Options


You can pass options when creating a DnD instance that controls each element
individually. So your options can be different from each other.

Dragging Threshold


The threshold object defines when the dragging event should be fired and
triggers the response of other sibling elements.

Threshold Interface


  1. ```ts
  2. interface ThresholdPercentages {
  3.   /** vertical threshold in percentage from 0-100 */
  4.   vertical: number;

  5.   /** horizontal threshold in percentage from 0-100 */
  6.   horizontal: number;
  7. }
  8. ```

Threshold Definition


  1. ```ts
  2. interface DFlexDnDOpts {
  3.   // ... other options.
  4.   threshold?: Partial<ThresholdPercentages>;
  5. }
  6. ```

Threshold Default Value


  1. ``` json
  2. {
  3.   "threshold": {
  4.     "vertical": 60,
  5.     "horizontal": 60
  6.   }
  7. }
  8. ```

Commit changes to DOM


DFlex is built to manipulate DOM elements with transformation indefinitely. This
means you can always drag and drop elements without reconstruction of the DOM.
Still, it comes with a reconciler that tracks elements' changes and only
reconciles the elements that have changed their position from their origin.

Commit Interface


  1. ```ts
  2. interface CommitInterface {
  3.   enableAfterEndingDrag: boolean;
  4.   enableForScrollOnly: boolean;
  5. }
  6. ```

Commit Definition


  1. ```ts
  2. interface DFlexDnDOpts {
  3.   // ... other options.
  4.   commit?: Partial<CommitInterface>;
  5. }
  6. ```

Commit Default Value


  1. ``` json
  2. {
  3.   "commit": {
  4.     "enableAfterEndingDrag": true,
  5.     "enableForScrollOnly": true
  6.   }
  7. }
  8. ```

Dragging Restrictions


You can define the dragging restrictions for each element relative:

1. Element position.
2. Element parent container.
3. Screen viewport (automatically enabled).

Restrictions Interface


  1. ```ts
  2. interface Restrictions {
  3.   self: {
  4.     allowLeavingFromTop: boolean;
  5.     allowLeavingFromBottom: boolean;
  6.     allowLeavingFromLeft: boolean;
  7.     allowLeavingFromRight: boolean;
  8.   };
  9.   container: {
  10.     allowLeavingFromTop: boolean;
  11.     allowLeavingFromBottom: boolean;
  12.     allowLeavingFromLeft: boolean;
  13.     allowLeavingFromRight: boolean;
  14.   };
  15. }
  16. ```

Restrictions Definition


  1. ```ts
  2. interface DFlexDnDOpts {
  3.   // ... other options.
  4.   restrictions?: {
  5.     self?: Partial<Restrictions["self"]>;
  6.     container?: Partial<Restrictions["container"]>;
  7.   };
  8. }
  9. ```

Restrictions Default Value


  1. ``` json
  2. {
  3.   "restrictions": {
  4.     "self": {
  5.       "allowLeavingFromTop": true,
  6.       "allowLeavingFromBottom": true,
  7.       "allowLeavingFromLeft": true,
  8.       "allowLeavingFromRight": true
  9.     },
  10.     "container": {
  11.       "allowLeavingFromTop": true,
  12.       "allowLeavingFromBottom": true,
  13.       "allowLeavingFromLeft": true,
  14.       "allowLeavingFromRight": true
  15.     }
  16.   }
  17. }
  18. ```

Auto-Scroll


Auto-Scroll Interface


  1. ```ts
  2. interface ScrollOptions {
  3.   enable?: boolean;
  4.   initialSpeed?: number;
  5.   threshold?: Partial<ThresholdPercentages>;
  6. }
  7. ```

Auto-Scroll Definition


  1. ```ts
  2. interface DFlexDnDOpts {
  3.   // ... other options.
  4.   scroll?: Partial<ScrollOptions>;
  5. }
  6. ```

Auto-Scroll Default Value


  1. ``` json
  2. {
  3.   "scroll": {
  4.     "enable": true,
  5.     "initialSpeed": 10,
  6.     "threshold": {
  7.       "vertical": 15,
  8.       "horizontal": 15
  9.     }
  10.   }
  11. }
  12. ```

Events


DFlex has three (3) types of [custom
events](https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent/CustomEvent).


Event Usage


  1. ``` js
  2. // DFlex event handler.
  3. const onDFlexEvent = (e: DFlexEvents) => {
  4.   // Do something.
  5.   console.log(`onDFlexEvent: ${e.type}`, e.detail);
  6. };

  7. // Dragged Events.
  8. const ON_OUT_CONTAINER = "$onDragOutContainer";
  9. const ON_OUT_THRESHOLD = "$onDragOutThreshold";

  10. //  Interactivity Events.
  11. const ON_DRAG_OVER = "$onDragOver";
  12. const ON_DRAG_LEAVE = "$onDragLeave";

  13. // Sibling Events.
  14. const ON_LIFT_UP = "$onLiftUpSiblings";
  15. const ON_MOVE_DOWN = "$onMoveDownSiblings";

  16. // Capture DFlex event.
  17. document.addEventListener(
  18.   ON_OUT_CONTAINER /** or another event */,
  19.   onDFlexEvent
  20. );

  21. // Remove it later when dragging is done.
  22. document.removeEventListener(
  23.   ON_OUT_CONTAINER /** or another event */,
  24.   onDFlexEvent
  25. );
  26. ```

Dragged Event


It's an event related to capturing dragged positions. This event is fired when
the dragged is out of its threshold position $onDragOutContainer or out of its
container $onDragOutThreshold.

DraggedEvent interface


  1. ```ts
  2. interface PayloadDraggedEvent {
  3.   /** Returns element id in the registry  */
  4.   id: string;

  5.   /** Returns dragged temp index */
  6.   index: number;
  7. }

  8. /** For dragged out of threshold or container event. */
  9. type DFlexDraggedEvent = CustomEvent<PayloadDraggedEvent>;
  10. ```

Interactivity Event


It's an event related to capturing dragged interactions with other elements.
This event is fired when the dragged is over another element $onDragOver or
when the dragged is leaving the occupied position $onDragLeave.

InteractivityEvent interface

  1. ```ts
  2. interface PayloadInteractivityEvent {
  3.   /** Returns element id in the registry  */
  4.   id: string;

  5.   /** Returns element current index */
  6.   index: number;

  7.   /** Returns the element that triggered the event  */
  8.   target: HTMLElement;
  9. }

  10. /** For dragged over an element or leaving an element. */
  11. type DFlexInteractivityEvent = CustomEvent<PayloadInteractivityEvent>;
  12. ```

Siblings Event


It's an event related to capturing siblings' positions. This event is fired when
the siblings are lifting up $onLiftUpSiblings or moving down $onMoveDownSiblings

SiblingsEvent interface

  1. ```ts
  2. interface PayloadSiblingsEvent {
  3.   /** Returns the index where the dragged left  */
  4.   from: number;

  5.   /** Returns the last index effected of the dragged leaving/entering  */
  6.   to: number;

  7.   /** Returns an array of sibling ids in order  */
  8.   siblings: string[];
  9. }

  10. /** When dragged movement triggers the siblings up/down. */
  11. type DFlexSiblingsEvent = CustomEvent<PayloadSiblingsEvent>;
  12. ```

Listeners


DFlex listeners are more generic than the custom events and responsible for
monitoring the entire layout and reporting back to you.

DFlex has two (2) types of listeners:


Listener Usage


  1. ``` js
  2. // app/index.js

  3. const unsubscribeLayout = store.listeners.subscribe((e) => {
  4.   console.info("new layout state", e);
  5. }, "layoutState");

  6. // call it later for clear listeners from memory.
  7. unsubscribeLayout();

  8. const unsubscribeMutation = store.listeners.subscribe((e) => {
  9.   console.info("new mutation state", e);
  10. }, "mutation");

  11. // call it later for clear listeners from memory.
  12. unsubscribeMutation();
  13. ```

Layout state listener


Responsible for monitoring any change that happens to layout interactivity.

Layout state listener interface


  1. ```ts
  2. type LayoutState =
  3.   | "pending" // when DnD is initiated but not activated yet.
  4.   | "ready" // When clicking over the registered element. The element is ready but not being dragged.
  5.   | "dragging" // as expected.
  6.   | "dragEnd" // as expected.
  7.   | "dragCancel"; // When releasing the drag without settling in the new position.

  8. interface DFlexLayoutStateEvent {
  9.   type: "layoutState";
  10.   status: LayoutState;
  11. }
  12. ```

Mutation listener


Responsible for monitoring DOM mutation that happens during reconciliation.

Mutation listener interface


  1. ```ts
  2. type ElmMutationType = "committed";

  3. interface DFlexElmMutationEvent {
  4.   type: "mutation";
  5.   status: ElmMutationType;
  6.   payload: {
  7.     target: HTMLElement; // HTML element container.
  8.     ids: string[]; // Committed Elements' id in order.
  9.   };
  10. }
  11. ```

Advanced


getSerializedElm


DFlex elements are serialized and exported accordingly.

  1. ```ts
  2. store.getSerializedElm(elmID: string): DFlexSerializedElement | null

  3. type DFlexSerializedElement = {
  4.   type: string;
  5.   version: number;
  6.   id: string;
  7.   translate: PointNum | null;
  8.   grid: PointNum;
  9.   order: DFlexDOMGenOrder;
  10.   initialPosition: AxesPoint;
  11.   rect: BoxRectAbstract;
  12.   hasTransformedFromOrigin: boolean;
  13.   hasPendingTransformation: boolean;
  14.   isVisible: boolean;
  15. };
  16. ```

getSerializedScrollContainer


DFlex scroll containers are serialized and exported accordingly. You can get any
scroll container for any registered element id.

  1. ```ts
  2. store.getSerializedScrollContainer(elmID: string): DFlexSerializedScroll | null

  3. type DFlexSerializedScroll = {
  4.   type: string;
  5.   version: number;
  6.   key: string;
  7.   hasOverFlow: AxesPoint<boolean>;
  8.   hasDocumentAsContainer: boolean;
  9.   scrollRect: AbstractBox;
  10.   scrollContainerRect: AbstractBox;
  11.   invisibleDistance: AbstractBox;
  12.   visibleScreen: Dimensions;
  13. };
  14. ```

commit


Commit changes to the DOM. commit will always do surgical reconciliation. and
it's the same function that's used in the options

  1. ```ts
  2. store.commit(): void
  3. ```

isLayoutAvailable


True when DFlex is not transforming any elements and not executing any task.

  1. ```ts
  2. isLayoutAvailable(): boolean
  3. ```

unregister


safely removing element from store.

  1. ```ts
  2. store.unregister(id: string): void
  3. ```

destroy


To destroy all DFlex instances. This is what you should do when you are done
with DnD completely and your app is about to be closed.

  1. ```ts
  2. store.destroy(): void;
  3. ```

Project Content 🚀


[@dflex/dom-gen](https://github.com/dflex-js/dflex/tree/main/packages/dflex-dom-gen)


DFlex DOM relations generator algorithm. It Generates relations between DOM elements based
on element depth so all the registered DOM can be called inside registry without
the need to call browser API. Read once, implement everywhere.

[@dflex/core-instance](https://github.com/dflex-js/dflex/tree/main/packages/dflex-core-instance)


Core instance is the mirror of interactive element that includes all the properties and methods to manipulate the node.

[@dflex/utils](https://github.com/dflex-js/dflex/tree/main/packages/dflex-utils)


A collection of shared functions. Mostly classes, and types that are used across
the project.

[@dflex/store](https://github.com/dflex-js/dflex/tree/main/packages/dflex-store)


DFex Store has main registry for all DOM elements that will be manipulated. It
is a singleton object that is accessible from anywhere in the application. The
initial release was generic but it only has the Core of the library since ^V3.

[@dflex/draggable](https://github.com/dflex-js/dflex/tree/main/packages/dflex-draggable)


Light weight draggable element without extra functionalities that is
responsible for interacting with the DOM and moving the affected element(s).

[@dflex/dnd](https://github.com/dflex-js/dflex/tree/main/packages/dflex-dnd)


The main package that depends on the other packages. It is responsible for the
magical logic of the library to introduce the drag and drop interactive
functionality.

Documentation 📖


For documentation, more information about DFlex and a live demo, be sure to visit the DFlex website

Contribution 🌎


PRs are welcome, If you wish to help, you can learn more about how you can
contribute to this project in the Contributing guide.

Work in progress 🔨


DFlex is a work-in-progress project and currently in development.

License 🤝


DFlex is MIT License.

Author


Jalal Maskoun (@jalal246)