├── .changeset ├── README.md └── config.json ├── .eslintrc.js ├── .github ├── CODEOWNERS ├── dependabot.yml ├── labeler.yml ├── stale.yml └── workflows │ ├── ci.yml │ ├── labeler.yml │ └── size-limit.yml ├── .gitignore ├── .npmrc ├── .prettierrc ├── .vscode └── settings.json ├── CODE_OF_CONDUCT.md ├── CONTRIBUTING.md ├── DFlex-readme.png ├── LICENSE ├── README.md ├── dflex-env.d.ts ├── dflex-github-cover.png ├── docs ├── drag-drop │ ├── api.md │ └── examples.md ├── draggable │ ├── api.md │ └── examples.md ├── nav.json └── utils │ ├── dom-gen.md │ └── dom-store.md ├── jest.config.ts ├── package.json ├── packages ├── dflex-core-instance │ ├── .eslintrc.js │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ │ ├── Container │ │ │ ├── DFlexParentContainer.ts │ │ │ ├── DFlexScrollContainer.ts │ │ │ ├── index.ts │ │ │ └── scrollUtils │ │ │ │ ├── DFlexOverflow.ts │ │ │ │ ├── DFlexScrollProps.ts │ │ │ │ └── index.ts │ │ ├── Element │ │ │ ├── DFlexBaseElement.ts │ │ │ ├── DFlexCoreElement.ts │ │ │ ├── DFlexElement.ts │ │ │ └── index.ts │ │ └── index.ts │ ├── test │ │ └── container.test.tsx │ └── tsconfig.json ├── dflex-dnd │ ├── .eslintrc.js │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── public │ │ ├── dnd.gif │ │ └── dnd.png │ ├── src │ │ ├── DnD.ts │ │ ├── Draggable │ │ │ ├── DraggableAxes.ts │ │ │ ├── DraggableInteractive.ts │ │ │ ├── DraggablePositions │ │ │ │ ├── DraggablePositions.ts │ │ │ │ ├── DraggedCurrentPosition.ts │ │ │ │ ├── index.ts │ │ │ │ └── types.ts │ │ │ └── index.ts │ │ ├── Events │ │ │ ├── DFlexEvents.ts │ │ │ ├── constants.ts │ │ │ ├── emitters.ts │ │ │ ├── index.ts │ │ │ └── types.ts │ │ ├── LayoutManager │ │ │ ├── DFlexDOMReconciler.ts │ │ │ ├── DFlexDnDExportedStore.ts │ │ │ ├── DFlexDnDStore.ts │ │ │ ├── DFlexDnDStoreSingleton.ts │ │ │ ├── DFlexScheduler.ts │ │ │ ├── DFlexVisibilityUpdater.ts │ │ │ └── index.ts │ │ ├── Listeners │ │ │ ├── DFlexListeners.ts │ │ │ ├── constants.ts │ │ │ ├── index.ts │ │ │ ├── notifications.ts │ │ │ └── types.ts │ │ ├── Mechanism │ │ │ ├── DFlexMechanismController.ts │ │ │ ├── DFlexPositionUpdater.ts │ │ │ ├── DFlexScrollTransition.ts │ │ │ ├── DFlexScrollableElement.ts │ │ │ ├── EndCycle.ts │ │ │ └── index.ts │ │ ├── Mutation │ │ │ ├── DFlexDirtyLeavesCollector.ts │ │ │ ├── DFlexIDGarbageCollector.ts │ │ │ ├── DFlexIDModifier.ts │ │ │ ├── DFlexMutations.ts │ │ │ └── index.ts │ │ ├── exportedStoreSingleton.ts │ │ ├── index.ts │ │ ├── types.ts │ │ └── utils │ │ │ ├── constants.ts │ │ │ └── extractOpts.ts │ ├── test │ │ ├── __snapshots__ │ │ │ ├── extractOpts.test.ts.snap │ │ │ └── layoutManager.test.tsx.snap │ │ ├── extractOpts.test.ts │ │ ├── getInsertionELmMeta.test.tsx │ │ └── layoutManager.test.tsx │ └── tsconfig.json ├── dflex-dom-gen │ ├── .eslintrc.js │ ├── LICENSE │ ├── README.md │ ├── img │ │ ├── connect.excalidraw │ │ ├── connect.png │ │ ├── pointer.excalidraw │ │ └── pointer.png │ ├── package.json │ ├── src │ │ ├── DFlexDOMKeysGenerator.ts │ │ ├── DFlexDOMKeysManager.ts │ │ └── index.ts │ ├── test │ │ ├── delete.test.ts │ │ ├── gen.asc.test.ts │ │ └── gen.des.test.ts │ └── tsconfig.json ├── dflex-draggable │ ├── .eslintrc.js │ ├── LICENSE │ ├── img │ │ └── draggable.gif │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── DFlexBaseDraggable.ts │ │ ├── DFlexDraggable.ts │ │ ├── DFlexDraggableStore.ts │ │ └── index.ts │ ├── test │ │ ├── __snapshots__ │ │ │ └── draggableStore.test.tsx.snap │ │ └── draggableStore.test.tsx │ └── tsconfig.json ├── dflex-store │ ├── .eslintrc.js │ ├── LICENSE │ ├── README.md │ ├── img │ │ ├── store-registry │ │ └── store-registry.png │ ├── package.json │ ├── src │ │ ├── DFlexBaseStore.ts │ │ ├── DFlexDOMManager.ts │ │ ├── index.ts │ │ └── utils.ts │ ├── test │ │ └── storeBase.test.tsx │ └── tsconfig.json └── dflex-utils │ ├── .eslintrc.js │ ├── LICENSE │ ├── README.md │ ├── package.json │ ├── src │ ├── Box │ │ ├── AbstractBox.ts │ │ ├── Box.ts │ │ ├── BoxBool.ts │ │ ├── BoxNum.ts │ │ ├── BoxRect.ts │ │ └── index.ts │ ├── DFlexCycle │ │ ├── DFlexCycle.ts │ │ └── index.ts │ ├── DFlexEventDebounce │ │ ├── eventDebounce.ts │ │ └── index.ts │ ├── DFlexTracker │ │ ├── DFlexTracker.ts │ │ ├── DFlexTrackerSingleton.ts │ │ ├── constant.ts │ │ └── index.ts │ ├── FeatureFlags.ts │ ├── Point │ │ ├── AxesPoint.ts │ │ ├── Point.ts │ │ ├── PointBool.ts │ │ ├── PointNum.ts │ │ └── index.ts │ ├── TaskQueue │ │ ├── TaskQueue.ts │ │ └── index.ts │ ├── Threshold │ │ ├── Threshold.ts │ │ ├── ThresholdDeadZone.ts │ │ └── index.ts │ ├── collections │ │ ├── assertElmPos.ts │ │ ├── combineKeys.ts │ │ ├── getAnimationOptions.ts │ │ ├── getRectTypeByAxis.ts │ │ ├── index.ts │ │ ├── utils.ts │ │ └── warnOnce.ts │ ├── computedStyleUtils │ │ ├── computedStyleUtils.ts │ │ └── index.ts │ ├── constants.ts │ ├── environment │ │ ├── DFlexCreateEvent.ts │ │ ├── DFlexRAF.ts │ │ ├── DFlexTimeout.ts │ │ ├── canUseDOM.ts │ │ ├── getElmBoxRect.ts │ │ ├── getParentElm.ts │ │ ├── getSelection.ts │ │ ├── index.ts │ │ ├── updateDOMAttr.ts │ │ └── updateElmDatasetGrid.ts │ ├── index.ts │ └── types.ts │ ├── test │ ├── BoxNum.test.ts │ ├── __snapshots__ │ │ └── threshold.test.ts.snap │ ├── eventDebounce.test.ts │ ├── getParsedElmTransform.test.ts │ ├── taskQueue.test.ts │ └── threshold.test.ts │ └── tsconfig.json ├── playgrounds ├── dflex-dnd-playground │ ├── .eslintrc.js │ ├── .gitignore │ ├── LICENSE │ ├── cypress.config.ts │ ├── cypress │ │ ├── .eslintrc.js │ │ ├── e2e │ │ │ ├── multiple-containers │ │ │ │ ├── continuity │ │ │ │ │ ├── distributeIntoContainers.cy.ts │ │ │ │ │ ├── emptyMultiple.restore.cy.ts │ │ │ │ │ └── multiple.intoTop.cy.ts │ │ │ │ ├── differentHeights │ │ │ │ │ ├── draggedBigger.release.cy.ts │ │ │ │ │ └── draggedSmaller.release.cy.ts │ │ │ │ ├── essential │ │ │ │ │ ├── toBiggerList │ │ │ │ │ │ ├── horizontally.bottom.cy.ts │ │ │ │ │ │ ├── horizontally.top.cy.ts │ │ │ │ │ │ ├── horizontally.top.multiSteps.cy.ts │ │ │ │ │ │ └── vertically.top.orphan.cy.ts │ │ │ │ │ ├── toOrphanList │ │ │ │ │ │ └── horizontally.bottom.cy.ts │ │ │ │ │ └── toSmallerList │ │ │ │ │ │ └── horizontally.bottom.cy.ts │ │ │ │ ├── extend │ │ │ │ │ ├── append.extend.cy.ts │ │ │ │ │ └── split.extend.cy.ts │ │ │ │ ├── obliquity │ │ │ │ │ ├── bottomUp │ │ │ │ │ │ ├── multi.cy.ts │ │ │ │ │ │ └── oneStep.cy.ts │ │ │ │ │ └── upBottom │ │ │ │ │ │ └── multi.cy.ts │ │ │ │ └── undo │ │ │ │ │ ├── inside.noSplit.cy.ts │ │ │ │ │ └── split.up.cy.ts │ │ │ └── same-container-vertical │ │ │ │ ├── differentHeights │ │ │ │ ├── inOut │ │ │ │ │ ├── inOut.draggedBigger.noRelease.cy.ts │ │ │ │ │ ├── inOut.draggedSmaller.Release.cy.ts │ │ │ │ │ └── inOut.draggedSmaller.noRelease.cy.ts │ │ │ │ └── strict │ │ │ │ │ ├── strict.draggedBigger.noRelease.cy.ts │ │ │ │ │ ├── strict.draggedBigger.release.cy.ts │ │ │ │ │ ├── strict.draggedSmaller.noRelease.cy.ts │ │ │ │ │ └── strict.draggedSmaller.release.cy.ts │ │ │ │ ├── extended │ │ │ │ └── edgeCases │ │ │ │ │ ├── differentViewport.inOut1.cy.ts │ │ │ │ │ └── differentViewport.inOut2.cy.ts │ │ │ │ └── restrictions │ │ │ │ ├── container-all │ │ │ │ ├── edgeCases │ │ │ │ │ └── continuity.topELm.cy.ts │ │ │ │ └── generalUsage │ │ │ │ │ ├── b.r.l.noRelease.cy.ts │ │ │ │ │ └── t.r.l.noRelease.cy.ts │ │ │ │ ├── container-diff │ │ │ │ ├── horizontal.cy.ts │ │ │ │ └── vertical.cy.ts │ │ │ │ ├── self │ │ │ │ ├── horizontal.cy.ts │ │ │ │ └── vertical.cy.ts │ │ │ │ └── viewport │ │ │ │ └── viewport.cy.ts │ │ ├── fixtures │ │ │ └── example.json │ │ ├── plugins │ │ │ └── index.js │ │ ├── support │ │ │ ├── commands.js │ │ │ └── e2e.js │ │ └── tsconfig.json │ ├── favicon.ico │ ├── index.html │ ├── package.json │ ├── readme.md │ ├── src │ │ ├── App.tsx │ │ ├── components │ │ │ ├── DFlexDnDComponent.tsx │ │ │ ├── depth │ │ │ │ ├── NestedList.tsx │ │ │ │ ├── index.ts │ │ │ │ └── readme.md │ │ │ ├── essential │ │ │ │ ├── List.css │ │ │ │ ├── ListMigration.tsx │ │ │ │ ├── Lists.tsx │ │ │ │ ├── component-based-event │ │ │ │ │ ├── Core.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ └── readme.md │ │ │ │ ├── container-based-event │ │ │ │ │ ├── Container.tsx │ │ │ │ │ ├── Core.tsx │ │ │ │ │ ├── index.ts │ │ │ │ │ └── readme.md │ │ │ │ └── index.ts │ │ │ ├── gap │ │ │ │ ├── bigGap.tsx │ │ │ │ └── index.ts │ │ │ ├── index.ts │ │ │ ├── restrictions │ │ │ │ ├── AllRestrictedContainer.tsx │ │ │ │ ├── SelRestricted.tsx │ │ │ │ ├── SomeRestrictedContainer.tsx │ │ │ │ └── index.ts │ │ │ ├── scroll │ │ │ │ ├── ExtendedList.tsx │ │ │ │ ├── ScrollablePage.tsx │ │ │ │ ├── WindowedDualList.tsx │ │ │ │ ├── index.ts │ │ │ │ └── multiLists.tsx │ │ │ ├── stream │ │ │ │ ├── StreamIncremental.tsx │ │ │ │ ├── StreamInterval.tsx │ │ │ │ ├── StreamNewELm.tsx │ │ │ │ ├── index.ts │ │ │ │ └── readme.md │ │ │ └── todo │ │ │ │ ├── TodoList.tsx │ │ │ │ ├── TodoListWithEvents.tsx │ │ │ │ ├── TodoListWithReadonly.tsx │ │ │ │ └── index.ts │ │ ├── index.css │ │ ├── main.tsx │ │ └── vite-env.d.ts │ ├── tests │ │ ├── core │ │ │ ├── cases │ │ │ │ ├── consistency.spec.ts │ │ │ │ ├── orphan.outList.spec.ts │ │ │ │ └── outContainer.threshold.spec.ts │ │ │ ├── newPosition │ │ │ │ ├── continuity.multiPositions.spec.ts │ │ │ │ ├── continuity.noRelease.spec.ts │ │ │ │ └── continuity.release.spec.ts │ │ │ ├── obliquity │ │ │ │ ├── bottomUp.spec.ts │ │ │ │ └── upBottom.spec.ts │ │ │ ├── outList │ │ │ │ ├── horizontally.spec.ts │ │ │ │ └── vertically.spec.ts │ │ │ └── outPosition │ │ │ │ ├── horizontally.spec.ts │ │ │ │ └── vertically.spec.ts │ │ ├── events │ │ │ ├── dragAttributes.spec.ts │ │ │ ├── dragEvents.spec.ts │ │ │ ├── interactivityEvents.spec.ts │ │ │ └── siblingEvents.spec.ts │ │ ├── features │ │ │ ├── dragSettleOnSwitch.spec.ts │ │ │ ├── grid.spec.ts │ │ │ ├── multiDepth.spec.ts │ │ │ ├── resize.reconcile.spec.ts │ │ │ ├── serialization.spec.ts │ │ │ ├── zIndexVisualTest.spec.ts │ │ │ └── zIndexVisualTest.spec.ts-snapshots │ │ │ │ └── parent-b-chromium-win32.png │ │ ├── keys │ │ │ ├── grid.keys.spec.ts │ │ │ ├── landing.keys.spec.ts │ │ │ ├── migration.keys.spec.ts │ │ │ └── stream.inc.keys.spec.ts │ │ ├── multiple-containers │ │ │ ├── emptyContainer.horizontal.spec.ts │ │ │ └── strict.horizontal.spec.ts │ │ ├── scroll │ │ │ ├── multi-containers │ │ │ │ └── windowedDualList.spec.ts │ │ │ ├── one-container │ │ │ │ ├── scroll.biDirection.outside.spec.ts │ │ │ │ ├── scroll.oneDirection.inside.spec.ts │ │ │ │ └── scroll.oneDirection.outside.spec.ts │ │ │ ├── scroll.attributes.spec.ts │ │ │ └── visibility │ │ │ │ ├── visibility.container.spec.ts │ │ │ │ ├── visibility.el.continuity.spec.ts │ │ │ │ └── visibility.el.middle.spec.ts │ │ ├── stabilizer │ │ │ ├── stabilizer.scroll.spec.ts │ │ │ └── stabilizer.spec.ts │ │ └── stream │ │ │ ├── stream.inc.spec.ts │ │ │ └── stream.newELm.spec.ts │ ├── tsconfig.json │ └── vite.config.ts ├── dflex-draggable-playground │ ├── .eslintrc.js │ ├── .gitignore │ ├── LICENSE │ ├── README.md │ ├── favicon.ico │ ├── index.html │ ├── package.json │ ├── src │ │ ├── App.css │ │ ├── App.tsx │ │ ├── DraggableHandler │ │ │ ├── DraggableHandler.tsx │ │ │ ├── HandlerSVG.tsx │ │ │ └── index.tsx │ │ ├── DraggableSolo │ │ │ ├── DraggableSolo.tsx │ │ │ └── index.tsx │ │ ├── index.css │ │ ├── main.tsx │ │ └── vite-env.d.ts │ ├── tests │ │ └── draggable.spec.ts │ ├── tsconfig.json │ └── vite.config.ts ├── dflex-e2e-utils │ ├── package.json │ ├── src │ │ ├── DFlexPageTest.ts │ │ ├── constants.ts │ │ ├── events.ts │ │ ├── index.ts │ │ ├── listeners.ts │ │ ├── types.ts │ │ └── utils.ts │ └── tsconfig.json ├── dflex-next-playground │ ├── .eslintrc.js │ ├── .gitignore │ ├── next.config.js │ ├── package.json │ ├── postcss.config.js │ ├── public │ │ └── favicon.ico │ ├── src │ │ ├── components │ │ │ ├── ClickableBox.tsx │ │ │ ├── TodoContainer.tsx │ │ │ ├── TodoItem.tsx │ │ │ └── index.ts │ │ ├── pages │ │ │ ├── _app.tsx │ │ │ ├── _document.tsx │ │ │ ├── from-to │ │ │ │ ├── index.tsx │ │ │ │ └── list.tsx │ │ │ ├── index.tsx │ │ │ ├── list │ │ │ │ ├── asymmetric.tsx │ │ │ │ ├── scrollable-page-content.tsx │ │ │ │ ├── symmetric.tsx │ │ │ │ └── transformation.tsx │ │ │ └── scroll │ │ │ │ └── extended.tsx │ │ └── styles │ │ │ └── globals.css │ ├── tailwind.config.ts │ ├── tests │ │ ├── client-side-routing.spec.ts │ │ └── window-resize-event.spec.ts │ ├── tsconfig.build.json │ └── tsconfig.json └── dflex-vue-playground │ ├── .eslintrc.cjs │ ├── .prettierrc.json │ ├── .vscode │ └── extensions.json │ ├── env.d.ts │ ├── index.html │ ├── package.json │ ├── public │ └── favicon.ico │ ├── src │ ├── App.vue │ ├── assets │ │ ├── base.css │ │ ├── logo.svg │ │ └── main.css │ ├── components │ │ ├── DFlexDnDComponent.vue │ │ ├── DnDList.vue │ │ ├── TheWelcome.vue │ │ ├── WelcomeItem.vue │ │ └── icons │ │ │ ├── IconCommunity.vue │ │ │ ├── IconDocumentation.vue │ │ │ ├── IconEcosystem.vue │ │ │ ├── IconSupport.vue │ │ │ └── IconTooling.vue │ ├── env.d.ts │ ├── main.ts │ ├── router │ │ └── index.ts │ └── views │ │ └── HomeView.vue │ ├── tsconfig.app.json │ ├── tsconfig.json │ ├── tsconfig.node.json │ └── vite.config.ts ├── playwright.config.ts ├── pnpm-lock.yaml ├── pnpm-workspace.yaml ├── scripts ├── build │ ├── index.js │ └── package.json ├── dflex-bundle-types │ ├── index.js │ └── package.json ├── eslint-config-dflex-react │ ├── config.eslintrc.js │ ├── index.js │ └── package.json ├── eslint-config-dflex │ ├── config.eslintrc.js │ ├── index.js │ └── package.json └── npm │ ├── index.js │ └── package.json ├── tsconfig.json └── tsconfig.test.json /.changeset/README.md: -------------------------------------------------------------------------------- 1 | # Changesets 2 | 3 | Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works 4 | with multi-package repos, or single-package repos to help you version and publish your code. You can 5 | find the full documentation for it [in our repository](https://github.com/changesets/changesets) 6 | 7 | We have a quick list of common questions to get you started engaging with this project in 8 | [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) 9 | -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.0.0/schema.json", 3 | "changelog": false, 4 | "commit": false, 5 | "fixed": [ 6 | [ 7 | "@dflex/utils", 8 | "@dflex/core-instance", 9 | "@dflex/dom-gen", 10 | "@dflex/store", 11 | "@dflex/draggable", 12 | "@dflex/dnd" 13 | ] 14 | ], 15 | "linked": [], 16 | "access": "public", 17 | "baseBranch": "main", 18 | "updateInternalDependencies": "patch", 19 | "ignore": [] 20 | } 21 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["eslint-config-dflex"], 3 | }; 4 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @jalal246 -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # To get started with Dependabot version updates, you'll need to specify which 2 | # package ecosystems to update and where the package manifests are located. 3 | # Please see the documentation for all configuration options: 4 | # https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates 5 | 6 | version: 2 7 | updates: 8 | - package-ecosystem: "npm" # See documentation for possible values 9 | directory: "/" # Location of package manifests 10 | schedule: 11 | interval: "monthly" 12 | 13 | - package-ecosystem: "github-actions" 14 | directory: "/" 15 | schedule: 16 | interval: "weekly" 17 | day: "sunday" 18 | -------------------------------------------------------------------------------- /.github/workflows/labeler.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request Labeler 2 | 3 | on: 4 | - pull_request_target 5 | # - pull_request # For testing 6 | 7 | jobs: 8 | triage: 9 | permissions: 10 | contents: read 11 | pull-requests: write 12 | runs-on: ubuntu-latest 13 | steps: 14 | - uses: actions/labeler@v5 15 | with: 16 | repo-token: "${{ secrets.GITHUB_TOKEN }}" 17 | -------------------------------------------------------------------------------- /.github/workflows/size-limit.yml: -------------------------------------------------------------------------------- 1 | name: "size" 2 | 3 | on: 4 | pull_request: 5 | branches: 6 | - main 7 | 8 | jobs: 9 | size-limit: 10 | runs-on: ubuntu-latest 11 | if: github.repository_owner == 'dflex-js' && github.event.pull_request.draft != true 12 | steps: 13 | - name: Check out repository 14 | uses: actions/checkout@v4 15 | 16 | - name: Cache pnpm modules 17 | uses: actions/cache@v4 18 | with: 19 | path: ~/.pnpm-store 20 | key: ${{ runner.os }}-${{ hashFiles('**/pnpm-lock.yaml') }} 21 | restore-keys: | 22 | ${{ runner.os }}- 23 | 24 | - uses: pnpm/action-setup@v4.0.0 25 | with: 26 | version: ^8.0.0 27 | run_install: true 28 | 29 | - uses: andresz1/size-limit-action@94bc357df29c36c8f8d50ea497c3e225c3c95d1d 30 | with: 31 | github_token: ${{ secrets.GITHUB_TOKEN }} 32 | package_manager: pnpm 33 | build_script: "build --production --release --minify" 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | coverage 2 | node_modules 3 | *.log 4 | .DS_Store 5 | 6 | # build 7 | dist 8 | types 9 | 10 | # TS composed 11 | tsconfig.tsbuildinfo 12 | tsconfig.build.tsbuildinfo 13 | 14 | # Cypress 15 | screenshots 16 | videos 17 | 18 | # next.js 19 | .next/ 20 | out/ 21 | 22 | # Disable auto generated changelog files generated by changeset 23 | CHANGELOG.md -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | link-workspace-packages = true 2 | prefer-workspace-packages = true 3 | recursive-install = true -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.formatOnSave": true, 3 | "editor.fontSize": 13, 4 | "eslint.packageManager": "yarn", 5 | "eslint.alwaysShowStatus": true, 6 | "eslint.debug": true, 7 | "typescript.tsdk": "node_modules/typescript/lib", 8 | "cSpell.words": ["dflex", "Jalal", "Maskoun", "pnpm", "VDOM", "Vite"] 9 | } 10 | -------------------------------------------------------------------------------- /DFlex-readme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dflex-js/dflex/031d202c72fd4a14a49c6444bdbeeb6baf7a72ca/DFlex-readme.png -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Jalal Maskoun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 10 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 11 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 12 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 13 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 14 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 15 | -------------------------------------------------------------------------------- /dflex-env.d.ts: -------------------------------------------------------------------------------- 1 | declare const __DEV__: boolean; 2 | 3 | declare global { 4 | // eslint-disable-next-line 5 | var $DFlex: DFlexDnDStore; 6 | } 7 | 8 | 9 | declare module "*.vue" { 10 | import Vue from "vue"; 11 | export default Vue; 12 | } -------------------------------------------------------------------------------- /dflex-github-cover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dflex-js/dflex/031d202c72fd4a14a49c6444bdbeeb6baf7a72ca/dflex-github-cover.png -------------------------------------------------------------------------------- /docs/draggable/api.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Draggable API 3 | description: "DFlex draggable installation and API" 4 | --- 5 | 6 | ## Installation 7 | 8 | ```bash 9 | npm install @dflex/draggable 10 | ``` 11 | 12 | ## API 13 | 14 | DFlex Draggable depends on three principles to achieve DOM interactivity: 15 | 16 | - Register element in the store. 17 | - Start dragging when mouse is down. 18 | - End dragging to release element when mouse is up. 19 | 20 | ```js 21 | import { store, Draggable } from "@dflex/draggable"; 22 | ``` 23 | 24 | ### Register element 25 | 26 | Each element should be registered in draggable store in order to be dragged later. 27 | 28 | ```ts 29 | store.register(id: string); 30 | ``` 31 | 32 | ### Create dragging instance 33 | 34 | The dragging instance should be created when `onmousedown` is fired. So you 35 | initialized the element before start dragging. 36 | 37 | ```ts 38 | const dflexDraggable = new Draggable(id, clickCoordinates); 39 | ``` 40 | 41 | - `id: string` registered element-id in the store. 42 | - `coordinate: AxesPoint` is an object with `{x: number, y: number}` contains the coordinates of the 43 | 44 | ### Start dragging 45 | 46 | ```ts 47 | dflexDraggable.dragAt(x, y); 48 | ``` 49 | 50 | - `x: number` is event.clientX, the horizontal click coordinate. 51 | - `y: number` is event.clientY, the vertical click coordinate. 52 | 53 | ### End dragging 54 | 55 | ```ts 56 | dflexDraggable.endDragging(); 57 | ``` 58 | 59 | ### Cleanup element 60 | 61 | It's necessary to cleanup the element from store when the element won't be used 62 | or will be removed/unmounted from the DOM to prevent memory leaks. 63 | 64 | ```ts 65 | store.unregister(id); 66 | ``` 67 | 68 | - `id: string` registered element-id. 69 | -------------------------------------------------------------------------------- /docs/draggable/examples.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: Draggable Example 3 | description: "DFlex draggable button example built with React triggered by moue event" 4 | --- 5 | 6 | Create draggable button with React. 7 | 8 | ```jsx 9 | import { store, draggable } from "@dflex/dnd"; 10 | 11 | let dflexDraggable: Draggable; 12 | 13 | const id = "DFlex-draggable-btn"; 14 | 15 | const DraggableButton = () => { 16 | const ref = React.createRef() as React.MutableRefObject; 17 | 18 | React.useEffect(() => { 19 | if (ref.current) { 20 | store.register(id); 21 | } 22 | 23 | return () => { 24 | store.unregister(id); 25 | }; 26 | }, [ref]); 27 | 28 | const onMouseMove = (e: MouseEvent) => { 29 | if (dflexDraggable) { 30 | const { clientX, clientY } = e; 31 | 32 | // Drag when mouse is moving! 33 | dflexDraggable.dragAt(clientX, clientY); 34 | } 35 | }; 36 | 37 | const onMouseUp = () => { 38 | if (dflexDraggable) { 39 | dflexDraggable.endDragging(); 40 | 41 | document.removeEventListener("mouseup", onMouseUp); 42 | document.removeEventListener("mousemove", onMouseMove); 43 | } 44 | }; 45 | 46 | const onMouseDown = (e: React.MouseEvent) => { 47 | const { button, clientX, clientY } = e; 48 | 49 | // Avoid right mouse click and ensure id 50 | if (typeof button === "number" && button === 0) { 51 | if (id) { 52 | dflexDraggable = new Draggable(id, { x: clientX, y: clientY }); 53 | 54 | document.addEventListener("mouseup", onMouseUp); 55 | document.addEventListener("mousemove", onMouseMove); 56 | } 57 | } 58 | }; 59 | 60 | return ( 61 | 70 | ); 71 | }; 72 | ``` 73 | -------------------------------------------------------------------------------- /docs/nav.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "category": "Drag & Drop", 4 | "id": "drag-drop", 5 | "content": [ 6 | { "title": "API", "id": "api" }, 7 | { "title": "Example", "id": "examples" }, 8 | { 9 | "title": "Implementations", 10 | "id": "implementations" 11 | } 12 | ] 13 | }, 14 | { 15 | "category": "Draggable Only", 16 | "id": "draggable", 17 | "content": [ 18 | { "title": "API", "id": "api" }, 19 | { "title": "Example", "id": "examples" }, 20 | { 21 | "title": "Implementations", 22 | "id": "implementations" 23 | } 24 | ] 25 | }, 26 | { 27 | "category": "DOM Utilities", 28 | "id": "utils", 29 | "content": [ 30 | { "title": "DOM Generator", "id": "dom-gen" }, 31 | { "title": "DOM Store", "id": "dom-store" } 32 | ] 33 | } 34 | ] 35 | -------------------------------------------------------------------------------- /jest.config.ts: -------------------------------------------------------------------------------- 1 | import type { JestConfigWithTsJest } from "ts-jest"; 2 | 3 | const tsJestConfig: JestConfigWithTsJest = { 4 | clearMocks: true, 5 | moduleFileExtensions: ["js", "ts", "tsx", "json"], 6 | globals: { 7 | fakeTimers: { enableGlobally: true }, 8 | __DEV__: true, 9 | // https://github.com/testing-library/react-testing-library/issues/1061#issuecomment-1117450890 10 | IS_REACT_ACT_ENVIRONMENT: true, 11 | }, 12 | moduleNameMapper: { 13 | "^./dist/(.+)": "./src/$1", 14 | "^@dflex/utils$": "/packages/dflex-utils/src/index.ts", 15 | "^@dflex/dom-gen$": "/packages/dflex-dom-gen/src/index.ts", 16 | "^@dflex/core-instance$": 17 | "/packages/dflex-core-instance/src/index.ts", 18 | "^@dflex/store$": "/packages/dflex-store/src/index.ts", 19 | "^@dflex/draggable$": "/packages/dflex-draggable/src/index.ts", 20 | "^@dflex/dnd$": "/packages/dflex-dnd/src/index.ts", 21 | }, 22 | testEnvironment: "jsdom", 23 | testPathIgnorePatterns: ["cypress"], 24 | transform: { 25 | "^.+\\.(ts|tsx)?$": [ 26 | "ts-jest", 27 | { 28 | tsconfig: "tsconfig.test.json", 29 | }, 30 | ], 31 | }, 32 | testMatch: ["**/test/**/*.test{.ts,.tsx,.js,.jsx}"], 33 | }; 34 | 35 | export default tsJestConfig; 36 | -------------------------------------------------------------------------------- /packages/dflex-core-instance/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["eslint-config-dflex"], 3 | rules: { 4 | "import/no-extraneous-dependencies": [ 5 | "error", 6 | { 7 | packageDir: __dirname, 8 | }, 9 | ], 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/dflex-core-instance/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022, Jalal Maskoun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/dflex-core-instance/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | DFlex is a Javascript library for modern Drag and Drop apps 6 | 7 |

