The sophisticated Drag and Drop library you've been waiting for 🥳
MIT License
Bot releases are hidden (Show)
This release includes several enhancements, bug fixes, and new features. Below are the details of the changes:
Enhanced Scrolling Functionality
Reduced API Surface
Improved Scroll Handling
Scrollable Content Criteria
Mutation Leak Fix
Race Condition Prevention
Fixed Container Sizing
Reconciliation Prioritization
Element Management Extraction
DFlexDOMManager
, improving modularity and organization.Preventing Shifting After Reconciliation
Published by jalal246 over 1 year ago
Allow null
as a valid value for the element animation configuration: This change introduces greater flexibility in configuring element animations. Previously, an animation configuration was required, but now users have the option to use null
to disable animations completely.
Rename super.register
to addElmToRegistry
in the base store's internal implementation: This modification updates the internal implementation of the base store to use the more descriptive method name addElmToRegistry
instead of super.register
. This improvement enhances code readability and clarity.
Add a unit test for the getParsedElmTransform
function: This addition includes a new unit test to verify the behavior and correctness of the getParsedElmTransform
function. The test covers various scenarios and ensures that the function accurately parses and returns the transform matrix of the given element.
Create the CSSTransform
property in the RegisterInputOpts
interface to allow users to specify CSS rules that will be applied to the element when it's interactively dragged: The inclusion of the CSSTransform
property provides users with a convenient way to customize the visual appearance of interactive elements during dragging. By specifying CSS rules for CSSTransform
, users can have precise control over various visual aspects, including background color, border styles, opacity, box-shadow, and more. These CSS rules are dynamically applied to the element while it is being transformed by dragging, and they are automatically removed when the element is settled in its new position. This enhancement enhances the overall user experience and allows for a more visually engaging dragging interaction.
Add the animation
property to the RegisterInputOpts
interface, allowing users to specify configuration options for animations applied to transformed elements during dragging. The animation
property accepts an object of type Partial<AnimationOpts>
, which includes properties such as easing and duration to control the animation behavior. By default, animation is applied unless explicitly configured otherwise.
type RegisterInputOpts = {
id: string;
readonly?: boolean;
+ animation?: Partial<AnimationOpts>;
+ CSSTransform?: CSSClass | CSSStyle;
};
Example:
const registerInput: RegisterInputOpts = {
id: "elementId",
readonly: false,
animation: {
easing: "ease-in-out",
duration: 500,
},
CSSTransform: {
background: "#ff0000",
opacity: 0.5,
},
};
In this example, we specify animation options by setting the animation
property to an object with the easing function set to "ease-in-out" and the duration set to 500 milliseconds. Additionally, we customize the CSS applied to the element during dragging by setting the CSSTransform
property to an object with background color "#ff0000" and opacity 0.5.
Please note that the animation
and CSSTransform
properties are optional, and you can omit them if you don't need to customize animations or apply specific CSS transformations during dragging.
Full Changelog: https://github.com/dflex-js/dflex/compare/v3.9.0...v3.9.1
Published by jalal246 over 1 year ago
computedStyle
, Resizing Fix, and Improved Element Position Detection (#645)Implement CSS Getters and Setters, Resize Event Handler Fix, Enhanced Element Position Detection, Refactoring, and Cleanup (#647). Introduces several improvements and fixes as follows:
Migrate related box model in Threshold to the Box Model (#646): Focuses on migrating the related box model present in the threshold to the Box model. The aim is to consolidate the codebase and improve consistency. Allowing better modularization, and enhancing code maintainability and readability.
Enhancing the style handling in the draggable element by refactoring and optimizing the setMirrorStyle and setOrUnsetOriginStyle functions. The changes improve reusability, reduce duplication, and make the code more concise and maintainable.
Addressing an issue with scroll thresholds for elements that are located outside the screen viewport. The scroll thresholds were not correctly calculated, leading to inaccurate auto-scrolling triggers for these elements.
Addressing the issue of interactive containers resizing during the migration of their child elements, which can lead to layout distortion and cause problems, particularly when the container becomes empty due to the migration of all children.
To mitigate this issue, the solution enforces fixed container sizes that remain consistent during element migration, ensuring that the container size is maintained and does not change unexpectedly.
It ensures that the layout remains visually consistent, and interactive elements remain correctly sized and positioned. It provides a better user experience by preventing any visual disturbances that could arise from container resizing.
Full Changelog: https://github.com/dflex-js/dflex/compare/v3.8.3...v3.9.0
Published by jalal246 over 1 year ago
TS
v5 (https://github.com/dflex-js/dflex/pull/632)e2e
test covering stabilizer mechanism (https://github.com/dflex-js/dflex/pull/633)pnpmV8
(https://github.com/dflex-js/dflex/pull/636)Full Changelog: https://github.com/dflex-js/dflex/compare/v3.8.2...v3.8.3
Published by jalal246 over 1 year ago
0,0
instead of 1,1
(https://github.com/dflex-js/dflex/pull/625)Full Changelog: https://github.com/dflex-js/dflex/compare/v3.8.1...v3.8.2
Published by jalal246 over 1 year ago
MutationObserver
for containers that don't belong to the same parent (#611)Full Changelog: https://github.com/dflex-js/dflex/compare/v3.8.0...v3.8.1
Published by jalal246 over 1 year ago
containersTransition
options for an orphan branch.commit
options for an orphan branch with a solo element to prevent unnecessary reconciliation. React.useEffect(() => {
store.config({
removeContainerWhenEmpty: false,
});
}, []);
Full Changelog: https://github.com/dflex-js/dflex/compare/v3.7.1...v3.8.0
Published by jalal246 over 1 year ago
Published by jalal246 about 2 years ago
Use abstract for axes point.
Move to the box model for more consistent geometry.
Add different positions for different types of dragging.
Support interactivity between scroll containers by allowing custom mirroring the dragged when necessary.
Update listener(s) allowing them to be attached to scroll containers with overflow even if they are not dynamic.
Initialize scroll threshold depending on the depth layer instead of the targeted container.
Flushing element indicators without checking the DOM state leaning more into DOM & VDOM instances.
Unify listeners system with payload and status.
Add keyboard listener to the playground app to enable commit for testing.
Add DFlex Cycle as a global migration holder inside the store.
Add DFlex Session to manage user interactivity with the DFlex app.
Manage reconciliation by using SK
(containers key) inside Cycle. So the reconciliation only targets the affected container(s).
Add options for committing changes to the DOM. Users still have the ability to transform the elements indefinitely but in some cases, apps need to commit after each cycle. This feature is now enabled with embedded reconciliation that targets only affecting elements in their containers. This feature makes DFlex the first drag-and-drop framework with its own reconciler and the first one that gives the user multiple options for indefinite transformation or committing to the DOM when necessary.
Published by jalal246 about 2 years ago
Remove all separate class interfaces in the utility classes. Use class as a type instead.
Enhance registration. All essential data will be assigned and registered even with DFlex being framework agnostic. Register functions still have the capability to create a DFlex-Node instance, along with its container, and scroll. This decreases the scripting time for the first interactive dragging without blocking the initial mount during registration https://github.com/dflex-js/dflex/pull/565.
Use different types for threshold. Previously all thresholds used were external thresholds. I added a new type internal
to apply the threshold percentage correctly for the scroll. An internal threshold is a threshold that checks the dragged inside the given area and is always smaller than the working area.
Use essential geometry for Point<number>
, Point<boolean>
for scroll.
Add more indicators to enhance scrolling. DFlex can identify thresholds, calculate invisible areas,s and based on this information do scroll animation.
Bind scrolling data with scroll listeners. So when scrolling DFlexScrollContainer
can do all the related calculations without reading from DOM.
Synchronously update element visibility when scrolling. Avoid any gap that may appear during the scroll. Also, still, the visible area is the scrolling area. Elements' position won't be updated if it's not visible. But the current approach updates visibility and then scrolls so visibility is calculated before taking action.
Thresholds are now direction-dependent. If drag is in the bottom but going up then the threshold is false.
Differentiate the mechanism from the container.
Published by jalal246 about 2 years ago
dflex/draggable
(#568)Published by jalal246 over 2 years ago
$
prefix.type DFlexEvents =
| "$onDragOutContainer"
| "$onDragOutThreshold"
| "$onDragOver"
| "$onDragLeave"
| "$onLiftUpSiblings"
| "$onMoveDownSiblings";
Implementation like any other event:
import type { DFlexEvents } from "@dflex/dnd";
const onDFlexEvent = (e: DFlexEvents) => {
// Do something, or just don't.
};
// Later you can remove the listener with `removeEventListener`
document.addEventListener("$onDragLeave", onDFlexEvent);
LayoutState
.interface DFlexLayoutStateEvent {
type: "layoutState",
layoutState: "pending" | "ready" | "dragging" | "dragEnd" | "dragCancel";
}
Implementation from anywhere inside the app:
React.useEffect(() => {
const unsubscribe = store.listeners.subscribe((e) => {
console.info("new layout state", e);
}, "layoutState");
return () => {
unsubscribe();
};
}, []);
parentID
from registry input by checking branches internally (#564)interface RegisterInput{
id: string;
depth?: number;
readonly?: boolean;
};
const { id, depth, readonly } = registerInput;
React.useEffect(() => {
if (ref.current) {
store.register({ id, depth, readonly });
}
return () => {
store.unregister(id);
};
}, [ref.current]);
Playwright
(#557)Published by jalal246 over 2 years ago
id
and parentID
for registered elements (#555).playwright
to cover core cases (#556).Published by jalal246 over 2 years ago
Published by jalal246 over 2 years ago
Published by jalal246 over 2 years ago
Refactor meta extractor to distinguish between an empty and orphan case (#524)
Change margin when positioning from orphan to non-orphan container (#529)
Refactor test description for essential cases (#528)
Add unit test to meta extractor (#527)
Refactor unit test for DnD (#526)
Fix margin-bottom calculation taking into consideration the last occupied position (#525)
Upgrade to pnpm 7.
Add essential cases for obliquity and continuity including from/to empty container (#531)
Remove unused instances and methods not used in the store and refactor related tests (#533).
Add origin length to determine when to restore and when the container is expanding (#533).
Refactor unit test for meta extractor (#533).
Fix continuity in extending orphan container (#534)
Clear transition instances after each operation (#537)
Testing transform element to an into empty container (#538)
Enable transforming elements between containers (#539)
interface ContainersTransition {
/** Default=true */
enable: boolean;
/**
* Support orphan to orphan transformation.
* Default=10px
* */
margin: number;
}
Published by jalal246 over 2 years ago
Transformation depends on three cases:
Calculations are made to:
occupied-position
because can't move based on its own position so this will kill dealing with different heights/widths. So it has to move to a post-defined point. For transformation inside the list itself, it's not a problem. But for migration to a new container then the calculated value should be done before triggering any transformation. It's not a step-by-step regular movement.Three different phases for execution:
out-position= true
.Roadmap:
A- Handle the case where transformation between containers happens to the absent bottom (#493)
numberOfElementsTransformed
and updateNumOfElementsTransformed
from dragged and replace the use case with exicting indicators.B- Refactor utils to Core removing #getDiff
(#494)
interface INode extends ICore {
isConnected(): boolean;
isPositionedUnder(elmY: number): boolean;
isPositionedLeft(elmX: number): boolean;
getRectBottom(): number;
getRectRight(): number;
getRectDiff(elm: this, axis: Axis): number;
getDisplacement(elm: this, axis: Axis): number;
getDistance(elm: this, axis: Axis): number;
getOffset(): RectDimensions;
hasSamePosition(elm: this, axis: Axis): boolean;
}
C- Check the layout shift for vertically transforming elements with different heights inside the same container (#496)
Outside the container then insert:
a. The dragged is bigger than the targeted element - moving without releasing.
b. The dragged is smaller than the targeted element - moving without releasing.
c. The dragged is smaller than the targeted element - move and release with iteration.
Inside the container:
a. The dragged is bigger than the targeted element - moving without releasing.
b. The dragged is bigger than the targeted element - move and release with iteration.
c. The dragged is smaller than the targeted element - moving without releasing.
d. The dragged is smaller than the targeted element - move and release with iteration.
D- Enable transformation for containers orphaned by migration (#497)
In this case, the origin container has two elements.
To solve it, when the container is receiving a new element. DFlex restores the preserved last element position and calculates the margin to guarantee the given scenario where there is no shifting in positions.
E- Enable transformation from origin higher than destination
One of the calculations DFlex has is defining a threshold for each layout (#418). Threshold tells the transformers when dragged is in/out its position or the container strict boundaries. This strict definition of threshold helps to improve user experience and the physical sense of elements' interactivity. It also plays role in detecting the active container when an element migrates from one to another. This definition prevents the transformation from a container having bigger height/width into another container having smaller boundaries. When leaving the position and entering a new one the transformer can't tell if the element is inside the less-boundaries container or not. To tackle this issue each depth must have a unified transforming boundary. So when the element is dragged at the same level horizontally or vertically it can be settled into the destination container and attached to the bottom which has the highest weight.
Steps to Solve it:
F- Dealing with multiple transformations without settling into a new position (#519)
This scenario requires checking all the containers that are affected by the transformation and rollback each last transformation accordingly. This is done by adding a unique id
to the migration instance instead of a central one created for each clickable operation. E.g. The user clicks and transforms then one id
is created for this operation. This allows to roll back each transformation connected to the same id
. But to achieve multiple undo between containers the id is shifted into migration.
interface MigrationPerContainer {
/** Last known index for draggable before transitioning. */
index: number;
/** Transition siblings key. */
SK: string;
/** Transition unique id. */
id: string;
}
G- Enable multiple steps of transformation (#520)
H- Add the ability to extend the transformation area between containers (#521). This allows accumulating all registered elements in one container.
Published by jalal246 over 2 years ago
setDistanceIndicators
calculations in one equation instead of if/else.build:w
and list length when migrated to a new container (#461)getDiff
to calculate difference in the space for : "offset" | "occupiedPosition" | "currentPosition"setDistanceIndicators
to setDistanceBtwPositions
core-instance
and Abstract Core and has all the utility methods for the core.interface ICoreUtils extends ICore {
isPositionedUnder(elmY: number): boolean;
isPositionedLeft(elmX: number): boolean;
getRectBottom(): number;
getRectRight(): number;
hasSamePosition(elm: this, axis: Axis): boolean;
}
Rect
option to keep the dimension on track. To do that efficiently it uses 2- CurrentPosition
. Presumably, width and height stay the same but the position keeps changing. So, to get the current offset we need getOffset
a new method introduced to the CoreUtils.
Migration
has now instances used for transition periods. Wich defined as the moment where the insertion is detected to the moment where the insertion is completed. To do that we have insertionTransform
and insertionOffset
. We have also the prev
method. Prev()
and latest()
returns easily the current and last migration instance. When migration is completed we set these instances to null
.handleElmMigration
to the Store. It helps update the boundaries for both new and original lists.newElmOffset
Check elmSyntheticOffset
to detect the right insertion.Create Container Instance which is similar to Core. But with the current edition, we now have multiple instances each one representing a different role.
interface IContainer {
/** Strict Rect for siblings containers. */
readonly boundaries: RectBoundaries;
/** Numbers of total columns and rows each container has. */
readonly grid: IPointNum;
/** Container scroll instance. */
scroll: IScroll;
setGrid(grid: IPointNum, rect: RectDimensions): void;
setBoundaries(rect: RectDimensions): void;
}
interface IDepth {
/** Grouping containers in the same level. */
readonly containers: {
[depth: number]: string[];
};
add(SK: string, depth: number): void;
getByDepth(depth: number): string[];
removeByDepth(depth: number): void;
removeAll(): void;
}
SK
generated by DOM-Gen
.enableContainersTransition
to opts
with a new playground /migration
(#475)Published by jalal246 over 2 years ago
Revert #446 approach Depends on getting parents/children for dragged migrations by enforcing parent registration if not exist in the store. It works fine but is not what it's intended to be.
DFlex main approach in the essential design depends on a flat hierarchy. Instead of getting each element and connecting it to the parent. The store already has all elements on the same level.
Supposed we have container -a
and container-b
. All elements in both containers are included in the registry. Both boundaries for siblings are calculated and can be added to the threshold instance since v3.3.1.
Regular Scenario: user click -> get element -> find the parent -> matching the parent overlay recursively -> matching the children overlay recursively -> remove node/append new node to new parent.
DFlex Implementation: user click -> get element -> matching siblings overlay -> add transformation for new positions.
1. Avoid recursively looking for parents.
2. Always allow elements from the same level to interact with each other.
3. Control element migration map. Registered/same level (siblingDepth
)
4. Insertion inside the containers already shipped. The scenario dragged outside the container and then moved inside. It's already done by getting the siblings' key SK
so it's somehow container agnostic.
Adding Migration
class (#450):
undo
multiple steps back when introducing the time travel
feature.interface IAbstract {
index: number;
key: string;
}
interface IMigration {
/** Get the latest migrations instance */
latest(): IAbstract;
/**
* We only update indexes considering migration definition when it happens
* outside container but not moving inside it.
* So we update an index but we add a key.
*/
setIndex(index: number): void;
/** When migration from one container to another. */
add(index: number, key: string): void;
}
Create an array for branches even if there's one child in (#457) to allow migration and flexible add/remove elements. Enhance actions by adding timeout this will cut off Cypress when there's more than one failing case.
setEffectedElemDirection
and effectedElemDirection
using the direction resulted in the update-element
depending on the increase/decreas parameter.setPosition
in the Core to include z axis
(#443)ESM
and CJS
and bundle them with esbuild
. (#459)yarn
to pnpm
. And Use Vite
instead of CRA
for playground. (#460)Full Changelog: https://github.com/dflex-js/dflex/compare/v3.3.2...v3.4.0
Published by jalal246 over 2 years ago
AxesCoordinates
to Point
For more readability.gridPlaceholder
for Dragged and update it for Core.updateElement
(#432)isLeavingFromHead
, public isLeavingFromTail
and mousePoints
(#435)