8 | 9 |

DFlex Core Instance

10 | 11 |

12 | 13 | Dflex build status 16 | 17 | 18 | number of opened pull requests 21 | 22 | 23 | DFlex last released version 26 | 27 | 28 | number of opened issues 31 | 32 | 33 | Dflex welcomes pull request 36 | 37 | 38 | Follow DFlex on twitter 41 | 42 |

43 | 44 | # @dflex/core-instance 45 | 46 | Core instance is an internal DFlex package. 47 | 48 | ## Documentation 📖 49 | 50 | For documentation, more information about DFlex and a live demo, be sure to visit the DFlex website 51 | 52 | ## License 🤝 53 | 54 | DFlex is [MIT License](LICENSE). 55 | -------------------------------------------------------------------------------- /packages/dflex-core-instance/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dflex/core-instance", 3 | "version": "3.10.6", 4 | "description": "Core instance is a DFlex package for interactive DOM element", 5 | "author": "Jalal Maskoun", 6 | "main": "./dist/dflex-core.js", 7 | "module": "./dist/dflex-core.mjs", 8 | "types": "./types/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "development": { 12 | "import": "./dist/dev.mjs", 13 | "require": "./dist/dev.js" 14 | }, 15 | "require": "./dist/dflex-core.js", 16 | "import": "./dist/dflex-core.mjs", 17 | "types": "./types/index.d.ts" 18 | }, 19 | "./dist/*": "./dist/*" 20 | }, 21 | "scripts": { 22 | "clean": "rimraf ./dist ./types tsconfig.tsbuildinfo", 23 | "emit": "tsc --emitDeclarationOnly" 24 | }, 25 | "repository": "https://github.com/dflex-js/dflex", 26 | "homepage": "https://github.com/dflex-js/dflex/tree/main/packages/dflex-core-instance", 27 | "license": "MIT", 28 | "files": [ 29 | "dist", 30 | "types", 31 | "LICENSE" 32 | ], 33 | "devDependencies": { 34 | "@dflex/utils": "workspace:^3.10.6" 35 | }, 36 | "keywords": [ 37 | "drag-drop", 38 | "dnd", 39 | "sortable", 40 | "reorder", 41 | "drag", 42 | "drop", 43 | "DOM", 44 | "@dflex", 45 | "@dflex/dom-gen", 46 | "@dflex/store", 47 | "@dflex/core-instance", 48 | "@dflex/draggable", 49 | "@dflex/dnd" 50 | ], 51 | "publishConfig": { 52 | "registry": "https://registry.npmjs.org/", 53 | "access": "public" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/dflex-core-instance/src/Container/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DFlexParentContainer } from "./DFlexParentContainer"; 2 | export { default as DFlexScrollContainer } from "./DFlexScrollContainer"; 3 | -------------------------------------------------------------------------------- /packages/dflex-core-instance/src/Container/scrollUtils/DFlexOverflow.ts: -------------------------------------------------------------------------------- 1 | import { getElmOverflow, Axis, getElmPos } from "@dflex/utils"; 2 | 3 | const OVERFLOW_REGEX = /(auto|scroll|overlay)/; 4 | 5 | type CSSOverflow = ReturnType; 6 | export type CSSPosition = ReturnType; 7 | 8 | function isScrollPropagationAllowed( 9 | parentDOM: HTMLElement, 10 | baseELmPosition: CSSPosition, 11 | ): boolean { 12 | if ( 13 | baseELmPosition === "fixed" || 14 | (baseELmPosition === "absolute" && getElmPos(parentDOM) === "static") 15 | ) { 16 | return false; 17 | } 18 | 19 | return true; 20 | } 21 | 22 | function hasScrollableContent(axis: Axis, parentDOM: HTMLElement): boolean { 23 | return axis === "y" 24 | ? parentDOM.scrollHeight > parentDOM.clientHeight 25 | : parentDOM.scrollWidth > parentDOM.clientWidth; 26 | } 27 | 28 | function hasOverflowProperty(axis: Axis, parentDOM: HTMLElement): boolean { 29 | const overflow: CSSOverflow = getElmOverflow( 30 | parentDOM, 31 | axis === "x" ? "overflow-x" : "overflow-y", 32 | ); 33 | 34 | return OVERFLOW_REGEX.test(overflow); 35 | } 36 | 37 | function isOverflowing(axis: Axis, parentDOM: HTMLElement): boolean { 38 | if (hasOverflowProperty(axis, parentDOM)) { 39 | return hasScrollableContent(axis, parentDOM); 40 | } 41 | 42 | return false; 43 | } 44 | 45 | export { isOverflowing, hasScrollableContent, isScrollPropagationAllowed }; 46 | -------------------------------------------------------------------------------- /packages/dflex-core-instance/src/Container/scrollUtils/DFlexScrollProps.ts: -------------------------------------------------------------------------------- 1 | import { getParentElm, getElmPos, PointBool } from "@dflex/utils"; 2 | 3 | import { isOverflowing, isScrollPropagationAllowed } from "./DFlexOverflow"; 4 | 5 | import type { CSSPosition } from "./DFlexOverflow"; 6 | 7 | type ScrollProps = [HTMLElement, boolean]; 8 | 9 | function calculateOverflow( 10 | parentDOM: HTMLElement, 11 | baseELmPosition: CSSPosition, 12 | hasOverflow: PointBool, 13 | ): boolean { 14 | const isPropagated = isScrollPropagationAllowed(parentDOM, baseELmPosition); 15 | 16 | if (!isPropagated) { 17 | return false; 18 | } 19 | 20 | hasOverflow.x = isOverflowing("x", parentDOM); 21 | hasOverflow.y = isOverflowing("y", parentDOM); 22 | 23 | return hasOverflow.isOneTruthy(); 24 | } 25 | 26 | function getScrollProps( 27 | baseDOMElm: HTMLElement | null, 28 | hasOverflow: PointBool, 29 | ): ScrollProps { 30 | const scrollProps: ScrollProps = [document.documentElement, true]; 31 | 32 | if (__DEV__) { 33 | Object.seal(scrollProps); 34 | } 35 | 36 | if (!baseDOMElm) { 37 | return scrollProps; 38 | } 39 | 40 | const baseELmPosition = getElmPos(baseDOMElm); 41 | 42 | const overflow = (parentDOM: HTMLElement) => 43 | calculateOverflow(parentDOM, baseELmPosition, hasOverflow); 44 | 45 | const scrollContainerDOM = getParentElm(baseDOMElm, overflow); 46 | 47 | if (scrollContainerDOM) { 48 | scrollProps[0] = scrollContainerDOM; 49 | scrollProps[1] = false; 50 | } 51 | 52 | if (__DEV__) { 53 | Object.freeze(scrollProps); 54 | } 55 | 56 | return scrollProps; 57 | } 58 | 59 | export default getScrollProps; 60 | -------------------------------------------------------------------------------- /packages/dflex-core-instance/src/Container/scrollUtils/index.ts: -------------------------------------------------------------------------------- 1 | export { hasScrollableContent } from "./DFlexOverflow"; 2 | export { default as getScrollProps } from "./DFlexScrollProps"; 3 | -------------------------------------------------------------------------------- /packages/dflex-core-instance/src/Element/DFlexBaseElement.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | import { PointNum, setStyleProperty } from "@dflex/utils"; 3 | 4 | const TRANSFORM = "transform"; 5 | 6 | function transform(DOM: HTMLElement, x: number, y: number): void { 7 | setStyleProperty(DOM, TRANSFORM, `translate3d(${x}px, ${y}px, 0)`); 8 | } 9 | 10 | class DFlexBaseElement { 11 | id: string; 12 | 13 | translate: PointNum; 14 | 15 | static getType(): string { 16 | return "base:element"; 17 | } 18 | 19 | static transform = transform; 20 | 21 | constructor(id: string) { 22 | this.id = id; 23 | this.translate = new PointNum(0, 0); 24 | } 25 | } 26 | 27 | export default DFlexBaseElement; 28 | -------------------------------------------------------------------------------- /packages/dflex-core-instance/src/Element/DFlexElement.ts: -------------------------------------------------------------------------------- 1 | import type { Axis, AxesPoint } from "@dflex/utils"; 2 | import DFlexCoreNode from "./DFlexCoreElement"; 3 | 4 | // TODO: Remove this class and depend entirely on Box/Rect classes instead. 5 | class DFlexElement extends DFlexCoreNode { 6 | static getDistance( 7 | currentPosition: AxesPoint, 8 | elm: DFlexElement, 9 | axis: Axis, 10 | ) { 11 | let diff = currentPosition[axis] - elm.rect[axis === "x" ? "left" : "top"]; 12 | 13 | diff += elm.translate![axis]; 14 | 15 | return diff; 16 | } 17 | 18 | static getDisplacement( 19 | currentPosition: AxesPoint, 20 | elm: DFlexElement, 21 | axis: Axis, 22 | ) { 23 | const diff = axis === "x" ? elm.rect.right : elm.rect.bottom; 24 | 25 | return currentPosition[axis] - diff; 26 | } 27 | 28 | getDisplacement(elm: this, axis: Axis): number { 29 | return DFlexElement.getDisplacement(this.rect.getPosition(), elm, axis); 30 | } 31 | 32 | getDistance(elm: this, axis: Axis): number { 33 | return DFlexElement.getDistance(this.rect.getPosition(), elm, axis); 34 | } 35 | } 36 | 37 | export default DFlexElement; 38 | -------------------------------------------------------------------------------- /packages/dflex-core-instance/src/Element/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DFlexElement } from "./DFlexElement"; 2 | export { default as DFlexBaseElement } from "./DFlexBaseElement"; 3 | 4 | export type { 5 | DFlexSerializedElement, 6 | DFlexElementInput, 7 | DFlexDOMGenOrder, 8 | } from "./DFlexCoreElement"; 9 | -------------------------------------------------------------------------------- /packages/dflex-core-instance/src/index.ts: -------------------------------------------------------------------------------- 1 | export type { 2 | DFlexSerializedElement, 3 | DFlexElementInput, 4 | DFlexDOMGenOrder, 5 | } from "./Element"; 6 | 7 | export { DFlexElement, DFlexBaseElement } from "./Element"; 8 | 9 | export { DFlexParentContainer, DFlexScrollContainer } from "./Container"; 10 | -------------------------------------------------------------------------------- /packages/dflex-core-instance/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../dflex-env.d.ts"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "outDir": "./types", 7 | "rootDir": "src/" 8 | }, 9 | "references": [{ "path": "../dflex-utils" }] 10 | } 11 | -------------------------------------------------------------------------------- /packages/dflex-dnd/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["eslint-config-dflex"], 3 | rules: { 4 | "import/no-extraneous-dependencies": [ 5 | "error", 6 | { 7 | packageDir: __dirname, 8 | }, 9 | ], 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/dflex-dnd/.gitignore: -------------------------------------------------------------------------------- 1 | cypress/screenshots 2 | cypress/videos 3 | .nyc_output 4 | site -------------------------------------------------------------------------------- /packages/dflex-dnd/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022, Jalal Maskoun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/dflex-dnd/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dflex/dnd", 3 | "version": "3.10.6", 4 | "description": "Modern drag and drop package for all JavaScript frameworks", 5 | "author": "Jalal Maskoun", 6 | "main": "./dist/dflex-dnd.js", 7 | "module": "./dist/dflex-dnd.mjs", 8 | "types": "./types/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "development": { 12 | "import": "./dist/dev.mjs", 13 | "require": "./dist/dev.js" 14 | }, 15 | "require": "./dist/dflex-dnd.js", 16 | "import": "./dist/dflex-dnd.mjs", 17 | "types": "./types/index.d.ts" 18 | }, 19 | "./dist/*": "./dist/*" 20 | }, 21 | "scripts": { 22 | "clean": "rimraf ./dist ./types tsconfig.tsbuildinfo", 23 | "emit": "tsc --emitDeclarationOnly", 24 | "compile:w": "tsc -b -w" 25 | }, 26 | "homepage": "https://github.com/dflex-js/dflex/tree/main/packages/dflex-dnd", 27 | "repository": "https://github.com/dflex-js/dflex", 28 | "license": "MIT", 29 | "files": [ 30 | "dist", 31 | "types", 32 | "LICENSE" 33 | ], 34 | "devDependencies": { 35 | "@dflex/core-instance": "workspace:^3.10.6", 36 | "@dflex/draggable": "workspace:^3.10.6", 37 | "@dflex/store": "workspace:^3.10.6", 38 | "@dflex/utils": "workspace:^3.10.6" 39 | }, 40 | "keywords": [ 41 | "drag-drop", 42 | "dnd", 43 | "sortable", 44 | "reorder", 45 | "drag", 46 | "drop", 47 | "DOM", 48 | "@dflex", 49 | "@dflex/dom-gen", 50 | "@dflex/store", 51 | "@dflex/core-instance", 52 | "@dflex/draggable", 53 | "@dflex/dnd" 54 | ], 55 | "publishConfig": { 56 | "registry": "https://registry.npmjs.org/", 57 | "access": "public" 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /packages/dflex-dnd/public/dnd.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dflex-js/dflex/031d202c72fd4a14a49c6444bdbeeb6baf7a72ca/packages/dflex-dnd/public/dnd.gif -------------------------------------------------------------------------------- /packages/dflex-dnd/public/dnd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dflex-js/dflex/031d202c72fd4a14a49c6444bdbeeb6baf7a72ca/packages/dflex-dnd/public/dnd.png -------------------------------------------------------------------------------- /packages/dflex-dnd/src/DnD.ts: -------------------------------------------------------------------------------- 1 | import type { AxesPoint } from "@dflex/utils"; 2 | 3 | import DraggableInteractive from "./Draggable"; 4 | import Mechanism from "./Mechanism"; 5 | import { store } from "./LayoutManager"; 6 | 7 | import type { DFlexDnDOpts, FinalDndOpts } from "./types"; 8 | 9 | import { extractOpts } from "./utils/extractOpts"; 10 | import { defaultOpts } from "./utils/constants"; 11 | 12 | class DnD extends Mechanism { 13 | /** 14 | * 15 | * @param id - 16 | * @param initCoordinates - 17 | * @param opts - 18 | */ 19 | constructor( 20 | id: string, 21 | initCoordinates: AxesPoint, 22 | opts: DFlexDnDOpts = defaultOpts, 23 | ) { 24 | if (__DEV__) { 25 | if (!store.registry.has(id)) { 26 | throw new Error(`DFlex: ${id} is not registered in the Store.`); 27 | } 28 | } 29 | 30 | const options = extractOpts(opts); 31 | 32 | if (__DEV__) { 33 | Object.freeze(options); 34 | } 35 | 36 | const draggable = new DraggableInteractive( 37 | id, 38 | initCoordinates, 39 | options as FinalDndOpts, 40 | ); 41 | 42 | super(draggable); 43 | } 44 | } 45 | 46 | export default DnD; 47 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/Draggable/DraggablePositions/DraggablePositions.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AbstractBoxRect, 3 | AxesPoint, 4 | Axis, 5 | BoxNum, 6 | BoxRect, 7 | PointNum, 8 | } from "@dflex/utils"; 9 | 10 | import CurrentPosition from "./DraggedCurrentPosition"; 11 | import type { MovementDirection } from "./types"; 12 | 13 | class DraggablePositions { 14 | private _absolute: CurrentPosition; 15 | 16 | private _viewport: CurrentPosition; 17 | 18 | constructor( 19 | initCoordinates: AxesPoint, 20 | rect: AbstractBoxRect, 21 | totalScrollRect: BoxRect, 22 | ) { 23 | this._viewport = new CurrentPosition( 24 | initCoordinates, 25 | rect, 26 | totalScrollRect, 27 | ); 28 | 29 | this._absolute = new CurrentPosition(initCoordinates, rect, { 30 | left: 0, 31 | top: 0, 32 | }); 33 | } 34 | 35 | setPos( 36 | x: number, 37 | y: number, 38 | scrollOffsetX: number, 39 | scrollOffsetY: number, 40 | ): void { 41 | this._viewport.setPos(x, y, 0, 0); 42 | this._absolute.setPos(x, y, scrollOffsetX, scrollOffsetY); 43 | } 44 | 45 | private _getInstance(isAbsolute: boolean): CurrentPosition { 46 | const $ = isAbsolute ? this._absolute : this._viewport; 47 | 48 | return $; 49 | } 50 | 51 | getPos(isAbsolute: boolean): BoxNum { 52 | return this._getInstance(isAbsolute).getPos(); 53 | } 54 | 55 | getInnerOffset(isAbsolute: boolean): PointNum { 56 | return this._getInstance(isAbsolute).getInnerOffset(); 57 | } 58 | 59 | getMovementDirection(axis: Axis, isAbsolute: boolean): MovementDirection { 60 | return this._getInstance(isAbsolute).getMovementDirection(axis); 61 | } 62 | } 63 | 64 | export default DraggablePositions; 65 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/Draggable/DraggablePositions/index.ts: -------------------------------------------------------------------------------- 1 | import DraggablePositions from "./DraggablePositions"; 2 | 3 | export default DraggablePositions; 4 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/Draggable/DraggablePositions/types.ts: -------------------------------------------------------------------------------- 1 | export type MovementDirection = "r" | "l" | "d" | "u"; 2 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/Draggable/index.ts: -------------------------------------------------------------------------------- 1 | import DraggableInteractive from "./DraggableInteractive"; 2 | 3 | export default DraggableInteractive; 4 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/Events/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DFlexEvent } from "./DFlexEvents"; 2 | 3 | export { 4 | emitInteractivityEvent, 5 | emitSiblingsEvent, 6 | emitDragMovedEvent, 7 | emitDragCommittedEvent, 8 | } from "./emitters"; 9 | 10 | export { DFLEX_EVENTS, DFLEX_EVENTS_CAT, DFLEX_ATTRS } from "./constants"; 11 | 12 | export type { 13 | InteractivityEventNames, 14 | DFlexDraggedEvent, 15 | DFlexInteractivityEvent, 16 | DFlexSiblingsEvent, 17 | DFlexEvents, 18 | DFlexEventNames, 19 | DFlexEventsMap, 20 | } from "./types"; 21 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/LayoutManager/DFlexDnDStoreSingleton.ts: -------------------------------------------------------------------------------- 1 | import { canUseDOM } from "@dflex/utils"; 2 | import DFlexDnDStore from "./DFlexDnDStore"; 3 | 4 | declare global { 5 | // eslint-disable-next-line 6 | var $DFlex: DFlexDnDStore; 7 | } 8 | 9 | export default (function createStoreInstance() { 10 | const store = new DFlexDnDStore(); 11 | 12 | if (__DEV__) { 13 | if (canUseDOM()) { 14 | if (!globalThis.$DFlex) { 15 | globalThis.$DFlex = store; 16 | } 17 | } 18 | } 19 | 20 | return store; 21 | })(); 22 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/LayoutManager/index.ts: -------------------------------------------------------------------------------- 1 | export { default as store } from "./DFlexDnDStoreSingleton"; 2 | export { default as DFlexDnDExportedStore } from "./DFlexDnDExportedStore"; 3 | export { default as scheduler } from "./DFlexScheduler"; 4 | export { addObserver as initMutationObserver } from "../Mutation"; 5 | 6 | export type { default as DFlexDnDStore } from "./DFlexDnDStore"; 7 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/Listeners/DFlexListeners.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-unused-vars */ 2 | 3 | import type { DFlexListenerNotifications } from "./types"; 4 | 5 | type ListenerTypes = 6 | DFlexListenerNotifications[keyof DFlexListenerNotifications]; 7 | 8 | type ListenerFunction = (event: DFlexListenerNotifications) => void; 9 | 10 | type ListenersMap = Map>; 11 | 12 | type CleanupFunction = () => void; 13 | 14 | const eventListeners: ListenersMap = new Map(); 15 | 16 | function subscribe( 17 | callback: ListenerFunction, 18 | eventType: ListenerTypes, 19 | ): CleanupFunction { 20 | if (!eventListeners.has(eventType)) { 21 | eventListeners.set(eventType, new Set()); 22 | } 23 | 24 | const listenersStateSet = eventListeners.get(eventType)!; 25 | 26 | listenersStateSet.add(callback); 27 | 28 | return () => { 29 | listenersStateSet.delete(callback); 30 | }; 31 | } 32 | 33 | function notify(event: DFlexListenerNotifications): void { 34 | const { type } = event; 35 | 36 | const listeners = eventListeners.get(type); 37 | 38 | if (!listeners) { 39 | return; 40 | } 41 | 42 | listeners.forEach((callback) => callback(event)); 43 | } 44 | 45 | function clear(): void { 46 | eventListeners.forEach((eventListenersSet) => eventListenersSet.clear()); 47 | eventListeners.clear(); 48 | } 49 | 50 | function DFlexListeners() { 51 | return { 52 | subscribe, 53 | notify, 54 | clear, 55 | }; 56 | } 57 | 58 | export default DFlexListeners; 59 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/Listeners/constants.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Layout State: 3 | * - 'pending': when DnD is initiated but not activated yet. 4 | */ 5 | const PENDING = "pending"; 6 | 7 | /** 8 | * Layout State: 9 | * - 'ready': When clicking over the registered element. The element is ready but not being dragged. 10 | */ 11 | const READY = "ready"; 12 | 13 | /** 14 | * Layout State: 15 | * - 'dragging': as expected. 16 | */ 17 | const DRAGGING = "dragging"; 18 | 19 | /** 20 | * Layout State: 21 | * - 'dragEnd': as expected. 22 | */ 23 | const DRAG_END = "dragEnd"; 24 | 25 | /** 26 | * Layout State: 27 | * - 'dragCancel': When releasing the drag without settling in the new position. 28 | */ 29 | const DRAG_CANCEL = "dragCancel"; 30 | 31 | const LAYOUT_STATES = { 32 | PENDING, 33 | READY, 34 | DRAGGING, 35 | DRAG_END, 36 | DRAG_CANCEL, 37 | } as const; 38 | 39 | const LAYOUT_CAT = "layoutState"; 40 | const MUTATION_CAT = "mutation"; 41 | const ERROR_CAT = "error"; 42 | 43 | const DFLEX_LISTENERS_CAT = { 44 | LAYOUT_CAT, 45 | MUTATION_CAT, 46 | ERROR_CAT, 47 | } as const; 48 | 49 | const { freeze } = Object; 50 | 51 | freeze(LAYOUT_STATES); 52 | freeze(DFLEX_LISTENERS_CAT); 53 | 54 | export { LAYOUT_STATES, DFLEX_LISTENERS_CAT }; 55 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/Listeners/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DFlexListeners } from "./DFlexListeners"; 2 | 3 | export { DFLEX_LISTENERS_CAT, LAYOUT_STATES } from "./constants"; 4 | 5 | export type { 6 | DFlexListenerNotifications, 7 | DFlexLayoutStateNotification, 8 | DFlexMutationNotification, 9 | } from "./types"; 10 | 11 | export { 12 | notifyLayoutStateListeners, 13 | notifyMutationListeners, 14 | } from "./notifications"; 15 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/Listeners/notifications.ts: -------------------------------------------------------------------------------- 1 | import { DFLEX_LISTENERS_CAT } from "./constants"; 2 | 3 | import { 4 | DFlexLayoutStateNotification, 5 | DFlexMutationNotification, 6 | } from "./types"; 7 | 8 | import type DFlexListeners from "./DFlexListeners"; 9 | 10 | const { LAYOUT_CAT, MUTATION_CAT } = DFLEX_LISTENERS_CAT; 11 | 12 | function notifyLayoutStateListeners( 13 | listeners: ReturnType, 14 | status: DFlexLayoutStateNotification["status"], 15 | ): void { 16 | listeners.notify({ 17 | type: LAYOUT_CAT, 18 | status, 19 | }); 20 | } 21 | 22 | function notifyMutationListeners( 23 | listeners: ReturnType, 24 | ids: DFlexMutationNotification["payload"]["ids"], 25 | target: DFlexMutationNotification["payload"]["target"], 26 | ): void { 27 | listeners.notify({ 28 | type: MUTATION_CAT, 29 | payload: { 30 | ids, 31 | target, 32 | }, 33 | }); 34 | } 35 | 36 | export { notifyLayoutStateListeners, notifyMutationListeners }; 37 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/Listeners/types.ts: -------------------------------------------------------------------------------- 1 | import { DFLEX_LISTENERS_CAT, LAYOUT_STATES } from "./constants"; 2 | 3 | export type LayoutState = (typeof LAYOUT_STATES)[keyof typeof LAYOUT_STATES]; 4 | 5 | type PayloadMutation = { 6 | /** 7 | * Represents the container where the element is now located after the 8 | * mutation. 9 | * */ 10 | target: HTMLElement; 11 | 12 | /** 13 | * Contains an ordered list of IDs representing the committed elements. 14 | * These IDs are listed in the sequence in which the elements were committed. 15 | */ 16 | ids: string[]; 17 | }; 18 | 19 | export type DFlexLayoutStateNotification = { 20 | type: typeof DFLEX_LISTENERS_CAT.LAYOUT_CAT; 21 | 22 | /** The current status of the layout. */ 23 | status: LayoutState; 24 | }; 25 | 26 | export type DFlexMutationNotification = { 27 | type: typeof DFLEX_LISTENERS_CAT.MUTATION_CAT; 28 | payload: PayloadMutation; 29 | }; 30 | 31 | export type DFlexErrorNotification = { 32 | type: typeof DFLEX_LISTENERS_CAT.ERROR_CAT; 33 | error: unknown; 34 | }; 35 | 36 | export type DFlexListenerNotifications = 37 | | DFlexLayoutStateNotification 38 | | DFlexMutationNotification 39 | | DFlexErrorNotification; 40 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/Mechanism/DFlexScrollTransition.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Axis, 3 | Direction, 4 | DFlexCreateRAF, 5 | getStartingPointByAxis, 6 | } from "@dflex/utils"; 7 | 8 | import type { DFlexScrollContainer } from "@dflex/core-instance"; 9 | 10 | // eslint-disable-next-line no-unused-vars 11 | type Accelerator = (progress: number) => number; 12 | 13 | export type ScrollTransitionAbort = () => void; 14 | 15 | function DFlexScrollTransition( 16 | scroll: DFlexScrollContainer, 17 | axis: Axis, 18 | direction: Direction, 19 | duration: number | null, 20 | accelerator: Accelerator, 21 | onComplete: () => void, 22 | onAbort: () => void, 23 | ): ScrollTransitionAbort { 24 | let startScroll: number; 25 | let distance: number; 26 | 27 | let calculatedDuration = duration || 0; 28 | 29 | let startTime: number; 30 | let aborted = false; 31 | 32 | const [RAF, cancelRAF] = DFlexCreateRAF(); 33 | 34 | const step = (timestamp: number) => { 35 | if (!startTime) { 36 | startTime = timestamp; 37 | startScroll = scroll.totalScrollRect[getStartingPointByAxis(axis)]; 38 | distance = scroll.calculateDistance(axis, direction); 39 | 40 | calculatedDuration = calculatedDuration || Math.sqrt(distance) * 75; 41 | } 42 | 43 | const elapsed = timestamp - startTime; 44 | const progress = Math.min(elapsed / calculatedDuration, 1); 45 | 46 | const easedProgress = accelerator(progress); 47 | 48 | const scrollPosition = startScroll + direction * (distance * easedProgress); 49 | 50 | if (axis === "x") { 51 | scroll.scrollTo(scrollPosition, -1); 52 | } else { 53 | scroll.scrollTo(-1, scrollPosition); 54 | } 55 | 56 | if (!aborted && progress < 1) { 57 | RAF(step, false); 58 | } else if (onComplete) { 59 | onComplete(); 60 | } 61 | }; 62 | 63 | const abortScroll = () => { 64 | aborted = true; 65 | cancelRAF(); 66 | onAbort(); 67 | }; 68 | 69 | RAF(step, false); 70 | 71 | return abortScroll; 72 | } 73 | 74 | export default DFlexScrollTransition; 75 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/Mechanism/index.ts: -------------------------------------------------------------------------------- 1 | import EndCycle from "./EndCycle"; 2 | 3 | export default EndCycle; 4 | 5 | // For test only. 6 | export { getInsertionELmMeta } from "./DFlexPositionUpdater"; 7 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/Mutation/DFlexDirtyLeavesCollector.ts: -------------------------------------------------------------------------------- 1 | import { featureFlags } from "@dflex/utils"; 2 | import DFlexDnDStore from "../LayoutManager/DFlexDnDStore"; 3 | import { 4 | DFlexIDGarbageCollector, 5 | TerminatedDOMiDs, 6 | } from "./DFlexIDGarbageCollector"; 7 | 8 | function DFlexDirtyLeavesCollector(store: DFlexDnDStore, depth: number) { 9 | const terminatedDOMiDs: TerminatedDOMiDs = new Set(); 10 | 11 | store.interactiveDOM.forEach((DOM, id) => { 12 | if (!DOM.isConnected) { 13 | if (store.registry.get(id)!.depth === depth) { 14 | terminatedDOMiDs.add(id); 15 | } 16 | } 17 | }); 18 | 19 | if (terminatedDOMiDs.size === 0) { 20 | return; 21 | } 22 | 23 | if (__DEV__) { 24 | if (featureFlags.enableRegisterDebugger) { 25 | // eslint-disable-next-line no-console 26 | console.log( 27 | `Found ${terminatedDOMiDs.size} dirty leaves`, 28 | terminatedDOMiDs, 29 | ); 30 | } 31 | } 32 | 33 | DFlexIDGarbageCollector(store, terminatedDOMiDs); 34 | } 35 | 36 | export default DFlexDirtyLeavesCollector; 37 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/Mutation/DFlexIDModifier.ts: -------------------------------------------------------------------------------- 1 | import type DFlexDnDStore from "../LayoutManager/DFlexDnDStore"; 2 | 3 | export type ChangedIds = Set<{ oldId: string; newId: string }>; 4 | 5 | function DFlexIDModifier(store: DFlexDnDStore, changedIds: ChangedIds): void { 6 | changedIds.forEach((idSet) => { 7 | if (store.registry.has(idSet.oldId)) { 8 | const elm = store.registry.get(idSet.oldId)!; 9 | const elmBranch = store.getElmSiblingsByKey(elm.keys.SK); 10 | 11 | // Update registry. 12 | store.registry.set(idSet.newId, elm); 13 | store.registry.delete(idSet.oldId); 14 | 15 | // Update DOM-gen branch. 16 | elmBranch[elm.VDOMOrder.self] = idSet.newId; 17 | 18 | // Update instance. 19 | elm.id = idSet.newId; 20 | } 21 | }); 22 | } 23 | 24 | export default DFlexIDModifier; 25 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/Mutation/index.ts: -------------------------------------------------------------------------------- 1 | export type { DFlexLMutationPlugin } from "./DFlexMutations"; 2 | 3 | export { default as DFlexDirtyLeavesCollector } from "./DFlexDirtyLeavesCollector"; 4 | 5 | export { 6 | DFlexIDGarbageCollector, 7 | hasGCInProgress, 8 | } from "./DFlexIDGarbageCollector"; 9 | 10 | export type { TerminatedDOMiDs } from "./DFlexIDGarbageCollector"; 11 | 12 | export { 13 | hasMutationsInProgress, 14 | addObserver, 15 | disconnectObservers, 16 | connectObservers, 17 | } from "./DFlexMutations"; 18 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/exportedStoreSingleton.ts: -------------------------------------------------------------------------------- 1 | import { DFlexDnDExportedStore } from "./LayoutManager"; 2 | 3 | export default (function createStoreInstance() { 4 | const store = new DFlexDnDExportedStore(); 5 | 6 | return store; 7 | })(); 8 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as store } from "./exportedStoreSingleton"; 2 | 3 | export { default as DnD } from "./DnD"; 4 | 5 | export type { DFlexDnDOpts } from "./types"; 6 | 7 | export { DFLEX_LISTENERS_CAT } from "./Listeners"; 8 | 9 | export type { 10 | DFlexLayoutStateNotification, 11 | DFlexMutationNotification, 12 | DFlexListenerNotifications, 13 | } from "./Listeners"; 14 | 15 | export { DFLEX_EVENTS, DFLEX_EVENTS_CAT, DFLEX_ATTRS } from "./Events"; 16 | 17 | export type { 18 | DFlexDraggedEvent, 19 | DFlexInteractivityEvent, 20 | DFlexSiblingsEvent, 21 | DFlexEvents, 22 | DFlexEventNames, 23 | } from "./Events"; 24 | 25 | export type { 26 | DFlexScrollContainer, 27 | DFlexSerializedElement, 28 | DFlexDOMGenOrder, 29 | } from "@dflex/core-instance"; 30 | 31 | export type { RegisterInputOpts, DFlexGlobalConfig } from "@dflex/store"; 32 | 33 | export type { 34 | AxesPoint, 35 | AbstractBoxRect as BoxRectAbstract, 36 | PointNum, 37 | AbstractBox, 38 | Dimensions, 39 | } from "@dflex/utils"; 40 | -------------------------------------------------------------------------------- /packages/dflex-dnd/src/utils/constants.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | import type { DefaultDndOpts } from "../types"; 3 | 4 | export const defaultOpts: DefaultDndOpts = Object.freeze({ 5 | containersTransition: { 6 | enable: true, 7 | margin: 10, 8 | }, 9 | 10 | threshold: { 11 | vertical: 60, 12 | horizontal: 60, 13 | }, 14 | 15 | restrictions: { 16 | self: { 17 | allowLeavingFromTop: true, 18 | allowLeavingFromBottom: true, 19 | allowLeavingFromLeft: true, 20 | allowLeavingFromRight: true, 21 | }, 22 | container: { 23 | allowLeavingFromTop: true, 24 | allowLeavingFromBottom: true, 25 | allowLeavingFromLeft: true, 26 | allowLeavingFromRight: true, 27 | }, 28 | }, 29 | 30 | scroll: { 31 | enable: true, 32 | initialSpeed: 10, 33 | threshold: { 34 | vertical: 15, 35 | horizontal: 15, 36 | }, 37 | }, 38 | 39 | commit: { 40 | enableAfterEndingDrag: true, 41 | }, 42 | }); 43 | -------------------------------------------------------------------------------- /packages/dflex-dnd/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../dflex-env.d.ts"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "outDir": "./types", 7 | "rootDir": "./src" 8 | }, 9 | "references": [ 10 | { "path": "../dflex-utils" }, 11 | { "path": "../dflex-store" }, 12 | { "path": "../dflex-core-instance" }, 13 | { "path": "../dflex-draggable" } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /packages/dflex-dom-gen/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["eslint-config-dflex"], 3 | rules: { 4 | "import/no-extraneous-dependencies": [ 5 | "error", 6 | { 7 | packageDir: __dirname, 8 | }, 9 | ], 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/dflex-dom-gen/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022, Jalal Maskoun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/dflex-dom-gen/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | DFlex is a Javascript library for modern Drag and Drop apps 6 | 7 |

8 | 9 |

DFlex DOM Relations Generator Algorithm

10 | 11 |

12 | 13 | Dflex build status 16 | 17 | 18 | number of opened pull requests 21 | 22 | 23 | DFlex last released version 26 | 27 | 28 | number of opened issues 31 | 32 | 33 | Dflex welcomes pull request 36 | 37 | 38 | Follow DFlex on twitter 41 | 42 |

43 | 44 | # @dflex/dom-gen 45 | 46 | DFlex Dom generator is an internal DFlex package. 47 | 48 | ## Documentation 📖 49 | 50 | For documentation, more information about DFlex and a live demo, be sure to visit the DFlex website 51 | 52 | ## License 🤝 53 | 54 | DFlex is [MIT License](LICENSE). 55 | -------------------------------------------------------------------------------- /packages/dflex-dom-gen/img/connect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dflex-js/dflex/031d202c72fd4a14a49c6444bdbeeb6baf7a72ca/packages/dflex-dom-gen/img/connect.png -------------------------------------------------------------------------------- /packages/dflex-dom-gen/img/pointer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dflex-js/dflex/031d202c72fd4a14a49c6444bdbeeb6baf7a72ca/packages/dflex-dom-gen/img/pointer.png -------------------------------------------------------------------------------- /packages/dflex-dom-gen/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dflex/dom-gen", 3 | "version": "3.10.6", 4 | "description": "DFlex DOM relations generator algorithm", 5 | "author": "Jalal Maskoun", 6 | "main": "./dist/dflex-dom.js", 7 | "module": "./dist/dflex-dom.mjs", 8 | "types": "./types/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "development": { 12 | "import": "./dist/dev.mjs", 13 | "require": "./dist/dev.js" 14 | }, 15 | "require": "./dist/dflex-dom.js", 16 | "import": "./dist/dflex-dom.mjs", 17 | "types": "./types/index.d.ts" 18 | }, 19 | "./dist/*": "./dist/*" 20 | }, 21 | "scripts": { 22 | "clean": "rimraf ./dist ./types tsconfig.tsbuildinfo", 23 | "emit": "tsc --emitDeclarationOnly" 24 | }, 25 | "homepage": "https://github.com/dflex-js/dflex/tree/main/packages/dflex-dom-gen", 26 | "repository": "https://github.com/dflex-js/dflex", 27 | "license": "MIT", 28 | "files": [ 29 | "dist", 30 | "types", 31 | "LICENSE" 32 | ], 33 | "keywords": [ 34 | "drag-drop", 35 | "dnd", 36 | "sortable", 37 | "reorder", 38 | "drag", 39 | "drop", 40 | "DOM", 41 | "@dflex", 42 | "@dflex/dom-gen", 43 | "@dflex/store", 44 | "@dflex/core-instance", 45 | "@dflex/draggable", 46 | "@dflex/dnd" 47 | ], 48 | "devDependencies": { 49 | "@dflex/utils": "workspace:^3.10.6" 50 | }, 51 | "publishConfig": { 52 | "registry": "https://registry.npmjs.org/", 53 | "access": "public" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /packages/dflex-dom-gen/src/index.ts: -------------------------------------------------------------------------------- 1 | import DOMKeysGenerator from "./DFlexDOMKeysGenerator"; 2 | 3 | export type { Keys, Order, Pointer } from "./DFlexDOMKeysGenerator"; 4 | 5 | export default DOMKeysGenerator; 6 | -------------------------------------------------------------------------------- /packages/dflex-dom-gen/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../dflex-env.d.ts"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "outDir": "./types", 7 | "rootDir": "./src" 8 | }, 9 | "references": [{ "path": "../dflex-utils" }] 10 | } 11 | -------------------------------------------------------------------------------- /packages/dflex-draggable/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["eslint-config-dflex"], 3 | rules: { 4 | "import/no-extraneous-dependencies": [ 5 | "error", 6 | { 7 | packageDir: __dirname, 8 | }, 9 | ], 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/dflex-draggable/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022, Jalal Maskoun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/dflex-draggable/img/draggable.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dflex-js/dflex/031d202c72fd4a14a49c6444bdbeeb6baf7a72ca/packages/dflex-draggable/img/draggable.gif -------------------------------------------------------------------------------- /packages/dflex-draggable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dflex/draggable", 3 | "version": "3.10.6", 4 | "description": "Draggable only package for all JavaScript frameworks", 5 | "author": "Jalal Maskoun", 6 | "main": "./dist/dflex-draggable.js", 7 | "module": "./dist/dflex-draggable.mjs", 8 | "types": "./types/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "development": { 12 | "import": "./dist/dev.mjs", 13 | "require": "./dist/dev.js" 14 | }, 15 | "require": "./dist/dflex-draggable.js", 16 | "import": "./dist/dflex-draggable.mjs", 17 | "types": "./types/index.d.ts" 18 | }, 19 | "./dist/*": "./dist/*" 20 | }, 21 | "scripts": { 22 | "clean": "rimraf ./dist ./types tsconfig.tsbuildinfo", 23 | "compile": "pnpm clean && pnpm tsc -b", 24 | "emit": "tsc --emitDeclarationOnly" 25 | }, 26 | "homepage": "https://github.com/dflex-js/dflex/tree/main/packages/dflex-draggable", 27 | "repository": "https://github.com/dflex-js/dflex", 28 | "license": "MIT", 29 | "files": [ 30 | "dist", 31 | "types", 32 | "LICENSE" 33 | ], 34 | "devDependencies": { 35 | "@dflex/core-instance": "workspace:^3.10.6", 36 | "@dflex/store": "workspace:^3.10.6", 37 | "@dflex/utils": "workspace:^3.10.6", 38 | "eslint-config-dflex-react": "workspace:*" 39 | }, 40 | "keywords": [ 41 | "drag-drop", 42 | "dnd", 43 | "sortable", 44 | "reorder", 45 | "drag", 46 | "drop", 47 | "DOM", 48 | "front-end", 49 | "@dflex", 50 | "@dflex/dom-gen", 51 | "@dflex/store", 52 | "@dflex/core-instance", 53 | "@dflex/draggable", 54 | "@dflex/dnd" 55 | ], 56 | "publishConfig": { 57 | "registry": "https://registry.npmjs.org/", 58 | "access": "public" 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /packages/dflex-draggable/src/DFlexDraggable.ts: -------------------------------------------------------------------------------- 1 | import type { DFlexBaseElement } from "@dflex/core-instance"; 2 | import type { AxesPoint } from "@dflex/utils"; 3 | 4 | import store from "./DFlexDraggableStore"; 5 | import DFlexBaseDraggable from "./DFlexBaseDraggable"; 6 | 7 | class DFlexDraggable extends DFlexBaseDraggable { 8 | /** 9 | * Creates an instance of Draggable. 10 | * Works Only on dragged element level. 11 | * 12 | * @param id - elementId 13 | * @param clickCoordinates - 14 | */ 15 | constructor(id: string, clickCoordinates: AxesPoint) { 16 | const [element, DOM] = store.getElmWithDOM(id); 17 | super(element, DOM, clickCoordinates); 18 | 19 | this.setDOMAttrAndStyle(this.draggedDOM, null, true, null, null); 20 | } 21 | 22 | dragAt(x: number, y: number) { 23 | this.translate(x, y); 24 | 25 | this.draggedElm.translate.clone(this.translatePlaceholder); 26 | } 27 | 28 | endDragging() { 29 | this.setDOMAttrAndStyle(this.draggedDOM, null, false, null, null); 30 | } 31 | } 32 | 33 | export default DFlexDraggable; 34 | -------------------------------------------------------------------------------- /packages/dflex-draggable/src/DFlexDraggableStore.ts: -------------------------------------------------------------------------------- 1 | import DFlexBaseStore from "@dflex/store"; 2 | import { canUseDOM, getAnimationOptions } from "@dflex/utils"; 3 | 4 | declare global { 5 | // eslint-disable-next-line 6 | var $DFlex_Draggable: DFlexDraggableStore; 7 | } 8 | 9 | class DFlexDraggableStore extends DFlexBaseStore { 10 | constructor() { 11 | super(); 12 | this._initBranch = this._initBranch.bind(this); 13 | this._initElmDOMInstance = this._initElmDOMInstance.bind(this); 14 | } 15 | 16 | private _initElmDOMInstance(id: string) { 17 | const [dflexNode, DOM] = this.getElmWithDOM(id); 18 | 19 | dflexNode.initElmRect(DOM, 0, 0); 20 | } 21 | 22 | private _initBranch(SK: string) { 23 | this.getElmSiblingsByKey(SK).forEach(this._initElmDOMInstance); 24 | } 25 | 26 | /** 27 | * Register element for Draggable store. 28 | * @param id 29 | */ 30 | // @ts-ignore 31 | register(id: string) { 32 | this.addElmToRegistry( 33 | { 34 | id, 35 | depth: 0, 36 | readonly: false, 37 | animation: getAnimationOptions(), 38 | CSSTransform: null, 39 | }, 40 | this._initBranch, 41 | ); 42 | } 43 | } 44 | 45 | export default (function createStoreInstance() { 46 | const store = new DFlexDraggableStore(); 47 | 48 | if (__DEV__) { 49 | if (canUseDOM()) { 50 | if (!globalThis.$DFlex_Draggable) { 51 | globalThis.$DFlex_Draggable = store; 52 | } 53 | } 54 | } 55 | 56 | return store; 57 | })(); 58 | -------------------------------------------------------------------------------- /packages/dflex-draggable/src/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Draggable } from "./DFlexDraggable"; 2 | export { default as store } from "./DFlexDraggableStore"; 3 | export { default as DFlexBaseDraggable } from "./DFlexBaseDraggable"; 4 | -------------------------------------------------------------------------------- /packages/dflex-draggable/test/__snapshots__/draggableStore.test.tsx.snap: -------------------------------------------------------------------------------- 1 | // Jest Snapshot v1, https://goo.gl/fbAQLP 2 | 3 | exports[`Draggable Store Registers element and initiates translateX,Y 1`] = ` 4 | Map { 5 | "id-0" => DFlexElement { 6 | "DOMGrid": PointNum { 7 | "x": 0, 8 | "y": 0, 9 | }, 10 | "DOMOrder": { 11 | "parent": 0, 12 | "self": 0, 13 | }, 14 | "VDOMOrder": { 15 | "parent": 0, 16 | "self": 0, 17 | }, 18 | "_CSSTransform": null, 19 | "_RAF": [Function], 20 | "_animation": { 21 | "duration": "dynamic", 22 | "easing": "ease-in", 23 | }, 24 | "_computedDimensions": null, 25 | "_hasPendingTransform": false, 26 | "_initialPosition": PointNum { 27 | "x": 0, 28 | "y": 0, 29 | }, 30 | "_isVisible": true, 31 | "_translateHistory": undefined, 32 | "depth": 0, 33 | "id": "id-0", 34 | "keys": { 35 | "BK": "dflex_bk_0", 36 | "CHK": null, 37 | "PK": "dflex_ky_1_0", 38 | "SK": "dflex_sk_0_0", 39 | }, 40 | "readonly": false, 41 | "rect": BoxRect { 42 | "bottom": 0, 43 | "height": 0, 44 | "left": 0, 45 | "right": 0, 46 | "top": 0, 47 | "width": 0, 48 | }, 49 | "translate": PointNum { 50 | "x": 0, 51 | "y": 0, 52 | }, 53 | }, 54 | } 55 | `; 56 | -------------------------------------------------------------------------------- /packages/dflex-draggable/test/draggableStore.test.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import * as React from "react"; 3 | import * as ReactTestUtils from "react-dom/test-utils"; 4 | import { Root, createRoot } from "react-dom/client"; 5 | 6 | import { store } from "../src"; 7 | 8 | describe("Draggable Store", () => { 9 | const elm0D0 = { 10 | id: "id-0", 11 | }; 12 | 13 | let container: HTMLDivElement | null; 14 | let reactRoot: Root; 15 | 16 | beforeAll(() => { 17 | container = document.createElement("div"); 18 | reactRoot = createRoot(container); 19 | document.body.appendChild(container); 20 | 21 | function init() { 22 | const ref = React.createRef(); 23 | 24 | function TestBase() { 25 | React.useEffect(() => { 26 | if (ref.current) { 27 | store.register(elm0D0.id); 28 | } 29 | 30 | return () => { 31 | store.unregister(elm0D0.id); 32 | }; 33 | }, [ref]); 34 | 35 | return
; 36 | } 37 | 38 | ReactTestUtils.act(() => { 39 | reactRoot.render(); 40 | }); 41 | } 42 | 43 | init(); 44 | }); 45 | 46 | afterAll(() => { 47 | document.body.removeChild(container!); 48 | container = null; 49 | jest.restoreAllMocks(); 50 | }); 51 | 52 | it("Registry is not empty", () => { 53 | expect(store.registry).toBeTruthy(); 54 | }); 55 | 56 | it("Registers element and initiates translateX,Y", () => { 57 | expect(store.registry).toMatchSnapshot(); 58 | }); 59 | }); 60 | -------------------------------------------------------------------------------- /packages/dflex-draggable/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../dflex-env.d.ts"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "outDir": "./types", 7 | "rootDir": "./src" 8 | }, 9 | "references": [ 10 | { "path": "../dflex-store" }, 11 | { "path": "../dflex-core-instance" } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/dflex-store/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["eslint-config-dflex"], 3 | rules: { 4 | "import/no-extraneous-dependencies": [ 5 | "error", 6 | { 7 | packageDir: __dirname, 8 | }, 9 | ], 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/dflex-store/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022, Jalal Maskoun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/dflex-store/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | DFlex is a Javascript library for modern Drag and Drop apps 6 | 7 |

8 | 9 |

DFlex DOM Store

10 | 11 |

12 | 13 | Dflex build status 16 | 17 | 18 | number of opened pull requests 21 | 22 | 23 | DFlex last released version 26 | 27 | 28 | number of opened issues 31 | 32 | 33 | Dflex welcomes pull request 36 | 37 | 38 | Follow DFlex on twitter 41 | 42 |

43 | 44 | # @dflex/store 45 | 46 | DFlex DOM store is an internal DFlex package. 47 | 48 | ## Documentation 📖 49 | 50 | For documentation, more information about DFlex and a live demo, be sure to visit the DFlex website 51 | 52 | ## License 🤝 53 | 54 | DFlex is [MIT License](LICENSE). 55 | -------------------------------------------------------------------------------- /packages/dflex-store/img/store-registry.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dflex-js/dflex/031d202c72fd4a14a49c6444bdbeeb6baf7a72ca/packages/dflex-store/img/store-registry.png -------------------------------------------------------------------------------- /packages/dflex-store/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dflex/store", 3 | "version": "3.10.6", 4 | "description": "DOM store allows you to traverse through the DOM tree with element-id", 5 | "author": "Jalal Maskoun", 6 | "main": "./dist/dflex-store.js", 7 | "module": "./dist/dflex-store.mjs", 8 | "types": "./types/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "development": { 12 | "import": "./dist/dev.mjs", 13 | "require": "./dist/dev.js" 14 | }, 15 | "require": "./dist/dflex-store.js", 16 | "import": "./dist/dflex-store.mjs", 17 | "types": "./types/index.d.ts" 18 | }, 19 | "./dist/*": "./dist/*" 20 | }, 21 | "scripts": { 22 | "clean": "rimraf ./dist ./types tsconfig.tsbuildinfo", 23 | "emit": "tsc --emitDeclarationOnly" 24 | }, 25 | "homepage": "https://github.com/dflex-js/dflex/tree/main/packages/dflex-store", 26 | "repository": "https://github.com/dflex-js/dflex", 27 | "license": "MIT", 28 | "files": [ 29 | "dist", 30 | "types", 31 | "LICENSE" 32 | ], 33 | "devDependencies": { 34 | "@dflex/core-instance": "workspace:^3.10.6", 35 | "@dflex/dom-gen": "workspace:^3.10.6", 36 | "@dflex/utils": "workspace:^3.10.6" 37 | }, 38 | "keywords": [ 39 | "drag-drop", 40 | "dnd", 41 | "sortable", 42 | "reorder", 43 | "drag", 44 | "drop", 45 | "DOM", 46 | "front-end", 47 | "@dflex", 48 | "@dflex/dom-gen", 49 | "@dflex/store", 50 | "@dflex/core-instance", 51 | "@dflex/draggable", 52 | "@dflex/dnd" 53 | ], 54 | "publishConfig": { 55 | "registry": "https://registry.npmjs.org/", 56 | "access": "public" 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /packages/dflex-store/src/index.ts: -------------------------------------------------------------------------------- 1 | import DFlexBaseStore from "./DFlexBaseStore"; 2 | 3 | export type { 4 | RegisterInputOpts, 5 | RegisterInputProcessed, 6 | DFlexGlobalConfig, 7 | } from "./DFlexBaseStore"; 8 | 9 | export default DFlexBaseStore; 10 | -------------------------------------------------------------------------------- /packages/dflex-store/src/utils.ts: -------------------------------------------------------------------------------- 1 | import { tracker, PREFIX_TRACKER_ID } from "@dflex/utils"; 2 | 3 | /** 4 | * Assigns an element ID to an HTMLElement if it doesn't already have one. 5 | * 6 | * @param DOM - The HTMLElement to assign an ID to. 7 | * @returns The assigned or existing ID of the element. 8 | */ 9 | function assignElementID(DOM: HTMLElement): string { 10 | let { id } = DOM; 11 | 12 | if (!id) { 13 | id = tracker.newTravel(PREFIX_TRACKER_ID); 14 | DOM.id = id; 15 | } 16 | 17 | return id; 18 | } 19 | 20 | /** 21 | * Retrieves an HTMLElement by its ID or throws an error if not found. 22 | * 23 | * @param id - The ID of the element to retrieve. 24 | * @returns The HTMLElement with the specified ID, or null if not found. 25 | * @throws Error - If the element is not found or is not a valid HTMLElement. 26 | */ 27 | function getElmDOMOrThrow(id: string): HTMLElement | null { 28 | let DOM = document.getElementById(id); 29 | 30 | if (!DOM) { 31 | if (__DEV__) { 32 | throw new Error( 33 | `Element with ID: ${id} is not found.This could be due wrong ID or missing DOM element.`, 34 | ); 35 | } 36 | } 37 | 38 | if (!DOM || DOM.nodeType !== Node.ELEMENT_NODE) { 39 | if (__DEV__) { 40 | throw new Error(`Invalid HTMLElement ${DOM} is passed to registry.`); 41 | } 42 | 43 | DOM = null; 44 | } 45 | 46 | return DOM; 47 | } 48 | 49 | export { assignElementID, getElmDOMOrThrow }; 50 | -------------------------------------------------------------------------------- /packages/dflex-store/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../dflex-env.d.ts"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "rootDir": "./src", 7 | "outDir": "./types" 8 | }, 9 | "references": [ 10 | { "path": "../dflex-dom-gen" }, 11 | { "path": "../dflex-core-instance" } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /packages/dflex-utils/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["eslint-config-dflex"], 3 | rules: { 4 | "import/no-extraneous-dependencies": [ 5 | "error", 6 | { 7 | packageDir: __dirname, 8 | }, 9 | ], 10 | }, 11 | }; 12 | -------------------------------------------------------------------------------- /packages/dflex-utils/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022, Jalal Maskoun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /packages/dflex-utils/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | DFlex is a Javascript library for modern Drag and Drop apps 6 | 7 |

8 | 9 |

DFlex Utilities

10 | 11 |

12 | 13 | Dflex build status 16 | 17 | 18 | number of opened pull requests 21 | 22 | 23 | DFlex last released version 26 | 27 | 28 | number of opened issues 31 | 32 | 33 | Dflex welcomes pull request 36 | 37 | 38 | Follow DFlex on twitter 41 | 42 |

43 | 44 | # @dflex/utils 45 | 46 | DFlex utilities is an internal DFlex package. 47 | 48 | ## Documentation 📖 49 | 50 | For documentation, more information about DFlex and a live demo, be sure to visit the DFlex website 51 | 52 | ## License 🤝 53 | 54 | DFlex is [MIT License](LICENSE). 55 | -------------------------------------------------------------------------------- /packages/dflex-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dflex/utils", 3 | "version": "3.10.6", 4 | "description": "Utility package for DFlex", 5 | "author": "Jalal Maskoun", 6 | "main": "./dist/dflex-utils.js", 7 | "module": "./dist/dflex-utils.mjs", 8 | "types": "./types/index.d.ts", 9 | "exports": { 10 | ".": { 11 | "development": { 12 | "import": "./dist/dev.mjs", 13 | "require": "./dist/dev.js" 14 | }, 15 | "require": "./dist/dflex-utils.js", 16 | "import": "./dist/dflex-utils.mjs", 17 | "types": "./types/index.d.ts" 18 | }, 19 | "./dist/*": "./dist/*" 20 | }, 21 | "scripts": { 22 | "clean": "rimraf ./dist ./types tsconfig.tsbuildinfo", 23 | "emit": "tsc --emitDeclarationOnly" 24 | }, 25 | "repository": "https://github.com/dflex-js/dflex", 26 | "homepage": "https://github.com/dflex-js/dflex/tree/main/packages/dflex-utils", 27 | "license": "MIT", 28 | "files": [ 29 | "dist", 30 | "types", 31 | "LICENSE" 32 | ], 33 | "keywords": [ 34 | "drag-drop", 35 | "dnd", 36 | "sortable", 37 | "reorder", 38 | "drag", 39 | "drop", 40 | "DOM", 41 | "@dflex", 42 | "@dflex/dom-gen", 43 | "@dflex/store", 44 | "@dflex/core-instance", 45 | "@dflex/draggable", 46 | "@dflex/dnd" 47 | ], 48 | "publishConfig": { 49 | "registry": "https://registry.npmjs.org/", 50 | "access": "public" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/Box/AbstractBox.ts: -------------------------------------------------------------------------------- 1 | /** Four direction instance - clockwise */ 2 | class AbstractBox { 3 | /** Minimal `Y` coordinate */ 4 | top!: T; 5 | 6 | /** Maximal `X` coordinate */ 7 | right!: T; 8 | 9 | /** Maximal `Y` coordinate */ 10 | bottom!: T; 11 | 12 | /** Minimal `X` coordinate */ 13 | left!: T; 14 | 15 | /** 16 | * 17 | * @param top - minimal y coordinate 18 | * @param right - maximal x coordinate 19 | * @param bottom - maximal y coordinate 20 | * @param left - minimal x coordinate 21 | */ 22 | constructor(top: T, right: T, bottom: T, left: T) { 23 | this.top = top; 24 | this.right = right; 25 | this.bottom = bottom; 26 | this.left = left; 27 | } 28 | } 29 | 30 | export default AbstractBox; 31 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/Box/BoxBool.ts: -------------------------------------------------------------------------------- 1 | import type { Axis, Direction } from "../types"; 2 | import Box from "./Box"; 3 | 4 | class BoxBool extends Box { 5 | constructor(top: boolean, right: boolean, bottom: boolean, left: boolean) { 6 | super(top, right, bottom, left); 7 | 8 | if (__DEV__) { 9 | Object.seal(this); 10 | } 11 | } 12 | 13 | /** 14 | * Reset all directions to false. 15 | * 16 | * @returns 17 | */ 18 | setFalsy(): this { 19 | this.setBox(false, false, false, false); 20 | 21 | return this; 22 | } 23 | 24 | /** 25 | * True when one of two directions in a given axis is true. 26 | * 27 | * @param axis 28 | * @returns 29 | */ 30 | isTruthyByAxis(axis: Axis): boolean { 31 | switch (axis) { 32 | case "x": 33 | return this.left || this.right; 34 | default: 35 | return this.top || this.bottom; 36 | } 37 | } 38 | 39 | isTruthyOnSide(axis: Axis, direction: Direction) { 40 | switch (axis) { 41 | case "x": 42 | return direction === 1 ? this.right : this.left; 43 | default: 44 | return direction === 1 ? this.bottom : this.top; 45 | } 46 | } 47 | 48 | /** 49 | * True when one of four directions is true. 50 | * 51 | * @returns 52 | */ 53 | isTruthy(): boolean { 54 | return this.left || this.right || this.top || this.bottom; 55 | } 56 | } 57 | 58 | export default BoxBool; 59 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/Box/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AbstractBox } from "./AbstractBox"; 2 | export { default as Box } from "./Box"; 3 | export { default as BoxBool } from "./BoxBool"; 4 | export { default as BoxNum } from "./BoxNum"; 5 | export { default as BoxRect } from "./BoxRect"; 6 | export type { AbstractBoxRect } from "./BoxRect"; 7 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/DFlexCycle/index.ts: -------------------------------------------------------------------------------- 1 | export type { AbstractDFlexCycle } from "./DFlexCycle"; 2 | export { default as DFlexCycle } from "./DFlexCycle"; 3 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/DFlexEventDebounce/eventDebounce.ts: -------------------------------------------------------------------------------- 1 | import { DFlexCreateRAF, DFlexCreateTimeout } from "../environment"; 2 | 3 | type DebouncedListener = () => void; 4 | 5 | interface DebounceControl extends DebouncedListener { 6 | isPaused: () => boolean; 7 | pause: () => void; 8 | resume: () => void; 9 | } 10 | 11 | function DFlexEventDebounce( 12 | listener: DebouncedListener, 13 | immediate = false, 14 | throttle = 200, 15 | ): DebounceControl { 16 | const [timeout, cancelTimeout] = DFlexCreateTimeout(throttle); 17 | const [RAF, cancelRAF] = DFlexCreateRAF(); 18 | 19 | let lastCall = performance.now(); 20 | let isPaused = false; 21 | 22 | const debouncedListener: DebounceControl = () => { 23 | if (isPaused) { 24 | return; 25 | } 26 | 27 | const currentTime = performance.now(); 28 | const timeSinceLastCall = currentTime - lastCall; 29 | 30 | const shouldCallListener = immediate || timeSinceLastCall >= throttle; 31 | 32 | if (shouldCallListener) { 33 | // Schedule a animated frame and cancel previous one. 34 | RAF(listener, true); 35 | lastCall = currentTime; 36 | } else { 37 | // Schedule a delayed listener to be executed after the throttle period and cancel previous schedule. 38 | timeout(debouncedListener, true); 39 | } 40 | }; 41 | 42 | debouncedListener.isPaused = () => isPaused; 43 | 44 | debouncedListener.pause = () => { 45 | if (!isPaused) { 46 | isPaused = true; 47 | cancelRAF(); 48 | cancelTimeout(); 49 | } 50 | }; 51 | 52 | debouncedListener.resume = () => { 53 | if (isPaused) { 54 | isPaused = false; 55 | debouncedListener(); 56 | } 57 | }; 58 | 59 | return debouncedListener; 60 | } 61 | 62 | export default DFlexEventDebounce; 63 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/DFlexEventDebounce/index.ts: -------------------------------------------------------------------------------- 1 | import DFlexEventDebounce from "./eventDebounce"; 2 | 3 | export default DFlexEventDebounce; 4 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/DFlexTracker/DFlexTracker.ts: -------------------------------------------------------------------------------- 1 | class DFlexTracker { 2 | private _travelID: Record; 3 | 4 | /** 5 | * Creates an instance of Tracker. 6 | */ 7 | constructor() { 8 | this._travelID = {}; 9 | } 10 | 11 | /** 12 | * Increment travels and return the last one. 13 | */ 14 | newTravel(prefix: string): string { 15 | if (this._travelID[prefix] === undefined) { 16 | this._travelID[prefix] = 0; 17 | } else { 18 | this._travelID[prefix] += 1; 19 | } 20 | 21 | return `${prefix}${this._travelID[prefix]}`; 22 | } 23 | } 24 | 25 | export default DFlexTracker; 26 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/DFlexTracker/DFlexTrackerSingleton.ts: -------------------------------------------------------------------------------- 1 | import DFlexTracker from "./DFlexTracker"; 2 | 3 | export default (function createInstance() { 4 | const tracker = new DFlexTracker(); 5 | 6 | return tracker; 7 | })(); 8 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/DFlexTracker/constant.ts: -------------------------------------------------------------------------------- 1 | const PREFIX_TRACKER_CYCLE = "dflex_cycle_"; 2 | 3 | const PREFIX_TRACKER_ID = "dflex_id_"; 4 | 5 | const PREFIX_TRACKER_KY = "dflex_ky_"; 6 | 7 | export { PREFIX_TRACKER_CYCLE, PREFIX_TRACKER_ID, PREFIX_TRACKER_KY }; 8 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/DFlexTracker/index.ts: -------------------------------------------------------------------------------- 1 | import tracker from "./DFlexTrackerSingleton"; 2 | 3 | import { 4 | PREFIX_TRACKER_CYCLE, 5 | PREFIX_TRACKER_ID, 6 | PREFIX_TRACKER_KY, 7 | } from "./constant"; 8 | 9 | export { tracker, PREFIX_TRACKER_CYCLE, PREFIX_TRACKER_ID, PREFIX_TRACKER_KY }; 10 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/FeatureFlags.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * If true, then DFlex will assert each element position that's change and match 3 | * with DOM. 4 | */ 5 | export const enablePositionAssertion = false; 6 | 7 | /** 8 | * If true, then DFlex will override input options and reconcile changes after 9 | * each cycle. 10 | */ 11 | export const enableCommit = false; 12 | 13 | export const enableUndoSiblingsDebugger = false; 14 | 15 | export const enableRegisterDebugger = false; 16 | 17 | export const enableMechanismDebugger = false; 18 | 19 | export const enableScrollDebugger = false; 20 | 21 | export const enableVisibilityDebugger = false; 22 | 23 | export const enableMutationDebugger = false; 24 | 25 | export const enableReconcileDebugger = false; 26 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/Point/AxesPoint.ts: -------------------------------------------------------------------------------- 1 | class AxesPoint { 2 | x!: T; 3 | 4 | y!: T; 5 | 6 | constructor(x: T, y: T) { 7 | this.x = x; 8 | this.y = y; 9 | 10 | if (__DEV__) { 11 | Object.seal(this); 12 | } 13 | } 14 | } 15 | 16 | export default AxesPoint; 17 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/Point/Point.ts: -------------------------------------------------------------------------------- 1 | import AxesPoint from "./AxesPoint"; 2 | 3 | class Point extends AxesPoint { 4 | /** 5 | * Assigns the given values to the local instance. 6 | * 7 | * @param x 8 | * @param y 9 | */ 10 | setAxes(x: T, y: T): void { 11 | this.x = x; 12 | this.y = y; 13 | } 14 | 15 | /** 16 | * Clone a given point into local instance. 17 | * 18 | * @param target 19 | */ 20 | clone(target: Point | AxesPoint): void { 21 | this.setAxes(target.x, target.y); 22 | } 23 | 24 | /** 25 | * Get local instance of point. 26 | * 27 | * @returns 28 | */ 29 | getInstance(): AxesPoint { 30 | return { 31 | x: this.x, 32 | y: this.y, 33 | }; 34 | } 35 | 36 | /** 37 | * True when both axes match the same value. 38 | * 39 | * @param target 40 | * @returns 41 | */ 42 | isInstanceEqual(target: Point | AxesPoint): boolean { 43 | return this.x === target.x && this.y === target.y; 44 | } 45 | 46 | /** 47 | * True when both axes match the same value. 48 | * 49 | * @param x 50 | * @param y 51 | * @returns 52 | */ 53 | isEqual(x: T, y: T): boolean { 54 | return this.x === x && this.y === y; 55 | } 56 | 57 | /** 58 | * True when both axes doesn't match the given value. 59 | * 60 | * @param x 61 | * @param y 62 | * @returns 63 | */ 64 | isNotEqual(x: T, y: T): boolean { 65 | return this.x !== x || this.y !== y; 66 | } 67 | } 68 | 69 | export default Point; 70 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/Point/PointBool.ts: -------------------------------------------------------------------------------- 1 | import Point from "./Point"; 2 | 3 | class PointBool extends Point { 4 | /** 5 | * True when both points X and Y are true. 6 | * @returns 7 | */ 8 | isOneTruthy(): boolean { 9 | return this.x || this.y; 10 | } 11 | 12 | /** 13 | * True when one point is false. 14 | * @returns 15 | */ 16 | isAllFalsy(): boolean { 17 | return !(this.x || this.y); 18 | } 19 | 20 | /** 21 | * Set both x and y to false. 22 | */ 23 | setFalsy(): void { 24 | this.x = false; 25 | this.y = false; 26 | } 27 | } 28 | 29 | export default PointBool; 30 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/Point/PointNum.ts: -------------------------------------------------------------------------------- 1 | import Point from "./Point"; 2 | import type AxesPoint from "./AxesPoint"; 3 | import { AbstractBox, BoxNum } from "../Box"; 4 | import type { Axis } from "../types"; 5 | 6 | class PointNum extends Point { 7 | /** 8 | * Increase the current point by the given another point. 9 | * 10 | * @param point 11 | */ 12 | increase(point: AxesPoint): this { 13 | this.x += point.x; 14 | this.y += point.y; 15 | 16 | return this; 17 | } 18 | 19 | composeBox(box: AbstractBox, isInner: boolean): BoxNum { 20 | const { top, left, bottom, right } = box; 21 | 22 | return isInner 23 | ? new BoxNum(top + this.y, right - this.x, bottom - this.y, left + this.x) 24 | : new BoxNum( 25 | top - this.y, 26 | right + this.x, 27 | bottom + this.y, 28 | left - this.x, 29 | ); 30 | } 31 | 32 | onSameAxis(axis: Axis, point: AxesPoint): boolean { 33 | return axis === "y" ? point.x === this.x : point.y === this.y; 34 | } 35 | } 36 | 37 | export default PointNum; 38 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/Point/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AxesPoint } from "./AxesPoint"; 2 | export { default as Point } from "./Point"; 3 | export { default as PointNum } from "./PointNum"; 4 | export { default as PointBool } from "./PointBool"; 5 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/TaskQueue/index.ts: -------------------------------------------------------------------------------- 1 | import TaskQueue from "./TaskQueue"; 2 | 3 | export default TaskQueue; 4 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/Threshold/index.ts: -------------------------------------------------------------------------------- 1 | export { default as DFlexThreshold } from "./Threshold"; 2 | export type { ThresholdPercentages } from "./Threshold"; 3 | 4 | export { default as ThresholdDeadZone } from "./ThresholdDeadZone"; 5 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/collections/assertElmPos.ts: -------------------------------------------------------------------------------- 1 | import type { AbstractBox } from "../Box"; 2 | 3 | let didThrowError = false; 4 | 5 | function assertElementPosition(DOM: HTMLElement, rect: AbstractBox): void { 6 | if (didThrowError) { 7 | return; 8 | } 9 | 10 | const DOMRect = DOM.getBoundingClientRect(); 11 | 12 | const keys = Object.keys(rect) as (keyof typeof rect)[]; 13 | 14 | keys.forEach((k) => { 15 | if ( 16 | Object.prototype.hasOwnProperty.call(DOMRect, k) && 17 | DOMRect[k] !== rect[k] 18 | ) { 19 | didThrowError = true; 20 | 21 | throw new Error( 22 | `Element position assertion failed. Expected: ${DOMRect[k]} found: ${rect[k]}`, 23 | ); 24 | } 25 | }); 26 | } 27 | 28 | export default assertElementPosition; 29 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/collections/combineKeys.ts: -------------------------------------------------------------------------------- 1 | type KY = number | string; 2 | 3 | function combineKeys(k1: KY, k2: KY) { 4 | return `${k1}_${k2}`; 5 | } 6 | 7 | export default combineKeys; 8 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/collections/getAnimationOptions.ts: -------------------------------------------------------------------------------- 1 | import type { AnimationOpts } from "../types"; 2 | 3 | function getAnimationOptions( 4 | animation?: Partial | null, 5 | ): Required | null { 6 | const defaultAnimation: AnimationOpts = { 7 | easing: "ease-in", 8 | duration: "dynamic", 9 | }; 10 | 11 | if (animation === undefined) { 12 | return defaultAnimation; 13 | } 14 | 15 | if (animation === null) { 16 | return null; 17 | } 18 | 19 | return { ...defaultAnimation, ...animation }; 20 | } 21 | 22 | export default getAnimationOptions; 23 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/collections/getRectTypeByAxis.ts: -------------------------------------------------------------------------------- 1 | import { Axis } from "../types"; 2 | import { HEIGHT, LEFT, TOP, WIDTH, RIGHT, BOTTOM } from "../constants"; 3 | 4 | function getDimensionTypeByAxis(axis: Axis) { 5 | return axis === "x" ? WIDTH : HEIGHT; 6 | } 7 | 8 | function getStartingPointByAxis(axis: Axis) { 9 | return axis === "x" ? LEFT : TOP; 10 | } 11 | 12 | function getEndingPointByAxis(axis: Axis) { 13 | return axis === "x" ? RIGHT : BOTTOM; 14 | } 15 | 16 | function getOppositeAxis(axis: Axis): Axis { 17 | return axis === "x" ? "y" : "x"; 18 | } 19 | 20 | export { 21 | getDimensionTypeByAxis, 22 | getStartingPointByAxis, 23 | getEndingPointByAxis, 24 | getOppositeAxis, 25 | }; 26 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/collections/index.ts: -------------------------------------------------------------------------------- 1 | export { default as combineKeys } from "./combineKeys"; 2 | export { default as warnOnce } from "./warnOnce"; 3 | export { default as assertElmPos } from "./assertElmPos"; 4 | export { default as getAnimationOptions } from "./getAnimationOptions"; 5 | export { noopSet, noop } from "./utils"; 6 | 7 | export { 8 | getDimensionTypeByAxis, 9 | getStartingPointByAxis, 10 | getEndingPointByAxis, 11 | getOppositeAxis, 12 | } from "./getRectTypeByAxis"; 13 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/collections/utils.ts: -------------------------------------------------------------------------------- 1 | function noop() {} 2 | 3 | const noopSet: Set = new Set(); 4 | 5 | if (__DEV__) { 6 | Object.freeze(noopSet); 7 | } 8 | 9 | export { noop, noopSet }; 10 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/collections/warnOnce.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | const log: Record = {}; 3 | 4 | function warnOnce(caller: string, ...message: any[]): void { 5 | if (!log[caller]) { 6 | log[caller] = true; 7 | console.warn(...message); 8 | } 9 | } 10 | 11 | export default warnOnce; 12 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/computedStyleUtils/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | // getCachedComputedStyle, 3 | getCachedComputedStyleProperty, 4 | clearComputedStyleCache, 5 | getElmDimensions, 6 | getElmPos, 7 | getElmOverflow, 8 | getParsedElmTransform, 9 | setFixedDimensions, 10 | setRelativePosition, 11 | setParentDimensions, 12 | setStyleProperty, 13 | removeStyleProperty, 14 | removeOpacity, 15 | hasCSSTransition, 16 | rmEmptyAttr, 17 | } from "./computedStyleUtils"; 18 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const HEIGHT = "height"; 2 | export const WIDTH = "width"; 3 | 4 | export const MIN_HEIGHT = "min-height"; 5 | export const MIN_WIDTH = "min-width"; 6 | 7 | export const LEFT = "left"; 8 | export const TOP = "top"; 9 | 10 | export const RIGHT = "right"; 11 | export const BOTTOM = "bottom"; 12 | 13 | export const BORDER_TOP_WIDTH = "border-top-width"; 14 | export const BORDER_BOTTOM_WIDTH = "border-bottom-width"; 15 | export const PADDING_TOP = "padding-top"; 16 | export const PADDING_BOTTOM = "padding-bottom"; 17 | export const BORDER_LEFT_WIDTH = "border-left-width"; 18 | export const BORDER_RIGHT_WIDTH = "border-right-width"; 19 | export const PADDING_LEFT = "padding-left"; 20 | export const PADDING_RIGHT = "padding-right"; 21 | 22 | export const OFFSET_HEIGHT = "offsetHeight"; 23 | export const OFFSET_WIDTH = "offsetWidth"; 24 | 25 | export const POSITION = "position"; 26 | export const TRANSFORM = "transform"; 27 | export const OPACITY = "opacity"; 28 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/environment/DFlexCreateEvent.ts: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line no-unused-vars 2 | type EventCallback = (event: Event) => void; 3 | 4 | interface EventInstance { 5 | eventName: string; 6 | eventCallback: EventCallback; 7 | } 8 | 9 | export type EventCleanup = () => void; 10 | 11 | const eventInstances: EventInstance[] = []; 12 | 13 | function DFlexCreateEvent( 14 | eventName: string, 15 | callback: EventCallback, 16 | ): EventCleanup { 17 | function eventHandler(event: Event): void { 18 | callback(event); 19 | } 20 | 21 | window.addEventListener(eventName, eventHandler); 22 | 23 | const instance: EventInstance = { 24 | eventName, 25 | eventCallback: eventHandler, 26 | }; 27 | 28 | eventInstances.push(instance); 29 | 30 | function cleanup(): void { 31 | window.removeEventListener(eventName, eventHandler); 32 | const index = eventInstances.indexOf(instance); 33 | if (index !== -1) { 34 | eventInstances.splice(index, 1); 35 | } 36 | } 37 | 38 | return cleanup; 39 | } 40 | 41 | function autoCleanupAllEvents(): void { 42 | eventInstances.forEach(({ eventName, eventCallback }) => { 43 | window.removeEventListener(eventName, eventCallback); 44 | }); 45 | 46 | eventInstances.length = 0; 47 | } 48 | 49 | export { DFlexCreateEvent, autoCleanupAllEvents }; 50 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/environment/DFlexTimeout.ts: -------------------------------------------------------------------------------- 1 | import { noop } from "../collections"; 2 | 3 | type TimeoutCallback = () => void; 4 | 5 | export type TimeoutCleanup = () => void; 6 | 7 | export type TimeoutFunction = ( 8 | // eslint-disable-next-line no-unused-vars 9 | callback: TimeoutCallback | null, 10 | // eslint-disable-next-line no-unused-vars 11 | cancelPrevSchedule: boolean, 12 | ) => void; 13 | 14 | export type IsThrottledFunction = () => boolean; 15 | 16 | const timeoutInstances: TimeoutCleanup[] = []; 17 | 18 | function DFlexCreateTimeout( 19 | msDelay: number, 20 | ): [TimeoutFunction, TimeoutCleanup, IsThrottledFunction] { 21 | let id: ReturnType | null = null; 22 | let isThrottled: boolean = false; 23 | 24 | function cleanup(): void { 25 | if (id) { 26 | clearTimeout(id); 27 | id = null; 28 | } 29 | } 30 | 31 | function timeout( 32 | callback: TimeoutCallback | null, 33 | cancelPrevSchedule: boolean, 34 | ): void { 35 | const cb = callback || noop; 36 | isThrottled = true; 37 | 38 | if (cancelPrevSchedule) { 39 | cleanup(); 40 | } 41 | 42 | id = setTimeout(() => { 43 | isThrottled = false; 44 | cb(); 45 | }, msDelay); 46 | } 47 | 48 | function getIsThrottled(): boolean { 49 | return isThrottled; 50 | } 51 | 52 | timeoutInstances.push(cleanup); 53 | 54 | return [timeout, cleanup, getIsThrottled]; 55 | } 56 | 57 | function autoCleanupAllTimeouts(): void { 58 | timeoutInstances.forEach((cleanup) => { 59 | cleanup(); 60 | }); 61 | 62 | timeoutInstances.length = 0; 63 | } 64 | 65 | export { autoCleanupAllTimeouts, DFlexCreateTimeout }; 66 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/environment/canUseDOM.ts: -------------------------------------------------------------------------------- 1 | function canUseDOM() { 2 | return ( 3 | typeof window !== "undefined" && 4 | typeof window.document !== "undefined" && 5 | typeof window.document.createElement !== "undefined" 6 | ); 7 | } 8 | 9 | export default canUseDOM; 10 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/environment/getElmBoxRect.ts: -------------------------------------------------------------------------------- 1 | import { BoxRect } from "../Box"; 2 | 3 | function getElmBoxRect( 4 | DOM: HTMLElement, 5 | scrollLeft: number, 6 | scrollTop: number, 7 | ): BoxRect { 8 | const { left, top, right, bottom, height, width } = 9 | DOM.getBoundingClientRect(); 10 | 11 | const boxRect = new BoxRect(top, right, bottom, left); 12 | 13 | if (scrollLeft === 0 && scrollTop === 0) { 14 | return boxRect; 15 | } 16 | 17 | /** 18 | * Calculate the element's position by adding the scroll position to the 19 | * left and top values obtained from getBoundingClientRect. 20 | */ 21 | const elementLeft = left + scrollLeft; 22 | const elementTop = top + scrollTop; 23 | 24 | boxRect.setByPointAndDimensions(elementTop, elementLeft, height, width); 25 | 26 | return boxRect; 27 | } 28 | 29 | export default getElmBoxRect; 30 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/environment/getParentElm.ts: -------------------------------------------------------------------------------- 1 | const MAX_LOOP_ELEMENTS_TO_WARN = 49; 2 | 3 | function getParentElm( 4 | baseElement: HTMLElement, 5 | // eslint-disable-next-line no-unused-vars 6 | cb: (arg: HTMLElement) => boolean, 7 | ): null | HTMLElement { 8 | let iterationCounter = 0; 9 | 10 | let current: HTMLElement | null = baseElement; 11 | 12 | try { 13 | do { 14 | iterationCounter += 1; 15 | 16 | if (__DEV__) { 17 | if (iterationCounter > MAX_LOOP_ELEMENTS_TO_WARN) { 18 | throw new Error( 19 | `getParentElm: DFlex detected performance issues while iterating to find the nearest parent element. ` + 20 | `The element with ID ${baseElement.id} may have an excessive number of ancestors. ` + 21 | `Iteration count: ${iterationCounter}.`, 22 | ); 23 | } 24 | } 25 | 26 | // Skip the same element `baseElement`. 27 | if (iterationCounter > 1) { 28 | // If the callback returns true, then we have found the parent element. 29 | if (cb(current)) { 30 | iterationCounter = 0; 31 | return current; 32 | } 33 | } 34 | 35 | current = current.parentElement; 36 | } while (current !== null && !current.isSameNode(document.body)); 37 | } catch (e) { 38 | if (__DEV__) { 39 | // eslint-disable-next-line no-console 40 | console.error(e); 41 | } 42 | } finally { 43 | iterationCounter = 0; 44 | } 45 | 46 | return null; 47 | } 48 | 49 | export default getParentElm; 50 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/environment/getSelection.ts: -------------------------------------------------------------------------------- 1 | function getSelection() { 2 | return window.getSelection(); 3 | } 4 | 5 | export default getSelection; 6 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/environment/index.ts: -------------------------------------------------------------------------------- 1 | export { default as getSelection } from "./getSelection"; 2 | export { default as getParentElm } from "./getParentElm"; 3 | export { default as canUseDOM } from "./canUseDOM"; 4 | export { default as updateElmDatasetGrid } from "./updateElmDatasetGrid"; 5 | export { updateIndexAttr, updateDOMAttr } from "./updateDOMAttr"; 6 | export { default as getElmBoxRect } from "./getElmBoxRect"; 7 | 8 | export { autoCleanupAllRAFs, DFlexCreateRAF } from "./DFlexRAF"; 9 | export type { RAFFunction, RAFCleanup } from "./DFlexRAF"; 10 | 11 | export { autoCleanupAllTimeouts, DFlexCreateTimeout } from "./DFlexTimeout"; 12 | export type { 13 | TimeoutCleanup, 14 | TimeoutFunction, 15 | IsThrottledFunction, 16 | } from "./DFlexTimeout"; 17 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/environment/updateDOMAttr.ts: -------------------------------------------------------------------------------- 1 | function updateDOMAttr( 2 | DOM: HTMLElement, 3 | name: T, 4 | isRemove: boolean, 5 | addPrefix: boolean = true, 6 | value: string | undefined = "true", 7 | ): void { 8 | // Keep dragged attribute as is. 9 | const attrName = addPrefix ? `data-${name}` : name; 10 | 11 | if (isRemove) { 12 | if (__DEV__) { 13 | if (!DOM.hasAttribute(attrName)) { 14 | // eslint-disable-next-line no-console 15 | console.error(`Attribute ${attrName} does not exist on the element.`); 16 | } 17 | } 18 | 19 | DOM.removeAttribute(attrName); 20 | 21 | return; 22 | } 23 | 24 | DOM.setAttribute(attrName, value); 25 | } 26 | 27 | function updateIndexAttr(DOM: HTMLElement, value: number): void { 28 | updateDOMAttr(DOM, "index", false, true, `${value}`); 29 | } 30 | 31 | export { updateDOMAttr, updateIndexAttr }; 32 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/environment/updateElmDatasetGrid.ts: -------------------------------------------------------------------------------- 1 | import { PointNum } from "../Point"; 2 | 3 | function updateElmDatasetGrid(DOM: HTMLElement, grid: PointNum) { 4 | DOM.dataset.devX = `${grid.x}`; 5 | DOM.dataset.devY = `${grid.y}`; 6 | } 7 | 8 | export default updateElmDatasetGrid; 9 | -------------------------------------------------------------------------------- /packages/dflex-utils/src/types.ts: -------------------------------------------------------------------------------- 1 | export interface Dimensions { 2 | height: number; 3 | width: number; 4 | } 5 | 6 | export type Direction = 1 | -1; 7 | 8 | /** Single Axis. */ 9 | export type Axis = "x" | "y"; 10 | 11 | /** Bi-directional Axis. */ 12 | export type Axes = Axis | "z"; 13 | 14 | export const BOTH_AXIS: readonly Axis[] = Object.freeze(["x", "y"]); 15 | 16 | export type CubicBezier = 17 | | "ease" 18 | | "ease-in" 19 | | "ease-out" 20 | | "ease-in-out" 21 | | "linear"; 22 | 23 | export type AnimationOpts = { 24 | /** 25 | * The easing function to use for the animation. 26 | * Specifies the speed curve of the animation. 27 | * Example values: 'linear', 'ease-in', 'ease-out', 'ease-in-out'. 28 | * (Default: 'ease-in') 29 | */ 30 | easing: CubicBezier; 31 | 32 | /** 33 | * The duration of the animation in milliseconds. 34 | * Specifies how long the animation should take to complete. 35 | * (Default: 'dynamic') 36 | */ 37 | duration: number | "dynamic"; 38 | } | null; 39 | 40 | export type CSSStyle = Record; 41 | 42 | export type CSSClass = string; 43 | 44 | export type CSS = CSSClass | CSSStyle; 45 | -------------------------------------------------------------------------------- /packages/dflex-utils/test/BoxNum.test.ts: -------------------------------------------------------------------------------- 1 | import { BoxNum } from "../src/Box"; 2 | 3 | describe("BoxNum", () => { 4 | let box: BoxNum; 5 | 6 | beforeEach(() => { 7 | box = new BoxNum(0, 10, 10, 0); 8 | }); 9 | 10 | describe("isIntersect", () => { 11 | it("should return true if the box intersects with another box", () => { 12 | const otherBox = new BoxNum(5, 15, 15, 5); 13 | const result = box.isBoxIntersect(otherBox); 14 | expect(result).toBe(true); 15 | }); 16 | 17 | it("should return false if the box does not intersect with another box", () => { 18 | const otherBox = new BoxNum(20, 30, 30, 20); 19 | const result = box.isBoxIntersect(otherBox); 20 | expect(result).toBe(false); 21 | }); 22 | }); 23 | }); 24 | -------------------------------------------------------------------------------- /packages/dflex-utils/test/eventDebounce.test.ts: -------------------------------------------------------------------------------- 1 | import { eventDebounce } from "../src"; 2 | 3 | jest.useFakeTimers(); 4 | 5 | describe("eventDebounce", () => { 6 | let listener: jest.Mock; 7 | let debounceControl: ReturnType; 8 | 9 | beforeEach(() => { 10 | listener = jest.fn(); 11 | debounceControl = eventDebounce(listener); 12 | }); 13 | 14 | afterEach(() => { 15 | debounceControl.pause(); 16 | jest.clearAllTimers(); 17 | }); 18 | 19 | test("Executes the listener immediately if immediate flag is set", () => { 20 | debounceControl = eventDebounce(listener, true); 21 | debounceControl(); 22 | expect(listener).toHaveBeenCalledTimes(0); 23 | jest.runAllTimers(); 24 | expect(listener).toHaveBeenCalledTimes(1); 25 | }); 26 | 27 | test("Executes the listener after the throttle period", () => { 28 | debounceControl(); 29 | expect(listener).not.toHaveBeenCalled(); 30 | jest.runAllTimers(); 31 | expect(listener).toHaveBeenCalledTimes(1); 32 | }); 33 | 34 | test("Executes the last call one time", () => { 35 | debounceControl(); 36 | debounceControl(); 37 | debounceControl(); 38 | debounceControl(); 39 | debounceControl(); 40 | jest.runAllTimers(); 41 | expect(listener).toHaveBeenCalledTimes(1); 42 | }); 43 | 44 | test("Pauses and resumes the debounce control", () => { 45 | debounceControl(); 46 | debounceControl.pause(); 47 | jest.runAllTimers(); 48 | expect(listener).not.toHaveBeenCalled(); 49 | debounceControl.resume(); 50 | jest.runAllTimers(); 51 | expect(listener).toHaveBeenCalledTimes(1); 52 | }); 53 | }); 54 | -------------------------------------------------------------------------------- /packages/dflex-utils/test/getParsedElmTransform.test.ts: -------------------------------------------------------------------------------- 1 | import { getParsedElmTransform } from "../src"; 2 | 3 | describe("getParsedElmTransform", () => { 4 | test("returns null for empty transform value", () => { 5 | const element = document.createElement("div"); 6 | element.style.transform = ""; 7 | 8 | const result = getParsedElmTransform(element); 9 | 10 | expect(result).toBeNull(); 11 | }); 12 | 13 | test("returns null for element without transform property", () => { 14 | const element = document.createElement("div"); 15 | 16 | const result = getParsedElmTransform(element); 17 | 18 | expect(result).toBeNull(); 19 | }); 20 | 21 | test("returns null for transform with other functions", () => { 22 | const element = document.createElement("div"); 23 | element.style.transform = "scale(2) rotate(45deg)"; 24 | 25 | const result = getParsedElmTransform(element); 26 | 27 | expect(result).toBeNull(); 28 | }); 29 | 30 | test("returns parsed transform matrix", () => { 31 | const element = document.createElement("div"); 32 | element.style.transform = "matrix(1, 0, 0, 1, 10, 20)"; 33 | 34 | const result = getParsedElmTransform(element); 35 | 36 | expect(result).toEqual([10, 20]); 37 | }); 38 | 39 | test("returns parsed transform matrix from multiple transform functions", () => { 40 | const element = document.createElement("div"); 41 | element.style.transform = 42 | "translateX(50px) scale(2) matrix(1, 0, 0, 1, 30, 40)"; 43 | 44 | const result = getParsedElmTransform(element); 45 | 46 | expect(result).toEqual([30, 40]); 47 | }); 48 | }); 49 | -------------------------------------------------------------------------------- /packages/dflex-utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*.ts", "../../dflex-env.d.ts"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "outDir": "./types", 7 | "rootDir": "./src" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["eslint-config-dflex-react"], 3 | }; 4 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022, Jalal Maskoun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/cypress.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "cypress"; 2 | 3 | export default defineConfig({ 4 | e2e: { 5 | testIsolation: false, 6 | video: false, 7 | }, 8 | }); 9 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/cypress/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["plugin:cypress/recommended"], 3 | rules: { 4 | "import/no-extraneous-dependencies": ["error", { devDependencies: true }], 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/cypress/fixtures/example.json: -------------------------------------------------------------------------------- 1 | {} 2 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/cypress/plugins/index.js: -------------------------------------------------------------------------------- 1 | /// 2 | // *********************************************************** 3 | // This example plugins/index.js can be used to load plugins 4 | // 5 | // You can change the location of this file or turn off loading 6 | // the plugins file with the 'pluginsFile' configuration option. 7 | // 8 | // You can read more here: 9 | // https://on.cypress.io/plugins-guide 10 | // *********************************************************** 11 | 12 | // This function is called when a project is opened or re-opened (e.g. due to 13 | // the project's config changing) 14 | 15 | /** 16 | * @type {Cypress.PluginConfig} 17 | */ 18 | module.exports = (on, config) => { 19 | // eslint-disable-next-line global-require 20 | // require("@cypress/code-coverage/task")(on, config); 21 | 22 | // add other tasks to be registered here 23 | 24 | // IMPORTANT to return the config object 25 | // with the any changed environment variables 26 | return config; 27 | }; 28 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/cypress/support/commands.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dflex-js/dflex/031d202c72fd4a14a49c6444bdbeeb6baf7a72ca/playgrounds/dflex-dnd-playground/cypress/support/commands.js -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/cypress/support/e2e.js: -------------------------------------------------------------------------------- 1 | // Import commands.js using ES2015 syntax: 2 | import "./commands"; 3 | // import "@cypress/code-coverage/support"; 4 | 5 | // Alternatively you can use CommonJS syntax: 6 | // require('./commands') 7 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/cypress/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["**/*.ts"], 3 | "compilerOptions": { 4 | "target": "es5", 5 | "lib": ["es5", "dom"], 6 | "types": ["cypress"] 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dflex-js/dflex/031d202c72fd4a14a49c6444bdbeeb6baf7a72ca/playgrounds/dflex-dnd-playground/favicon.ico -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | DFlex DnD Playground 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dflex-dnd-playground", 3 | "version": "0.4.3", 4 | "license": "MIT", 5 | "private": true, 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@dflex/dnd": "workspace:^3.10.6" 13 | }, 14 | "devDependencies": { 15 | "cypress": "^13.4.0", 16 | "dflex-e2e-utils": "workspace:^1.0.4", 17 | "eslint": "^8.52.0", 18 | "eslint-config-dflex-react": "workspace:*", 19 | "eslint-plugin-cypress": "^2.15.1" 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/depth/index.ts: -------------------------------------------------------------------------------- 1 | import NestedList from "./NestedList"; 2 | 3 | export default NestedList; 4 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/depth/readme.md: -------------------------------------------------------------------------------- 1 | # Multiple depth 2 | 3 | Waiting for development and refactor. 4 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/essential/component-based-event/index.ts: -------------------------------------------------------------------------------- 1 | import DFlexDnDComponent from "./Core"; 2 | 3 | export default DFlexDnDComponent; 4 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/essential/component-based-event/readme.md: -------------------------------------------------------------------------------- 1 | # Component Based Event 2 | 3 | Drag and Drop happens at the component level. In this case, the event is 4 | triggered from the component and the parent is unaware of any event. 5 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/essential/container-based-event/Core.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-console */ 2 | /* eslint-disable import/no-extraneous-dependencies */ 3 | 4 | import React from "react"; 5 | 6 | import { store } from "@dflex/dnd"; 7 | 8 | interface Props { 9 | component: string | React.JSXElementConstructor; 10 | id: string; 11 | children: React.ReactNode; 12 | depth: number; 13 | } 14 | 15 | const isCI = import.meta.env.MODE === "CI"; 16 | 17 | const Core = ({ 18 | component: Component = "div", 19 | id, 20 | children, 21 | depth, 22 | ...rest 23 | }: Props) => { 24 | const ref = React.useRef(null) as React.MutableRefObject; 25 | 26 | React.useEffect(() => { 27 | if (ref.current) { 28 | store.register({ id, depth, animation: isCI ? null : undefined }); 29 | } 30 | 31 | return () => { 32 | store.unregister(id); 33 | }; 34 | }, [ref]); 35 | 36 | return ( 37 | 38 | {children} 39 | 40 | ); 41 | }; 42 | 43 | export default Core; 44 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/essential/container-based-event/index.ts: -------------------------------------------------------------------------------- 1 | import Core from "./Core"; 2 | import Container from "./Container"; 3 | 4 | export { Core, Container }; 5 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/essential/container-based-event/readme.md: -------------------------------------------------------------------------------- 1 | # Container Based Event 2 | 3 | Drag and Drop happens at the container level. The component is only responsible for the store registration. 4 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/essential/index.ts: -------------------------------------------------------------------------------- 1 | import { ComponentBasedEvent, ContainerBasedEvent } from "./Lists"; 2 | 3 | export { ComponentBasedEvent, ContainerBasedEvent }; 4 | 5 | export { default as ListMigration } from "./ListMigration"; 6 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/gap/bigGap.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import DFlexDnDComponent from "../DFlexDnDComponent"; 4 | 5 | const BigGap = () => { 6 | const tasks = [ 7 | { id: "mtg", msg: "Meet with Laura" }, 8 | { id: "org", msg: "Organize weekly meetup" }, 9 | 10 | { id: "gym", msg: "Hit the gym" }, 11 | ]; 12 | 13 | return ( 14 |
15 |
16 |
    17 | {tasks.map(({ msg, id }) => ( 18 | 29 | {msg} 30 | 31 | ))} 32 |
33 |
34 |
35 | ); 36 | }; 37 | 38 | export default BigGap; 39 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/gap/index.ts: -------------------------------------------------------------------------------- 1 | import BigGap from "./bigGap"; 2 | 3 | export default BigGap; 4 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as Depth1 } from "./depth"; 2 | 3 | export { default as BigGap } from "./gap"; 4 | 5 | export { 6 | AllRestrictedContainer, 7 | SomeRestrictedContainer, 8 | SelRestricted, 9 | } from "./restrictions"; 10 | 11 | export { TodoList, TodoListWithEvents, TodoListWithReadonly } from "./todo"; 12 | 13 | export { 14 | ComponentBasedEvent, 15 | ContainerBasedEvent, 16 | ListMigration, 17 | } from "./essential"; 18 | 19 | export { 20 | ScrollMultiLists, 21 | ScrollablePage, 22 | ExtendedList, 23 | WindowedDualList, 24 | } from "./scroll"; 25 | 26 | export { StreamInterval, StreamNewELm, StreamIncremental } from "./stream"; 27 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/restrictions/AllRestrictedContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import DFlexDnDComponent from "../DFlexDnDComponent"; 3 | 4 | const AllRestrictedContainer = () => { 5 | const items = [ 6 | { 7 | id: "item-rest-1", 8 | item: "1", 9 | style: { width: "10rem", height: "2rem", marginLeft: "92px" }, 10 | }, 11 | { 12 | id: "item-rest-2", 13 | item: "2", 14 | style: { width: "12rem", height: "2.5rem", marginLeft: "22px" }, 15 | }, 16 | { 17 | id: "item-rest-3", 18 | item: "3", 19 | style: { width: "16rem", height: "1rem", marginLeft: "32px" }, 20 | }, 21 | { 22 | id: "item-rest-4", 23 | item: "4", 24 | style: { width: "13rem", height: "3rem", marginLeft: "12px" }, 25 | }, 26 | { 27 | id: "item-rest-5", 28 | item: "5", 29 | style: { width: "18rem", height: "0.25rem" }, 30 | }, 31 | ]; 32 | 33 | return ( 34 |
35 |
36 |
    37 | {items.map(({ id, style, item }) => ( 38 | 57 | {item} 58 | 59 | ))} 60 |
61 |
62 |
63 | ); 64 | }; 65 | 66 | export default AllRestrictedContainer; 67 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/restrictions/index.ts: -------------------------------------------------------------------------------- 1 | export { default as AllRestrictedContainer } from "./AllRestrictedContainer"; 2 | export { default as SomeRestrictedContainer } from "./SomeRestrictedContainer"; 3 | export { default as SelRestricted } from "./SelRestricted"; 4 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/scroll/ExtendedList.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-array-index-key */ 2 | import React from "react"; 3 | 4 | import { store } from "@dflex/dnd"; 5 | import DFlexDnDComponent from "../DFlexDnDComponent"; 6 | 7 | /** 8 | * Extended List Component 9 | * 10 | * A list with overflow and a hundred elements. This playground is intended to: 11 | * 1- Check elements visibility while scrolling. 12 | * 2- Check the scroll performance for a list with an unusual amount of elements. 13 | */ 14 | const ExtendedList = () => { 15 | const tasks = []; 16 | 17 | for (let i = 1; i <= 100; i += 1) { 18 | const uni = `${i}-extended`; 19 | 20 | tasks.push({ id: uni, key: uni, task: `${i}` }); 21 | } 22 | 23 | const handleKeyPress = (e: KeyboardEvent) => { 24 | if (e.key === "g" || e.key === "G") { 25 | // Pick random id from the list. 26 | const siblings = store.getSiblingsByID(`${1}-extended`); 27 | 28 | const serializedElms = siblings.map((id) => 29 | store.getSerializedElement(id), 30 | ); 31 | 32 | // Log the serialized elements as a table 33 | // eslint-disable-next-line no-console 34 | console.log(serializedElms); 35 | } 36 | }; 37 | 38 | React.useEffect(() => { 39 | document.addEventListener("keydown", handleKeyPress); 40 | 41 | return () => { 42 | document.removeEventListener("keydown", handleKeyPress); 43 | }; 44 | }, [handleKeyPress]); 45 | 46 | return ( 47 |
48 |
49 |
    50 | {tasks.map(({ task, id, key }) => ( 51 | 61 | {task} 62 | 63 | ))} 64 |
65 |
66 |
67 | ); 68 | }; 69 | 70 | export default ExtendedList; 71 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/scroll/ScrollablePage.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import DFlexDnDComponent from "../DFlexDnDComponent"; 4 | 5 | /** 6 | * A non-scrollable list with overflown page. 7 | * @returns 8 | */ 9 | const ScrollablePage = () => { 10 | return ( 11 |
12 |
13 |
    14 | {Array.from({ length: 4 }).map((_, i) => ( 15 | 25 | {`${i}`} 26 | 27 | ))} 28 |
29 |
30 |
31 | ); 32 | }; 33 | 34 | export default ScrollablePage; 35 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/scroll/index.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | export { default as ScrollMultiLists } from "./multiLists"; 3 | export { default as ScrollablePage } from "./ScrollablePage"; 4 | export { default as ExtendedList } from "./ExtendedList"; 5 | export { default as WindowedDualList } from "./WindowedDualList"; 6 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/scroll/multiLists.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-array-index-key */ 2 | import React from "react"; 3 | 4 | import DFlexDnDComponent from "../DFlexDnDComponent"; 5 | 6 | const MultiLists = () => { 7 | const tasks1 = []; 8 | const tasks2 = []; 9 | const tasks3 = []; 10 | 11 | for (let i = 1; i <= 20; i += 1) { 12 | const id = `scroll-c1-${i}`; 13 | tasks1.push({ id, key: id, task: `c1-${i}` }); 14 | } 15 | 16 | for (let i = 1; i <= 100; i += 1) { 17 | const id = `scroll-c2-${i}`; 18 | tasks2.push({ id, key: id, task: `c2-${i}` }); 19 | } 20 | 21 | for (let i = 1; i <= 10; i += 1) { 22 | const id = `scroll-c3-${i}`; 23 | tasks3.push({ id, key: id, task: `c3-${i}` }); 24 | } 25 | 26 | return ( 27 |
28 | {[{ tasks: tasks1 }, { tasks: tasks2 }, { tasks: tasks3 }].map( 29 | ({ tasks }, i) => ( 30 |
    31 | {tasks.map(({ task, id, key }) => ( 32 | 37 | {task} 38 | 39 | ))} 40 |
41 | ), 42 | )} 43 |
44 | ); 45 | }; 46 | 47 | export default MultiLists; 48 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/stream/StreamInterval.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import DFlexDnDComponent from "../DFlexDnDComponent"; 4 | 5 | const NUM_INTERVAL = 4; 6 | 7 | const rowMap = { 8 | 0: 5, 9 | 1: 10, 10 | 2: 15, 11 | 3: 20, 12 | 4: 25, 13 | }; 14 | 15 | const StreamInterval = () => { 16 | const [todo, updateTodo] = React.useState< 17 | { 18 | msg: string; 19 | id: string; 20 | key: string; 21 | }[] 22 | >([{ msg: "1", id: "1", key: "1" }]); 23 | 24 | const intervalID = React.useRef>(); 25 | 26 | const numberOfUpdates = React.useRef(0); 27 | 28 | React.useEffect(() => { 29 | intervalID.current = setInterval(() => { 30 | // @ts-ignore - TODO: Fix the type issue. 31 | const arr = new Array(rowMap[numberOfUpdates.current]) 32 | 33 | .fill(null) 34 | .map((_v, i) => { 35 | const unique = `${numberOfUpdates.current}-${i}`; 36 | 37 | return { 38 | msg: unique, 39 | id: unique, 40 | key: unique, 41 | }; 42 | }); 43 | 44 | updateTodo(arr); 45 | 46 | numberOfUpdates.current += 1; 47 | }, 1000); 48 | 49 | return () => { 50 | clearInterval(intervalID.current); 51 | }; 52 | }, []); 53 | 54 | React.useEffect(() => { 55 | if (numberOfUpdates.current === NUM_INTERVAL) { 56 | clearInterval(intervalID.current); 57 | } 58 | }, [numberOfUpdates.current]); 59 | 60 | return ( 61 |
62 |
63 |
    64 | {todo.map(({ msg, id, key }) => ( 65 | 70 | {msg} 71 | 72 | ))} 73 |
74 |
75 |
76 | ); 77 | }; 78 | 79 | export default StreamInterval; 80 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/stream/index.ts: -------------------------------------------------------------------------------- 1 | export { default as StreamInterval } from "./StreamInterval"; 2 | export { default as StreamNewELm } from "./StreamNewELm"; 3 | export { default as StreamIncremental } from "./StreamIncremental"; 4 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/stream/readme.md: -------------------------------------------------------------------------------- 1 | # Streaming inside the same container 2 | 3 | The parent container is the same while children keep updating. 4 | 5 | - `StreamInterval` Automatically updating the children with interval. Each time totally new children. 6 | 7 | - `StreamNewELm` Updating the children is triggered by keyboard event. Each time totally new children. 8 | 9 | - `StreamIncremental` Updating the children is triggered by keyboard event. Persist the old children and increment to the same container. 10 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/todo/TodoList.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | // import type { LayoutStateEvent } from "@dflex/dnd"; 3 | 4 | import DFlexDnDComponent from "../DFlexDnDComponent"; 5 | 6 | const TodoListWithEvents = () => { 7 | const tasks = [ 8 | { id: "mtg", msg: "Meet with Laura", style: { height: "3rem" } }, 9 | { id: "org", msg: "Organize weekly meetup", style: { height: "6.5rem" } }, 10 | { 11 | id: "proj", 12 | msg: "Continue working on the project", 13 | style: { height: "5rem" }, 14 | }, 15 | { id: "gym", msg: "Hit the gym", style: { height: "4.5rem" } }, 16 | ]; 17 | 18 | return ( 19 |
20 |
21 |
    22 | {tasks.map(({ msg, id, style }) => ( 23 | 34 | {msg} 35 | 36 | ))} 37 |
38 |
39 |
40 | ); 41 | }; 42 | 43 | export default TodoListWithEvents; 44 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/todo/TodoListWithEvents.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import DFlexDnDComponent from "../DFlexDnDComponent"; 4 | 5 | const TodoListWithEvents = () => { 6 | const tasks = [ 7 | { id: "mtg", msg: "Meet with Laura" }, 8 | { id: "org", msg: "Organize weekly meetup" }, 9 | 10 | { id: "gym", msg: "Hit the gym" }, 11 | ]; 12 | 13 | return ( 14 |
15 |
16 |
    17 | {tasks.map(({ msg, id }) => ( 18 | 29 | {msg} 30 | 31 | ))} 32 |
33 |
34 |
35 | ); 36 | }; 37 | 38 | export default TodoListWithEvents; 39 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/todo/TodoListWithReadonly.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import DFlexDnDComponent from "../DFlexDnDComponent"; 4 | 5 | const TodoListWithReadonly = () => { 6 | const tasks = [ 7 | { 8 | readonly: false, 9 | id: "interactive-1", 10 | msg: "Interactive task 1", 11 | style: { height: "4.5rem" }, 12 | }, 13 | { 14 | readonly: true, 15 | id: "readonly-1", 16 | msg: "Readonly task 1", 17 | style: { height: "4.5rem" }, 18 | }, 19 | { 20 | readonly: false, 21 | id: "interactive-2", 22 | msg: "Interactive task 2", 23 | style: { height: "4.5rem" }, 24 | }, 25 | { 26 | readonly: true, 27 | id: "readonly-2", 28 | msg: "Readonly task 2", 29 | style: { height: "4.5rem" }, 30 | }, 31 | ]; 32 | 33 | return ( 34 |
35 |
36 |
    37 | {tasks.map(({ msg, id, readonly, style }) => ( 38 | 49 | {msg} 50 | 51 | ))} 52 |
53 |
54 |
55 | ); 56 | }; 57 | export default TodoListWithReadonly; 58 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/components/todo/index.ts: -------------------------------------------------------------------------------- 1 | export { default as TodoList } from "./TodoList"; 2 | export { default as TodoListWithReadonly } from "./TodoListWithReadonly"; 3 | export { default as TodoListWithEvents } from "./TodoListWithEvents"; 4 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | 4 | import "./index.css"; 5 | import App from "./App"; 6 | 7 | const container = document.getElementById("root"); 8 | const root = createRoot(container!); 9 | root.render(); 10 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/tests/core/cases/orphan.outList.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | test, 3 | expect, 4 | Page, 5 | Locator, 6 | BrowserContext, 7 | Browser, 8 | } from "@playwright/test"; 9 | import { 10 | DraggedRect, 11 | getDraggedRect, 12 | initialize, 13 | moveDragged, 14 | } from "dflex-e2e-utils"; 15 | 16 | test.describe.serial("Orphan dragged won't break", async () => { 17 | let page: Page; 18 | 19 | const draggedID = "#id-1"; 20 | 21 | let draggedRect: DraggedRect; 22 | 23 | let elmP1: Locator; 24 | 25 | let context: BrowserContext; 26 | let activeBrowser: Browser; 27 | 28 | test.beforeAll(async ({ browser, browserName, baseURL }) => { 29 | activeBrowser = browser; 30 | context = await activeBrowser.newContext(); 31 | 32 | page = await context.newPage(); 33 | initialize(page, browserName); 34 | await page.goto(baseURL!); 35 | 36 | elmP1 = page.locator(draggedID); 37 | }); 38 | 39 | test.afterAll(async () => { 40 | await page.close(); 41 | await context.close(); 42 | // await activeBrowser.close(); 43 | }); 44 | 45 | test("Moving dragged element list horizontally and vertically", async () => { 46 | draggedRect = await getDraggedRect(elmP1); 47 | await moveDragged(draggedRect.width, draggedRect.height); 48 | }); 49 | 50 | test("Triggers mouseup", async () => { 51 | await page.dispatchEvent(draggedID, "mouseup", { 52 | button: 0, 53 | force: true, 54 | }); 55 | }); 56 | 57 | test("Dragged is back to its origin", async () => { 58 | await expect(elmP1).toHaveCSS("transform", "matrix(1, 0, 0, 1, 0, 0)"); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/tests/features/zIndexVisualTest.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | expect, 3 | Page, 4 | Locator, 5 | BrowserContext, 6 | Browser, 7 | } from "@playwright/test"; 8 | 9 | import { 10 | DFlexPageTest as test, 11 | // assertChildrenOrderIDs, 12 | // assertConsoleMsg, 13 | // assertDefaultChildrenIndex, 14 | // DraggedRect, 15 | getDraggedRect, 16 | initialize, 17 | // invokeKeyboardAndAssertEmittedMsg, 18 | moveDragged, 19 | } from "dflex-e2e-utils"; 20 | 21 | test.describe("DOM Mirror element is above both containers", async () => { 22 | let page: Page; 23 | let context: BrowserContext; 24 | let activeBrowser: Browser; 25 | 26 | // Second container. 27 | // let elmAParent: Locator; 28 | let elmBParent: Locator; 29 | let elmA1: Locator; 30 | 31 | test.skip( 32 | ({ browserName }) => browserName !== "chromium", 33 | "No need to test it for multiple browsers.", 34 | ); 35 | 36 | test.skip( 37 | process.platform === "darwin" || process.platform === "linux", 38 | "Skip the test on Mac and Linux devices.", 39 | ); 40 | 41 | test.beforeAll(async ({ browser, browserName }) => { 42 | activeBrowser = browser; 43 | 44 | context = await activeBrowser.newContext(); 45 | page = await context.newPage(); 46 | initialize(page, browserName, 50); 47 | await page.goto("/windowed-dual-list"); 48 | 49 | [, elmA1, elmBParent] = await Promise.all([ 50 | page.locator("#dflex_id_0"), 51 | page.locator("#list-a-1"), 52 | page.locator("#dflex_id_1"), 53 | ]); 54 | }); 55 | 56 | test.afterAll(async () => { 57 | await page.close(); 58 | await context.close(); 59 | // await activeBrowser.close(); 60 | }); 61 | 62 | test("Move elm1 outside its container to the (b) container", async () => { 63 | await getDraggedRect(elmA1); 64 | await moveDragged(-1, 230); 65 | await moveDragged(450, -1); 66 | }); 67 | 68 | test("dragged is above parent (b) container", async () => { 69 | await expect(elmBParent).toHaveScreenshot("parent-b.png"); 70 | }); 71 | }); 72 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/tests/features/zIndexVisualTest.spec.ts-snapshots/parent-b-chromium-win32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dflex-js/dflex/031d202c72fd4a14a49c6444bdbeeb6baf7a72ca/playgrounds/dflex-dnd-playground/tests/features/zIndexVisualTest.spec.ts-snapshots/parent-b-chromium-win32.png -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/tests/stabilizer/stabilizer.scroll.spec.ts: -------------------------------------------------------------------------------- 1 | import { 2 | Page, 3 | Locator, 4 | BrowserContext, 5 | Browser, 6 | expect, 7 | } from "@playwright/test"; 8 | 9 | import { 10 | DFlexPageTest as test, 11 | getDraggedRect, 12 | initialize, 13 | moveDragged, 14 | } from "dflex-e2e-utils"; 15 | 16 | test.describe("Stabilizer with scroll", async () => { 17 | let page: Page; 18 | let context: BrowserContext; 19 | let activeBrowser: Browser; 20 | 21 | let elm1: Locator; 22 | let elm2: Locator; 23 | 24 | test.beforeAll(async ({ browser, browserName }) => { 25 | activeBrowser = browser; 26 | 27 | context = await activeBrowser.newContext(); 28 | page = await context.newPage(); 29 | initialize(page, browserName, 50); 30 | await page.goto("/scrollable-page"); 31 | 32 | [elm1, elm2] = await Promise.all([ 33 | page.locator("#id_0"), 34 | page.locator("#id_1"), 35 | ]); 36 | }); 37 | 38 | test.afterAll(async () => { 39 | await page.close(); 40 | await context.close(); 41 | // await activeBrowser.close(); 42 | }); 43 | 44 | test("Scroll the page in Y axis", async () => { 45 | await page.evaluate(() => { 46 | window.scrollBy(0, 20); 47 | }); 48 | }); 49 | 50 | test("Move elm1 into elm2 position", async () => { 51 | await getDraggedRect(elm1); 52 | await moveDragged(-1, 50); 53 | }); 54 | 55 | test("elm2 transformed into elm1 position", async () => { 56 | // Due to variations in scroll behavior across different machines and browsers, 57 | // it's challenging to predict the exact transformation value. 58 | // Therefore, we cannot reliably determine the specific transformation value. 59 | await expect(elm2).not.toHaveCSS("transform", "matrix(1, 0, 0, 1, 0, 0)"); 60 | }); 61 | 62 | test("Move elm1 back into its position", async () => { 63 | await moveDragged(-1, 0); 64 | }); 65 | 66 | test("stabilizer doesn't prevent elm2 from returning into its position", async () => { 67 | await expect(elm2).toHaveCSS("transform", "matrix(1, 0, 0, 1, 0, 0)"); 68 | }); 69 | }); 70 | -------------------------------------------------------------------------------- /playgrounds/dflex-dnd-playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../dflex-env.d.ts"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "outDir": "./types", 7 | "rootDir": "./src" 8 | }, 9 | "references": [ 10 | { "path": "../../packages/dflex-dnd" }, 11 | { "path": "../dflex-e2e-utils" } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["eslint-config-dflex-react"], 3 | }; 4 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019-2020 Jalal Maskoun 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/README.md: -------------------------------------------------------------------------------- 1 |

2 | 3 | DFlex is a Javascript library for modern Drag and Drop apps 6 | 7 |

8 | 9 |

DFlex Draggable Playground

10 | 11 |

12 | 13 | Dflex build status 16 | 17 | 18 | number of opened pull requests 21 | 22 | 23 | DFlex last released version 26 | 27 | 28 | number of opened issues 31 | 32 | 33 | Dflex welcomes pull request 36 | 37 | 38 | Follow DFlex on twitter 41 | 42 |

43 | 44 | # DFlex Draggable Playground 45 | 46 | This playground has basic DFlex draggable implementation for React. 47 | 48 | ## Documentation 📖 49 | 50 | For documentation, more information about DFlex and a live demo, be sure to visit the DFlex website 51 | 52 | ## License 🤝 53 | 54 | DFlex is [MIT License](LICENSE). 55 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dflex-js/dflex/031d202c72fd4a14a49c6444bdbeeb6baf7a72ca/playgrounds/dflex-draggable-playground/favicon.ico -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DFlex Draggable Only Playground 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dflex-draggable-playground", 3 | "version": "0.3.2", 4 | "private": true, 5 | "license": "MIT", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview" 10 | }, 11 | "dependencies": { 12 | "@dflex/draggable": "workspace:^3.9.10" 13 | }, 14 | "devDependencies": { 15 | "eslint-config-dflex-react": "workspace:*", 16 | "dflex-e2e-utils": "workspace:^1.0.3" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/src/App.css: -------------------------------------------------------------------------------- 1 | .button-solo { 2 | position: relative; 3 | background-color: #22140e; 4 | border: none; 5 | border-radius: 10px; 6 | color: #fff; 7 | padding: 10px 20px; 8 | font-size: 16px; 9 | cursor: pointer; 10 | width: 150px; 11 | height: 40px; 12 | outline: none; 13 | transition: box-shadow 0.3s ease; 14 | display: flex; 15 | align-items: center; /* Center vertically */ 16 | } 17 | 18 | .button-solo:hover { 19 | background-color: #54341f; 20 | box-shadow: 0px 4px 8px rgba(0, 0, 0, 0.1); 21 | } 22 | 23 | .handler { 24 | position: absolute; 25 | top: 50%; 26 | right: 10px; 27 | transform: translateY(-50%); 28 | fill: #fff; 29 | } 30 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/src/App.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter, Routes, Route } from "react-router-dom"; 3 | import "./App.css"; 4 | 5 | import DraggableSolo from "./DraggableSolo"; 6 | import DraggableHandler from "./DraggableHandler"; 7 | 8 | function App() { 9 | return ( 10 | 11 | 12 | }> 13 | }> 14 | 15 | 16 | ); 17 | } 18 | 19 | export default App; 20 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/src/DraggableHandler/DraggableHandler.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | 3 | import React from "react"; 4 | 5 | import { store, Draggable } from "@dflex/draggable"; 6 | import HandlerSVG from "./HandlerSVG"; 7 | 8 | // shared dragged event 9 | let draggedEvent: Draggable; 10 | const id = "DFlex-draggable-with-handler"; 11 | 12 | const DraggableHandler = () => { 13 | const [isDragged, setIsDragged] = React.useState(false); 14 | 15 | React.useEffect(() => { 16 | store.register(id); 17 | 18 | return () => { 19 | store.unregister(id); 20 | }; 21 | }, []); 22 | 23 | const onMouseMove = (e: MouseEvent) => { 24 | if (draggedEvent) { 25 | const { clientX, clientY } = e; 26 | 27 | // Drag when mouse is moving! 28 | draggedEvent.dragAt(clientX, clientY); 29 | } 30 | }; 31 | 32 | const onMouseUp = () => { 33 | if (draggedEvent) { 34 | draggedEvent.endDragging(); 35 | 36 | document.removeEventListener("mouseup", onMouseUp); 37 | document.removeEventListener("mousemove", onMouseMove); 38 | 39 | setIsDragged(false); 40 | } 41 | }; 42 | 43 | const onMouseDown = (e: React.MouseEvent) => { 44 | const { button, clientX, clientY } = e; 45 | 46 | // avoid right mouse click and ensure id 47 | if (typeof button === "number" && button === 0) { 48 | if (id) { 49 | draggedEvent = new Draggable(id, { x: clientX, y: clientY }); 50 | 51 | document.addEventListener("mouseup", onMouseUp); 52 | document.addEventListener("mousemove", onMouseMove); 53 | 54 | setIsDragged(true); 55 | } 56 | } 57 | }; 58 | 59 | return ( 60 |
61 | {isDragged ? Being dragged! : Drag me!} 62 | 63 |
64 | ); 65 | }; 66 | 67 | export default DraggableHandler; 68 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/src/DraggableHandler/HandlerSVG.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | // eslint-disable-next-line no-unused-vars 4 | const HandlerSVG = (props: { onMouseDown: (e: React.MouseEvent) => void }) => ( 5 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | ); 30 | 31 | export default HandlerSVG; 32 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/src/DraggableHandler/index.tsx: -------------------------------------------------------------------------------- 1 | import DraggableHandler from "./DraggableHandler"; 2 | 3 | export default DraggableHandler; 4 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/src/DraggableSolo/DraggableSolo.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | 3 | import React from "react"; 4 | 5 | import { store, Draggable } from "@dflex/draggable"; 6 | 7 | // shared dragged event 8 | let dflexDraggable: Draggable; 9 | 10 | const id = "DFlex-draggable-solo"; 11 | 12 | const DraggableSolo = () => { 13 | const [isDragged, setIsDragged] = React.useState(false); 14 | 15 | React.useEffect(() => { 16 | store.register(id); 17 | 18 | return () => { 19 | store.unregister(id); 20 | }; 21 | }, []); 22 | 23 | const onMouseMove = (e: MouseEvent) => { 24 | if (dflexDraggable) { 25 | const { clientX, clientY } = e; 26 | 27 | // Drag when mouse is moving! 28 | dflexDraggable.dragAt(clientX, clientY); 29 | } 30 | }; 31 | 32 | const onMouseUp = () => { 33 | if (dflexDraggable) { 34 | dflexDraggable.endDragging(); 35 | 36 | document.removeEventListener("mouseup", onMouseUp); 37 | document.removeEventListener("mousemove", onMouseMove); 38 | 39 | setIsDragged(false); 40 | } 41 | }; 42 | 43 | const onMouseDown = (e: React.MouseEvent) => { 44 | const { button, clientX, clientY } = e; 45 | 46 | // Avoid right mouse click and ensure id 47 | if (typeof button === "number" && button === 0) { 48 | if (id) { 49 | dflexDraggable = new Draggable(id, { x: clientX, y: clientY }); 50 | 51 | document.addEventListener("mouseup", onMouseUp); 52 | document.addEventListener("mousemove", onMouseMove); 53 | 54 | setIsDragged(true); 55 | } 56 | } 57 | }; 58 | 59 | return ( 60 | 69 | ); 70 | }; 71 | 72 | export default DraggableSolo; 73 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/src/DraggableSolo/index.tsx: -------------------------------------------------------------------------------- 1 | import DraggableSolo from "./DraggableSolo"; 2 | 3 | export default DraggableSolo; 4 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 4 | 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | } 9 | 10 | code { 11 | font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', 12 | monospace; 13 | } 14 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/src/main.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { createRoot } from "react-dom/client"; 3 | 4 | import "./index.css"; 5 | import App from "./App"; 6 | 7 | const container = document.getElementById("root"); 8 | const root = createRoot(container!); 9 | root.render(); 10 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/tests/draggable.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | import { 3 | Page, 4 | BrowserContext, 5 | Browser, 6 | expect, 7 | Locator, 8 | } from "@playwright/test"; 9 | 10 | import { 11 | getDraggedRect, 12 | initialize, 13 | moveDragged, 14 | DFlexPageTest as test, 15 | } from "dflex-e2e-utils"; 16 | 17 | test.describe("Draggable only", async () => { 18 | let page: Page; 19 | let context: BrowserContext; 20 | let activeBrowser: Browser; 21 | let btn: Locator; 22 | 23 | test.beforeAll(async ({ browser, baseURL }) => { 24 | activeBrowser = browser; 25 | context = await activeBrowser.newContext(); 26 | page = await context.newPage(); 27 | await page.goto(baseURL!); 28 | }); 29 | 30 | test.afterAll(async () => { 31 | await page.close(); 32 | await context.close(); 33 | // await activeBrowser.close(); 34 | }); 35 | 36 | test("Drag the button", async ({ browserName }) => { 37 | initialize(page, browserName, 50); 38 | btn = page.locator("#DFlex-draggable-solo"); 39 | await getDraggedRect(btn); 40 | await moveDragged(180, 180); 41 | }); 42 | 43 | test("Drag has updated position", async () => { 44 | const textContent = await btn.textContent(); 45 | expect(textContent).toBe("Being dragged"); 46 | expect(btn).toHaveCSS("transform", "matrix(1, 0, 0, 1, 180, 180)"); 47 | }); 48 | 49 | test("Release the button", async () => { 50 | await page.dispatchEvent("#DFlex-draggable-solo", "mouseup", { 51 | button: 0, 52 | force: true, 53 | }); 54 | }); 55 | 56 | test("button text is back", async () => { 57 | const textContent = await btn.textContent(); 58 | expect(textContent).toBe("Drag me"); 59 | }); 60 | }); 61 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../dflex-env.d.ts"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "outDir": "./types", 7 | "rootDir": "./src" 8 | }, 9 | "references": [ 10 | { "path": "../../packages/dflex-draggable" }, 11 | { "path": "../dflex-e2e-utils" } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /playgrounds/dflex-draggable-playground/vite.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import { UserConfigExport, defineConfig } from "vite"; 3 | import path from "path"; 4 | import react from "@vitejs/plugin-react"; 5 | import { replaceCodePlugin } from "vite-plugin-replace"; 6 | 7 | const PORT = 3000; 8 | 9 | const moduleResolution = [ 10 | { 11 | find: "@dflex/utils", 12 | replacement: path.resolve("../../packages/dflex-utils/src/index.ts"), 13 | }, 14 | { 15 | find: "@dflex/core-instance", 16 | replacement: path.resolve( 17 | "../../packages/dflex-core-instance/src/index.ts", 18 | ), 19 | }, 20 | { 21 | find: "@dflex/store", 22 | replacement: path.resolve("../../packages/dflex-store/src/index.ts"), 23 | }, 24 | { 25 | find: "@dflex/dom-gen", 26 | replacement: path.resolve("../../packages/dflex-dom-gen/src/index.ts"), 27 | }, 28 | { 29 | find: "@dflex/draggable", 30 | replacement: path.resolve("../../packages/dflex-draggable/src/index.ts"), 31 | }, 32 | ]; 33 | 34 | const config: UserConfigExport = { 35 | plugins: [ 36 | react(), 37 | replaceCodePlugin({ 38 | replacements: [ 39 | { 40 | from: /__DEV__/g, 41 | to: "true", 42 | }, 43 | ], 44 | }), 45 | ], 46 | server: { 47 | port: PORT, 48 | }, 49 | preview: { 50 | port: PORT, 51 | }, 52 | resolve: { 53 | alias: moduleResolution, 54 | }, 55 | }; 56 | 57 | // https://vitejs.dev/config/ 58 | export default defineConfig(config); 59 | -------------------------------------------------------------------------------- /playgrounds/dflex-e2e-utils/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dflex-e2e-utils", 3 | "version": "1.0.4", 4 | "license": "MIT", 5 | "main": "src/index.ts", 6 | "private": true, 7 | "scripts": {}, 8 | "dependencies": { 9 | "@dflex/core-instance": "workspace:^3.10.3" 10 | }, 11 | "devDependencies": {} 12 | } 13 | -------------------------------------------------------------------------------- /playgrounds/dflex-e2e-utils/src/DFlexPageTest.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | import { test as base, expect } from "@playwright/test"; 3 | 4 | export const DFlexPageTest = base.extend({ 5 | page: async ({ page }, use) => { 6 | const messages: string[] = []; 7 | 8 | // Listen for console errors. 9 | page.on("console", (msg) => { 10 | // Ignore regular log messages; we are only interested in errors. 11 | if (msg.type() === "error") { 12 | messages.push(`[${msg.type()}] ${msg.text()}`); 13 | } 14 | }); 15 | 16 | // Listen for page errors. 17 | // Uncaught (in promise) TypeError + friends are page errors. 18 | page.on("pageerror", (error) => { 19 | messages.push(`[${error.name}] ${error.message}`); 20 | }); 21 | 22 | await use(page); 23 | 24 | // Fail the test if there are any console errors. 25 | expect(messages).toStrictEqual([]); 26 | }, 27 | }); 28 | 29 | export default DFlexPageTest; 30 | -------------------------------------------------------------------------------- /playgrounds/dflex-e2e-utils/src/constants.ts: -------------------------------------------------------------------------------- 1 | export const TransformTimeout = { 2 | timeout: 30000, 3 | }; 4 | 5 | const { PACKAGE_BUNDLE } = process.env; 6 | 7 | export const isProdBundle = PACKAGE_BUNDLE === "production"; 8 | 9 | export const DEVELOPMENT_ONLY_ASSERTION = 10 | "This assertion works with development bundle only"; 11 | -------------------------------------------------------------------------------- /playgrounds/dflex-e2e-utils/src/events.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | /* eslint-disable import/no-extraneous-dependencies */ 3 | import { ConsoleMessage, expect } from "@playwright/test"; 4 | 5 | async function assertMutationListenerEmittedMsg( 6 | msg: ConsoleMessage, 7 | FINAL_IDS: string[], 8 | ) { 9 | // TODO: 10 | // cast the type for `emittedMsg` 11 | const emittedMsg = await msg.args()[1].jsonValue(); 12 | 13 | const { type, payload } = emittedMsg; 14 | 15 | expect(type).toBe("mutation"); 16 | expect(payload).toEqual({ 17 | target: "ref: ", 18 | ids: FINAL_IDS, 19 | }); 20 | } 21 | 22 | export { assertMutationListenerEmittedMsg }; 23 | -------------------------------------------------------------------------------- /playgrounds/dflex-e2e-utils/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | initialize, 3 | getDraggedRect, 4 | moveDragged, 5 | invokeKeyboard, 6 | assertConsoleMutationListener, 7 | pressCKeyAndAssertEmittedMsg, 8 | getSerializedElementsAfterKeyPress, 9 | assertChildrenOrderIDs, 10 | getChildrenLength, 11 | assertDefaultChildrenIndex, 12 | assertChildrenGrid, 13 | } from "./utils"; 14 | 15 | export type { DraggedRect } from "./utils"; 16 | 17 | export { 18 | TransformTimeout, 19 | isProdBundle, 20 | DEVELOPMENT_ONLY_ASSERTION, 21 | } from "./constants"; 22 | 23 | export { DFlexPageTest } from "./DFlexPageTest"; 24 | 25 | export type { DOMGenKeysType, StorE2EType } from "./types"; 26 | -------------------------------------------------------------------------------- /playgrounds/dflex-e2e-utils/src/listeners.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/prefer-default-export */ 2 | /* eslint-disable import/no-extraneous-dependencies */ 3 | import { ConsoleMessage, expect } from "@playwright/test"; 4 | 5 | async function assertMutationListenerEmittedMsg( 6 | msg: ConsoleMessage, 7 | FINAL_IDS: string[], 8 | ) { 9 | // TODO: 10 | // cast the type for `emittedMsg` 11 | const emittedMsg = await msg.args()[1].jsonValue(); 12 | 13 | const { type, payload } = emittedMsg; 14 | 15 | expect(type).toBe("mutation"); 16 | expect(payload).toEqual({ 17 | target: "ref: ", 18 | ids: FINAL_IDS, 19 | }); 20 | } 21 | 22 | export { assertMutationListenerEmittedMsg }; 23 | -------------------------------------------------------------------------------- /playgrounds/dflex-e2e-utils/src/types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | DFlexParentContainer, 3 | DFlexScrollContainer, 4 | } from "@dflex/core-instance"; 5 | 6 | interface DOMGenKeysType { 7 | idsBySk: { 8 | [key: string]: string[]; 9 | }; 10 | branchesRegistry: { 11 | [key: string]: { 12 | [depth: string]: { 13 | ids: string[]; 14 | SK: string; 15 | }; 16 | }; 17 | }; 18 | SKByDepth: { 19 | [key: string]: string[]; 20 | }; 21 | } 22 | 23 | interface StorE2EType { 24 | containers: { 25 | [k: string]: DFlexParentContainer; 26 | }; 27 | scrolls: { 28 | [k: string]: DFlexScrollContainer; 29 | }; 30 | mutationObserverMap: { 31 | [k: string]: MutationObserver | null; 32 | }; 33 | } 34 | 35 | export type { DOMGenKeysType, StorE2EType }; 36 | -------------------------------------------------------------------------------- /playgrounds/dflex-e2e-utils/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../dflex-env.d.ts"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "outDir": "./types", 7 | "rootDir": "." 8 | }, 9 | "references": [ 10 | { "path": "../../packages/dflex-dnd" }, 11 | { "path": "../../packages/dflex-core-instance" } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | extends: ["eslint-config-dflex-react"], 3 | }; 4 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | 27 | # local env files 28 | .env*.local 29 | 30 | # vercel 31 | .vercel 32 | 33 | # typescript 34 | *.tsbuildinfo 35 | next-env.d.ts 36 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | // eslint-disable-next-line import/no-unresolved 3 | const webpack = require("webpack"); 4 | 5 | module.exports = 6 | process.env.NODE_ENV === "production" 7 | ? { 8 | reactStrictMode: true, 9 | typescript: { 10 | tsconfigPath: "./tsconfig.build.json", 11 | }, 12 | } 13 | : { 14 | reactStrictMode: true, 15 | typescript: { 16 | tsconfigPath: "./tsconfig.json", 17 | }, 18 | transpilePackages: ["@dflex/dnd"], 19 | webpack: (config) => { 20 | config.plugins.push( 21 | new webpack.DefinePlugin({ 22 | __DEV__: "true", 23 | }), 24 | ); 25 | 26 | return config; 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dflex-next-playground", 3 | "version": "0.1.4", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev -p 3002", 7 | "build": "next build", 8 | "start": "next start -p 3002", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@dflex/dnd": "workspace:^3.9.10", 13 | "@types/node": "^20.8.10", 14 | "@types/react": "^18.2.34", 15 | "@types/react-dom": "^18.2.14", 16 | "autoprefixer": "10.4.16", 17 | "classnames": "^2.3.2", 18 | "next": "14.0.1", 19 | "postcss": "8.4.31", 20 | "react": "^18.2.0", 21 | "react-dom": "^18.2.0", 22 | "tailwindcss": "3.3.5", 23 | "typescript": "^5.2.2" 24 | }, 25 | "devDependencies": { 26 | "dflex-e2e-utils": "workspace:^1.0.3", 27 | "eslint": "^8.52.0", 28 | "eslint-config-dflex-react": "workspace:*" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dflex-js/dflex/031d202c72fd4a14a49c6444bdbeeb6baf7a72ca/playgrounds/dflex-next-playground/public/favicon.ico -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/src/components/ClickableBox.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Link from "next/link"; 3 | 4 | function replaceSlashWithUnderscore(inputString: string): string { 5 | return inputString.replace(/\//g, "_"); 6 | } 7 | 8 | function ClickableBox({ 9 | link, 10 | title, 11 | desc, 12 | }: { 13 | link: string; 14 | title: string; 15 | desc?: string; 16 | }) { 17 | return ( 18 | 23 |

24 | {title} 25 | 26 | -> 27 | 28 |

29 |

{desc}

30 | 31 | ); 32 | } 33 | 34 | export default ClickableBox; 35 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/src/components/TodoContainer.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import cn from "classnames"; 3 | 4 | const TodoContainer = ({ 5 | id, 6 | children, 7 | className, 8 | isCenterV = true, 9 | isCenterH = true, 10 | }: { 11 | id: string; 12 | children: React.ReactNode; 13 | className?: string; 14 | isCenterH?: boolean; 15 | isCenterV?: boolean; 16 | }) => { 17 | const containerClasses = cn( 18 | "flex", 19 | "min-h-screen", 20 | "p-6", 21 | { 22 | "justify-center": isCenterV, 23 | "items-center": isCenterH, 24 | }, 25 | className, 26 | ); 27 | 28 | return ( 29 |
30 |
    31 | {children} 32 |
33 |
34 | ); 35 | }; 36 | 37 | export default TodoContainer; 38 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { default as TodoContainer } from "./TodoContainer"; 2 | export { default as TodoItem } from "./TodoItem"; 3 | export { default as ClickableBox } from "./ClickableBox"; 4 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/src/pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import "../styles/globals.css"; 3 | import type { AppProps } from "next/app"; 4 | import { store } from "@dflex/dnd"; 5 | 6 | export default function App({ Component, pageProps }: AppProps) { 7 | // Not required but for testing. 8 | React.useEffect(() => { 9 | return () => { 10 | store.destroy(); 11 | }; 12 | }, []); 13 | 14 | return ; 15 | } 16 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/src/pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Html, Head, Main, NextScript } from "next/document"; 3 | 4 | export default function Document() { 5 | return ( 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/src/pages/from-to/index.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-array-index-key */ 2 | import React from "react"; 3 | import { TodoItem, TodoContainer, ClickableBox } from "../../components"; 4 | 5 | const SymmetricList = () => { 6 | const tasks = Array.from({ length: 4 }); 7 | 8 | return ( 9 |
10 | 11 | {tasks.map((_, i) => ( 12 | 18 | {`main-list-${i}`} 19 | 20 | ))} 21 | 22 | 23 |
24 | ); 25 | }; 26 | 27 | export default SymmetricList; 28 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/src/pages/from-to/list.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-array-index-key */ 2 | import React from "react"; 3 | import { TodoItem, TodoContainer, ClickableBox } from "../../components"; 4 | 5 | const AsymmetricPage = () => { 6 | const tasks = Array.from({ length: 4 }); 7 | 8 | return ( 9 |
10 | 11 | {tasks.map((_, i) => ( 12 | 23 | {`sub-list-${i}`} 24 | 25 | ))} 26 | 27 | 28 |
29 | ); 30 | }; 31 | 32 | export default AsymmetricPage; 33 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ClickableBox } from "../components"; 3 | 4 | export default function Home() { 5 | return ( 6 |
7 |
11 | 16 | 21 | 26 | 31 | 36 | 41 |
42 |
43 | ); 44 | } 45 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/src/pages/list/asymmetric.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-array-index-key */ 2 | import React from "react"; 3 | import { TodoItem, TodoContainer } from "../../components"; 4 | 5 | const AsymmetricPage = () => { 6 | const tasks = [ 7 | { 8 | id: "async-meeting-laura", 9 | task: "Meet with Laura", 10 | style: { height: "3rem" }, 11 | }, 12 | { 13 | id: "async-weekly-meetup", 14 | task: "Organize the weekly meetup", 15 | style: { height: "6rem" }, 16 | }, 17 | { 18 | id: "async-project-work", 19 | task: "Work on the project", 20 | style: { height: "4rem" }, 21 | }, 22 | { 23 | id: "async-gym-session", 24 | task: "Go to the gym", 25 | style: { height: "3.5rem" }, 26 | }, 27 | ]; 28 | 29 | return ( 30 | 31 | {tasks.map(({ task, id }) => ( 32 | 33 | {task} 34 | 35 | ))} 36 | 37 | ); 38 | }; 39 | 40 | export default AsymmetricPage; 41 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/src/pages/list/scrollable-page-content.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-array-index-key */ 2 | import React from "react"; 3 | import { TodoItem, TodoContainer } from "../../components"; 4 | 5 | const SymmetricList = () => { 6 | const tasks = [ 7 | { id: "sym-mtg", task: "Meet with Laura" }, 8 | { id: "clean", task: "Clean the house" }, 9 | { 10 | id: "sym-shop", 11 | task: "Go Grocery shopping", 12 | }, 13 | { id: "sym-gym", task: "Go to the gym" }, 14 | ]; 15 | 16 | return ( 17 |
18 |
19 | 20 | {tasks.map(({ task, id }) => ( 21 | 22 | {task} 23 | 24 | ))} 25 | 26 |
27 | ); 28 | }; 29 | 30 | export default SymmetricList; 31 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/src/pages/list/symmetric.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-array-index-key */ 2 | import React from "react"; 3 | import { TodoItem, TodoContainer } from "../../components"; 4 | 5 | const SymmetricList = () => { 6 | const tasks = [ 7 | { id: "sym-mtg", task: "Meet with Laura" }, 8 | { id: "clean", task: "Clean the house" }, 9 | { 10 | id: "sym-shop", 11 | task: "Go Grocery shopping", 12 | }, 13 | { id: "sym-gym", task: "Go to the gym" }, 14 | ]; 15 | 16 | return ( 17 | 18 | {tasks.map(({ task, id }) => ( 19 | 20 | {task} 21 | 22 | ))} 23 | 24 | ); 25 | }; 26 | 27 | export default SymmetricList; 28 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/src/pages/list/transformation.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-array-index-key */ 2 | import React from "react"; 3 | import { TodoItem, TodoContainer } from "../../components"; 4 | 5 | const TransformationPage = () => { 6 | const tasks = [ 7 | { id: "trans-clean", task: "Clean the house" }, 8 | { 9 | id: "trans-shop", 10 | task: "Grocery shopping", 11 | }, 12 | { id: "trans-gym", task: "Hit the gym" }, 13 | ]; 14 | 15 | return ( 16 | 17 | {tasks.map(({ task, id }) => ( 18 | 28 | {task} 29 | 30 | ))} 31 | 32 | ); 33 | }; 34 | 35 | export default TransformationPage; 36 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/src/pages/scroll/extended.tsx: -------------------------------------------------------------------------------- 1 | /* eslint-disable react/no-array-index-key */ 2 | import React from "react"; 3 | 4 | import { store } from "@dflex/dnd"; 5 | import { TodoItem, TodoContainer } from "../../components"; 6 | 7 | /** 8 | * Extended List Component 9 | * 10 | * A list with overflow and a hundred elements. This playground is intended to: 11 | * 1- Check elements visibility while scrolling. 12 | * 2- Check the scroll performance for a list with an unusual amount of elements. 13 | */ 14 | const ExtendedList = () => { 15 | const tasks = []; 16 | 17 | for (let i = 1; i <= 100; i += 1) { 18 | const uni = `${i}-extended`; 19 | 20 | tasks.push({ id: uni, key: uni, task: `${i}` }); 21 | } 22 | 23 | const handleKeyPress = (e: KeyboardEvent) => { 24 | if (e.key === "g" || e.key === "G") { 25 | const serializedElms = store.getSerializedElements(`${1}-extended`); 26 | 27 | // eslint-disable-next-line no-console 28 | console.log(serializedElms); 29 | } 30 | }; 31 | 32 | React.useEffect(() => { 33 | document.addEventListener("keydown", handleKeyPress); 34 | 35 | return () => { 36 | document.removeEventListener("keydown", handleKeyPress); 37 | }; 38 | }, [handleKeyPress]); 39 | 40 | return ( 41 | 42 | {tasks.map(({ task, id, key }) => ( 43 | 53 | {task} 54 | 55 | ))} 56 | 57 | ); 58 | }; 59 | 60 | export default ExtendedList; 61 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/src/styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/tailwind.config.ts: -------------------------------------------------------------------------------- 1 | import type { Config } from "tailwindcss"; 2 | 3 | const config: Config = { 4 | content: [ 5 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", 6 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}", 7 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}", 8 | ], 9 | theme: { 10 | extend: { 11 | backgroundImage: { 12 | "gradient-radial": "radial-gradient(var(--tw-gradient-stops))", 13 | "gradient-conic": 14 | "conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))", 15 | }, 16 | }, 17 | }, 18 | plugins: [], 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/tests/window-resize-event.spec.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-underscore-dangle */ 2 | import { Page, BrowserContext, Browser, expect } from "@playwright/test"; 3 | 4 | import { DFlexPageTest as test } from "dflex-e2e-utils"; 5 | 6 | test.describe("Resize window when store in not active", async () => { 7 | let page: Page; 8 | let context: BrowserContext; 9 | let activeBrowser: Browser; 10 | 11 | test.beforeAll(async ({ browser, baseURL }) => { 12 | activeBrowser = browser; 13 | context = await activeBrowser.newContext(); 14 | page = await context.newPage(); 15 | await page.goto(baseURL!); 16 | }); 17 | 18 | test.afterAll(async () => { 19 | await page.close(); 20 | await context.close(); 21 | // await activeBrowser.close(); 22 | }); 23 | 24 | test("Initiate store by navigation to registered elements", async () => { 25 | await page.click('a[href="/list/symmetric"]'); 26 | await page.waitForSelector("ul#symmetric-container-list"); 27 | await page.goBack(); 28 | 29 | // Wait for clean up to be done internally. 30 | await page.waitForTimeout(500); 31 | 32 | await page.waitForSelector("div#main-page-content"); 33 | }); 34 | 35 | test("should not log errors on resize", async () => { 36 | // Add an event listener to capture console logs 37 | page.on("console", (msg) => { 38 | if (msg.type() === "error") { 39 | expect(true).toBeFalsy(); // This will fail the test if an error is logged 40 | } 41 | }); 42 | 43 | // Simulate a window resize 44 | await page.setViewportSize({ width: 800, height: 600 }); 45 | }); 46 | }); 47 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true 16 | }, 17 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 18 | "exclude": ["node_modules"] 19 | } 20 | -------------------------------------------------------------------------------- /playgrounds/dflex-next-playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "compilerOptions": { 4 | "composite": true, 5 | "outDir": "./types", 6 | "rootDir": ".", 7 | "target": "es5", 8 | "lib": ["dom", "dom.iterable", "esnext"], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "strict": true, 12 | "noEmit": true, 13 | "esModuleInterop": true, 14 | "module": "esnext", 15 | "moduleResolution": "bundler", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "jsx": "preserve", 19 | "incremental": true 20 | }, 21 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 22 | "exclude": ["node_modules"], 23 | "references": [ 24 | { "path": "../../packages/dflex-dnd" }, 25 | { "path": "../dflex-e2e-utils" } 26 | ] 27 | } 28 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | require('@rushstack/eslint-patch/modern-module-resolution') 3 | 4 | module.exports = { 5 | root: true, 6 | 'extends': [ 7 | 'plugin:vue/vue3-essential', 8 | 'eslint:recommended', 9 | '@vue/eslint-config-typescript', 10 | '@vue/eslint-config-prettier/skip-formatting' 11 | ], 12 | parserOptions: { 13 | ecmaVersion: 'latest' 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/prettierrc", 3 | "semi": false, 4 | "tabWidth": 2, 5 | "singleQuote": true, 6 | "printWidth": 100, 7 | "trailingComma": "none" 8 | } -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Vue.volar", 4 | "Vue.vscode-typescript-vue-plugin", 5 | "dbaeumer.vscode-eslint", 6 | "esbenp.prettier-vscode" 7 | ] 8 | } 9 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | DFlex Vue DnD 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dflex-vue-playground", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "vite", 7 | "build": "run-p type-check \"build-only {@}\" --", 8 | "preview": "vite preview", 9 | "build-only": "vite build", 10 | "type-check": "vue-tsc --noEmit -p tsconfig.app.json --composite false", 11 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", 12 | "format": "prettier --write src/" 13 | }, 14 | "dependencies": { 15 | "@dflex/dnd": "workspace:^3.9.9", 16 | "vue": "^3.3.7", 17 | "vue-router": "^4.2.5" 18 | }, 19 | "devDependencies": { 20 | "@rushstack/eslint-patch": "^1.5.1", 21 | "@tsconfig/node18": "^18.2.2", 22 | "@types/node": "^20.8.10", 23 | "@vitejs/plugin-vue": "^4.4.0", 24 | "@vitejs/plugin-vue-jsx": "^3.0.2", 25 | "@vue/eslint-config-prettier": "^8.0.0", 26 | "@vue/eslint-config-typescript": "^12.0.0", 27 | "@vue/tsconfig": "^0.4.0", 28 | "eslint": "^8.52.0", 29 | "eslint-plugin-vue": "^9.18.1", 30 | "npm-run-all2": "^6.1.1", 31 | "prettier": "^3.0.3", 32 | "typescript": "~5.2.2", 33 | "vite": "^4.5.0", 34 | "vite-plugin-replace": "^0.1.1", 35 | "vue-tsc": "^1.8.22" 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dflex-js/dflex/031d202c72fd4a14a49c6444bdbeeb6baf7a72ca/playgrounds/dflex-vue-playground/public/favicon.ico -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/src/App.vue: -------------------------------------------------------------------------------- 1 | 28 | 29 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/src/assets/main.css: -------------------------------------------------------------------------------- 1 | @import './base.css'; 2 | 3 | #app { 4 | max-width: 1280px; 5 | margin: 0 auto; 6 | padding: 2rem; 7 | 8 | font-weight: normal; 9 | } 10 | 11 | a, 12 | .green { 13 | text-decoration: none; 14 | color: hsla(160, 100%, 37%, 1); 15 | transition: 0.4s; 16 | } 17 | 18 | @media (hover: hover) { 19 | a:hover { 20 | background-color: hsla(160, 100%, 37%, 0.2); 21 | } 22 | } 23 | 24 | @media (min-width: 1024px) { 25 | body { 26 | display: flex; 27 | place-items: center; 28 | } 29 | 30 | #app { 31 | display: grid; 32 | grid-template-columns: 1fr 1fr; 33 | padding: 0 2rem; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/src/components/WelcomeItem.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 88 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/src/components/icons/IconCommunity.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/src/components/icons/IconDocumentation.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/src/components/icons/IconEcosystem.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/src/components/icons/IconSupport.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/src/components/icons/IconTooling.vue: -------------------------------------------------------------------------------- 1 | 2 | 20 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/src/env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/src/main.ts: -------------------------------------------------------------------------------- 1 | import './assets/main.css' 2 | 3 | import { createApp } from 'vue' 4 | import App from './App.vue' 5 | import router from './router' 6 | 7 | const app = createApp(App) 8 | 9 | app.use(router) 10 | 11 | app.mount('#app') 12 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/src/router/index.ts: -------------------------------------------------------------------------------- 1 | import { createRouter, createWebHistory } from 'vue-router' 2 | import HomeView from '../views/HomeView.vue' 3 | 4 | const router = createRouter({ 5 | history: createWebHistory(import.meta.env.BASE_URL), 6 | routes: [ 7 | { 8 | path: '/', 9 | name: 'home', 10 | component: HomeView 11 | } 12 | ] 13 | }) 14 | 15 | export default router 16 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/src/views/HomeView.vue: -------------------------------------------------------------------------------- 1 | 4 | 5 | 10 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/tsconfig.app.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@vue/tsconfig/tsconfig.dom.json", 3 | "include": ["env.d.ts", "src/**/*", "src/**/*.vue"], 4 | "exclude": ["src/**/__tests__/*"], 5 | "compilerOptions": { 6 | "composite": true, 7 | "baseUrl": ".", 8 | "paths": { 9 | "@/*": ["./src/*"] 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../../tsconfig.json", 3 | "include": ["src/**/*", "../../dflex-env.d.ts"], 4 | "compilerOptions": { 5 | "composite": true, 6 | "outDir": "./types", 7 | "rootDir": "./src" 8 | }, 9 | "references": [ 10 | { 11 | "path": "./tsconfig.node.json" 12 | }, 13 | { 14 | "path": "./tsconfig.app.json" 15 | }, 16 | { "path": "../../packages/dflex-dnd" }, 17 | { "path": "../dflex-e2e-utils" } 18 | ] 19 | } 20 | -------------------------------------------------------------------------------- /playgrounds/dflex-vue-playground/tsconfig.node.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@tsconfig/node18/tsconfig.json", 3 | "include": [ 4 | "vite.config.*", 5 | "vitest.config.*", 6 | "cypress.config.*", 7 | "nightwatch.conf.*", 8 | "playwright.config.*" 9 | ], 10 | "compilerOptions": { 11 | "composite": true, 12 | "module": "ESNext", 13 | "moduleResolution": "Bundler", 14 | "types": ["node"] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /playwright.config.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable import/no-extraneous-dependencies */ 2 | 3 | import { PlaywrightTestConfig, devices } from "@playwright/test"; 4 | 5 | const { CI, PLAYGROUND_TYPE } = process.env; 6 | const IS_CI = CI === "true"; 7 | 8 | let testDir = ""; 9 | let baseURL = ""; 10 | 11 | if (PLAYGROUND_TYPE === "dflex-dnd") { 12 | testDir = "./playgrounds/dflex-dnd-playground/tests/"; 13 | baseURL = "http://localhost:3001"; 14 | } else if (PLAYGROUND_TYPE === "next-dnd") { 15 | testDir = "./playgrounds/dflex-next-playground/tests/"; 16 | baseURL = "http://localhost:3002"; 17 | } else if (PLAYGROUND_TYPE === "dflex-draggable") { 18 | testDir = "./playgrounds/dflex-draggable-playground/tests/"; 19 | baseURL = "http://localhost:3000"; 20 | } else { 21 | throw new Error( 22 | "Invalid PLAYGROUND_TYPE. Please set PLAYGROUND_TYPE to 'dflex-dnd', 'next-dnd' or 'dflex-draggable'.", 23 | ); 24 | } 25 | 26 | // Log CI and PLAYGROUND_TYPE in bold pink 27 | // eslint-disable-next-line no-console 28 | console.log(`\x1b[1m\x1b[95mPLAYGROUND_TYPE: ${PLAYGROUND_TYPE}\x1b[0m`); 29 | 30 | const config: PlaywrightTestConfig = { 31 | forbidOnly: IS_CI, 32 | retries: IS_CI ? 4 : 1, 33 | // timeout: 30000, 34 | use: { 35 | video: "retain-on-failure", 36 | navigationTimeout: 30000, 37 | baseURL, 38 | viewport: { 39 | height: 1080, 40 | width: 1920, 41 | }, 42 | }, 43 | projects: [ 44 | { 45 | name: "chromium", 46 | testDir, 47 | use: { ...devices["Desktop Chrome"] }, 48 | }, 49 | { 50 | name: "firefox", 51 | testDir, 52 | use: { ...devices["Desktop Firefox"] }, 53 | }, 54 | { 55 | name: "webkit", 56 | testDir, 57 | use: { ...devices["Desktop Safari"] }, 58 | }, 59 | ], 60 | }; 61 | 62 | export default config; 63 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - "packages/**" 3 | - "playgrounds/**" 4 | - "scripts/*" 5 | -------------------------------------------------------------------------------- /scripts/build/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dflex-build", 3 | "version": "1.1.4", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "private": true, 7 | "type": "module", 8 | "dependencies": { 9 | "@rollup/plugin-alias": "^5.0.1", 10 | "@rollup/plugin-babel": "^6.0.4", 11 | "@rollup/plugin-commonjs": "^25.0.7", 12 | "@rollup/plugin-node-resolve": "^15.2.3", 13 | "@rollup/plugin-replace": "^5.0.5", 14 | "@rollup/plugin-terser": "^0.4.4", 15 | "@rollup/pluginutils": "^5.0.5", 16 | "minimist": "^1.2.8", 17 | "npm-packages": "workspace:*", 18 | "rollup": "^4.3.0" 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /scripts/dflex-bundle-types/index.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | 3 | /* eslint-disable no-console */ 4 | 5 | import { rollup as _rollup } from "rollup"; 6 | import { resolve, join } from "path"; 7 | import packages from "npm-packages"; 8 | import { dts } from "rollup-plugin-dts"; 9 | 10 | /** 11 | * @param {"es" |"cjs"} format 12 | * @param {string} inputFile 13 | * @param {string} outputFile 14 | */ 15 | async function build(format, inputFile, outputFile) { 16 | const inputOptions = { 17 | input: inputFile, 18 | plugins: [dts({ tsconfig: resolve("./tsconfig.json") })], 19 | }; 20 | 21 | const result = await _rollup(inputOptions); 22 | 23 | await result.write({ 24 | externalLiveBindings: false, 25 | format, 26 | exports: "auto", 27 | file: outputFile, 28 | freeze: false, 29 | interop: undefined, 30 | }); 31 | } 32 | 33 | const buildPromise = packages.map((pkg) => { 34 | const { sourcePath, typesPath, modules } = pkg; 35 | 36 | const { sourceFileName, format } = modules[1]; 37 | 38 | const inputFile = resolve(join(sourcePath, sourceFileName)); 39 | 40 | const outputFile = resolve(join(typesPath, "index.d.ts")); 41 | 42 | return build(format, inputFile, outputFile); 43 | }); 44 | 45 | Promise.all(buildPromise) 46 | .then() 47 | .catch((e) => { 48 | console.error(e); 49 | }); 50 | -------------------------------------------------------------------------------- /scripts/dflex-bundle-types/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dflex-bundle-types", 3 | "version": "1.0.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "private": true, 7 | "type": "module", 8 | "dependencies": { 9 | "npm-packages": "workspace:*", 10 | "rollup": "^4.3.0", 11 | "rollup-plugin-dts": "^6.1.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /scripts/eslint-config-dflex-react/config.eslintrc.js: -------------------------------------------------------------------------------- 1 | const OFF = 0; 2 | 3 | module.exports = { 4 | extends: ["plugin:react/recommended", "eslint-config-dflex"], 5 | parserOptions: { 6 | ecmaFeatures: { 7 | jsx: true, 8 | }, 9 | }, 10 | plugins: ["react"], 11 | rules: { 12 | "import/no-extraneous-dependencies": OFF, 13 | "react/prop-types": OFF, 14 | }, 15 | settings: { 16 | react: { 17 | version: "detect", 18 | }, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /scripts/eslint-config-dflex-react/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./config.eslintrc"); 2 | -------------------------------------------------------------------------------- /scripts/eslint-config-dflex-react/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-dflex-react", 3 | "version": "0.3.0", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "private": true, 7 | "dependencies": { 8 | "eslint": "^8.52.0", 9 | "eslint-plugin-jsx-a11y": "^6.8.0", 10 | "eslint-plugin-react": "^7.33.2", 11 | "eslint-plugin-react-hooks": "^4.6.0" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /scripts/eslint-config-dflex/config.eslintrc.js: -------------------------------------------------------------------------------- 1 | const OFF = 0; 2 | const ERROR = 2; 3 | 4 | module.exports = { 5 | parser: "@typescript-eslint/parser", 6 | env: { 7 | browser: true, 8 | es2021: true, 9 | }, 10 | globals: { 11 | __DEV__: "readonly", 12 | }, 13 | extends: ["airbnb-base", "plugin:prettier/recommended"], 14 | parserOptions: { 15 | ecmaVersion: "latest", 16 | sourceType: "module", 17 | }, 18 | plugins: ["@typescript-eslint"], 19 | ignorePatterns: ["node_modules", "dist", "lib", "coverage", "**/*.d.ts"], 20 | rules: { 21 | "no-param-reassign": OFF, 22 | "no-underscore-dangle": [ERROR, { allowAfterThis: true }], 23 | "no-nested-ternary": OFF, 24 | "no-use-before-define": OFF, 25 | "@typescript-eslint/no-use-before-define": [ERROR], 26 | "prettier/prettier": [ 27 | ERROR, 28 | { 29 | endOfLine: "auto", 30 | }, 31 | ], 32 | "import/extensions": [ 33 | ERROR, 34 | "ignorePackages", 35 | { 36 | js: "never", 37 | ts: "never", 38 | tsx: "never", 39 | jsx: "never", 40 | }, 41 | ], 42 | }, 43 | settings: { 44 | "import/resolver": { 45 | typescript: {}, 46 | node: { 47 | extensions: [".js", ".jsx", ".json", ".ts", ".tsx"], 48 | }, 49 | }, 50 | }, 51 | overrides: [ 52 | { 53 | files: ["*.test.js", "*.test.ts", "*.test.tsx"], 54 | env: { 55 | jest: true, 56 | }, 57 | }, 58 | { 59 | files: ["types.ts"], 60 | rules: { 61 | "no-unused-vars": OFF, 62 | }, 63 | }, 64 | ], 65 | }; 66 | -------------------------------------------------------------------------------- /scripts/eslint-config-dflex/index.js: -------------------------------------------------------------------------------- 1 | module.exports = require("./config.eslintrc"); 2 | -------------------------------------------------------------------------------- /scripts/eslint-config-dflex/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-config-dflex", 3 | "version": "0.3.0", 4 | "private": true, 5 | "main": "index.js", 6 | "license": "MIT", 7 | "dependencies": { 8 | "@typescript-eslint/eslint-plugin": "^6.9.1", 9 | "@typescript-eslint/parser": "^6.9.1", 10 | "eslint": "^8.52.0", 11 | "eslint-config-airbnb-base": "^15.0.0", 12 | "eslint-config-prettier": "^9.0.0", 13 | "eslint-import-resolver-typescript": "^3.6.1", 14 | "eslint-plugin-import": "^2.29.0", 15 | "eslint-plugin-prettier": "^5.0.1" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /scripts/npm/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "npm-packages", 3 | "version": "1.0.1", 4 | "main": "index.js", 5 | "license": "MIT", 6 | "private": true, 7 | "type": "module" 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "jsx": "react", 4 | "strict": true, 5 | "stripInternal": true, 6 | "noUnusedLocals": true, 7 | "noUnusedParameters": true, 8 | "noImplicitReturns": true, 9 | "noFallthroughCasesInSwitch": true, 10 | "noImplicitAny": true, 11 | "strictFunctionTypes": true, 12 | "strictNullChecks": true, 13 | "downlevelIteration": true, 14 | // "verbatimModuleSyntax": true, 15 | "target": "ES2019", 16 | "module": "ESNext", 17 | "sourceMap": true, 18 | "outDir": "./types", 19 | "lib": ["esnext", "dom"], 20 | "moduleResolution": "Node", 21 | "baseUrl": ".", 22 | "paths": { 23 | "@dflex/utils": ["./packages/dflex-utils/src/index.ts"], 24 | "@dflex/dom-gen": ["./packages/dflex-dom-gen/src/index.ts"], 25 | "@dflex/core-instance": ["./packages/dflex-core-instance/src/index.ts"], 26 | "@dflex/store": ["./packages/dflex-store/src/index.ts"], 27 | "@dflex/draggable": ["./packages/dflex-draggable/src/index.ts"], 28 | "@dflex/dnd": ["./packages/dflex-dnd/src/index.ts"] 29 | }, 30 | "skipLibCheck": true, 31 | "declaration": true, 32 | "forceConsistentCasingInFileNames": true, 33 | "isolatedModules": true, 34 | "esModuleInterop": true, 35 | "typeRoots": ["dflex-env.d.ts", "node_modules/@types"] 36 | }, 37 | "include": ["packages", "playgrounds", "dflex-env.d.ts"], 38 | "exclude": ["node_modules", "dist", "**/*.cy*", "**/*.test*"] 39 | } 40 | -------------------------------------------------------------------------------- /tsconfig.test.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "include": ["**/*.test*", "dflex-env.d.ts"] 4 | } 5 | --------------------------------------------------------------------------------