├── env.d.ts ├── pnpm-workspace.yaml ├── packages ├── sortable │ ├── src │ │ ├── sensors │ │ │ ├── index.ts │ │ │ └── keyboard │ │ │ │ └── index.ts │ │ ├── hooks │ │ │ ├── utilities │ │ │ │ ├── index.ts │ │ │ │ └── useDerivedTransform.ts │ │ │ ├── index.ts │ │ │ ├── types.ts │ │ │ └── defaults.ts │ │ ├── types │ │ │ ├── disabled.ts │ │ │ ├── index.ts │ │ │ ├── data.ts │ │ │ ├── strategies.ts │ │ │ └── type-guard.ts │ │ ├── utilities │ │ │ ├── isValidIndex.ts │ │ │ ├── index.ts │ │ │ ├── normalizeDisabled.ts │ │ │ ├── arraySwap.ts │ │ │ ├── arrayMove.ts │ │ │ ├── itemsEqual.ts │ │ │ └── getSortedRects.ts │ │ ├── components │ │ │ ├── index.ts │ │ │ └── context │ │ │ │ ├── Provider.tsx │ │ │ │ └── Consumer.tsx │ │ ├── strategies │ │ │ ├── index.ts │ │ │ ├── rectSorting.ts │ │ │ ├── rectSwapping.ts │ │ │ └── horizontalListSorting.ts │ │ ├── createContext │ │ │ └── index.ts │ │ └── index.ts │ ├── test │ │ ├── SortableDemo1.spec.tsx │ │ └── demo1 │ │ │ ├── styles.module.css │ │ │ └── SortableItem.tsx │ ├── vite.config.ts │ ├── tsconfig.json │ ├── README.md │ ├── LICENSE │ ├── package.json │ └── CHANGELOG.md ├── core │ ├── src │ │ ├── utilities │ │ │ ├── other │ │ │ │ ├── index.ts │ │ │ │ └── noop.ts │ │ │ ├── nodes │ │ │ │ ├── index.ts │ │ │ │ └── getMeasurableNode.ts │ │ │ ├── transform │ │ │ │ ├── index.ts │ │ │ │ ├── parseTransform.ts │ │ │ │ └── inverseTransform.ts │ │ │ ├── coordinates │ │ │ │ ├── constants.ts │ │ │ │ ├── index.ts │ │ │ │ ├── distanceBetweenPoints.ts │ │ │ │ └── getRelativeTransformOrigin.ts │ │ │ ├── scroll │ │ │ │ ├── isFixed.ts │ │ │ │ ├── documentScrollingElement.ts │ │ │ │ ├── isScrollable.ts │ │ │ │ ├── getScrollElementRect.ts │ │ │ │ ├── getScrollableElement.ts │ │ │ │ ├── index.ts │ │ │ │ ├── getScrollCoordinates.ts │ │ │ │ ├── scrollIntoViewIfNeeded.ts │ │ │ │ ├── getScrollOffsets.ts │ │ │ │ ├── getScrollPosition.ts │ │ │ │ ├── getScrollableAncestors.ts │ │ │ │ └── getScrollDirectionAndSpeed.ts │ │ │ ├── algorithms │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ ├── closestCorners.ts │ │ │ │ ├── closestCenter.ts │ │ │ │ ├── pointerWithin.ts │ │ │ │ ├── helpers.ts │ │ │ │ └── rectIntersection.ts │ │ │ ├── rect │ │ │ │ ├── index.ts │ │ │ │ ├── getWindowClientRect.ts │ │ │ │ ├── getRectDelta.ts │ │ │ │ ├── adjustScale.ts │ │ │ │ ├── rectAdjustment.ts │ │ │ │ ├── getRect.ts │ │ │ │ └── Rect.ts │ │ │ ├── watchRef.ts │ │ │ └── index.ts │ │ ├── types │ │ │ ├── other.ts │ │ │ ├── direction.ts │ │ │ ├── rect.ts │ │ │ ├── react.ts │ │ │ ├── coordinates.ts │ │ │ ├── index.ts │ │ │ └── events.ts │ │ ├── components │ │ │ ├── Accessibility │ │ │ │ ├── components │ │ │ │ │ ├── index.ts │ │ │ │ │ └── RestoreFocus.tsx │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ ├── defaults.ts │ │ │ │ └── Accessibility.tsx │ │ │ ├── DragOverlay │ │ │ │ ├── components │ │ │ │ │ ├── NullifiedContextProvider │ │ │ │ │ │ ├── index.ts │ │ │ │ │ │ └── NullifiedContextProvider.tsx │ │ │ │ │ ├── AnimationManager │ │ │ │ │ │ └── index.ts │ │ │ │ │ ├── PositionedOverlay │ │ │ │ │ │ └── index.ts │ │ │ │ │ └── index.ts │ │ │ │ ├── index.ts │ │ │ │ └── hooks │ │ │ │ │ ├── useKey.ts │ │ │ │ │ └── index.ts │ │ │ ├── DndContext │ │ │ │ ├── hooks │ │ │ │ │ ├── index.ts │ │ │ │ │ └── useMeasuringConfiguration.ts │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ └── defaults.ts │ │ │ ├── DndMonitor │ │ │ │ ├── context.ts │ │ │ │ ├── index.ts │ │ │ │ ├── useDndMonitorProvider.tsx │ │ │ │ ├── useDndMonitor.ts │ │ │ │ └── types.ts │ │ │ └── index.ts │ │ ├── modifiers │ │ │ ├── index.ts │ │ │ ├── applyModifiers.ts │ │ │ └── types.ts │ │ ├── sensors │ │ │ ├── mouse │ │ │ │ ├── index.ts │ │ │ │ └── MouseSensor.ts │ │ │ ├── touch │ │ │ │ ├── index.ts │ │ │ │ └── TouchSensor.ts │ │ │ ├── utilities │ │ │ │ ├── index.ts │ │ │ │ ├── hasExceededDistance.ts │ │ │ │ ├── Listeners.ts │ │ │ │ └── getEventListenerTarget.ts │ │ │ ├── keyboard │ │ │ │ ├── index.ts │ │ │ │ ├── types.ts │ │ │ │ └── defaults.ts │ │ │ ├── useSensor.ts │ │ │ ├── pointer │ │ │ │ ├── index.ts │ │ │ │ └── PointerSensor.ts │ │ │ ├── events.ts │ │ │ ├── useSensors.ts │ │ │ └── index.ts │ │ ├── vue │ │ │ └── useMemo.ts │ │ ├── hooks │ │ │ ├── utilities │ │ │ │ ├── useWindowRect.ts │ │ │ │ ├── useInitialRect.ts │ │ │ │ ├── useScrollIntoViewIfNeeded.ts │ │ │ │ ├── useRectDelta.ts │ │ │ │ ├── useSensorSetup.ts │ │ │ │ ├── useInitialValue.ts │ │ │ │ ├── useSyntheticListeners.ts │ │ │ │ ├── useScrollableAncestors.ts │ │ │ │ ├── useCombineActivators.ts │ │ │ │ ├── useMutationObserver.ts │ │ │ │ ├── useResizeObserver.ts │ │ │ │ ├── useScrollOffsetsDelta.ts │ │ │ │ ├── useCachedNode.ts │ │ │ │ ├── index.ts │ │ │ │ ├── useDragOverlayMeasuring.ts │ │ │ │ └── useRects.ts │ │ │ ├── useDndContext.ts │ │ │ └── index.ts │ │ ├── store │ │ │ ├── index.ts │ │ │ ├── constructors.ts │ │ │ ├── actions.ts │ │ │ └── context.ts │ │ └── CreateContextVueVNode │ │ │ ├── DndMonitorContextConsumer.tsx │ │ │ ├── InternalContextConsumer.tsx │ │ │ ├── DndContextProvider.tsx │ │ │ ├── PublicContextConsumer.tsx │ │ │ ├── DndContextConsumer.tsx │ │ │ ├── PublicContextProvider.tsx │ │ │ ├── DndMonitorContextProvider.tsx │ │ │ └── InternalContextProvider.tsx │ ├── test │ │ ├── core.test.tsx │ │ └── demo │ │ │ ├── Draggable.tsx │ │ │ └── CoreTest.tsx │ ├── README.md │ ├── vite.config.ts │ ├── tsconfig.json │ ├── LICENSE │ ├── package.json │ └── CHANGELOG.md ├── accessibility │ ├── src │ │ ├── hooks │ │ │ ├── index.ts │ │ │ └── useAnnouncement.ts │ │ ├── components │ │ │ ├── HiddenText │ │ │ │ ├── index.ts │ │ │ │ └── HiddenText.tsx │ │ │ ├── LiveRegion │ │ │ │ ├── index.ts │ │ │ │ └── LiveRegion.tsx │ │ │ └── index.ts │ │ └── index.ts │ ├── README.md │ ├── CHANGELOG.md │ ├── vite.config.ts │ ├── tsconfig.json │ ├── LICENSE │ └── package.json ├── utilities │ ├── src │ │ ├── focus │ │ │ ├── index.ts │ │ │ └── findFirstFocusableNode.ts │ │ ├── coordinates │ │ │ ├── types.ts │ │ │ ├── index.ts │ │ │ └── getEventCoordinates.ts │ │ ├── type-guards │ │ │ ├── isNode.ts │ │ │ ├── isSVGElement.ts │ │ │ ├── index.ts │ │ │ ├── isDocument.ts │ │ │ ├── isHTMLElement.ts │ │ │ └── isWindow.ts │ │ ├── execution-context │ │ │ ├── index.ts │ │ │ ├── canUseDOM.ts │ │ │ ├── getWindow.ts │ │ │ └── getOwnerDocument.ts │ │ ├── event │ │ │ ├── index.ts │ │ │ ├── hasViewportRelativeCoordinates.ts │ │ │ ├── isTouchEvent.ts │ │ │ └── isKeyboardEvent.ts │ │ ├── hooks │ │ │ ├── useCombinedRefs.ts │ │ │ ├── useEvent.ts │ │ │ ├── usePrevious.ts │ │ │ ├── useUniqueId.ts │ │ │ ├── useIsomorphicLayoutEffect.ts │ │ │ ├── index.ts │ │ │ ├── useLatestValue.ts │ │ │ ├── useInterval.ts │ │ │ ├── useLazyMemo.ts │ │ │ └── useNodeRef.ts │ │ ├── types.ts │ │ ├── adjustment.ts │ │ ├── index.ts │ │ └── css.ts │ ├── README.md │ ├── CHANGELOG.md │ ├── vite.config.ts │ ├── tsconfig.json │ ├── LICENSE │ └── package.json └── modifiers │ ├── src │ ├── utilities │ │ ├── index.ts │ │ └── restrictToBoundingRect.ts │ ├── restrictToHorizontalAxis.ts │ ├── restrictToVerticalAxis.ts │ ├── createSnapModifier.ts │ ├── restrictToWindowEdges.ts │ ├── restrictToParentElement.ts │ ├── index.ts │ ├── restrictToFirstScrollableAncestor.ts │ └── snapCenterToCursor.ts │ ├── vite.config.ts │ ├── tsconfig.json │ ├── LICENSE │ ├── package.json │ └── CHANGELOG.md ├── public └── favicon.ico ├── src ├── test │ └── sortable │ │ └── Virtualized.module.css ├── main.ts ├── vite-env.d.ts ├── assets │ ├── logo.svg │ ├── vue.svg │ └── main.css ├── components │ ├── icons │ │ ├── IconSupport.vue │ │ ├── IconTooling.vue │ │ ├── IconCommunity.vue │ │ ├── IconDocumentation.vue │ │ └── IconEcosystem.vue │ ├── __tests__ │ │ └── HelloWorld.spec.ts │ ├── HelloWorld.vue │ └── WelcomeItem.vue ├── App.tsx ├── App.vue └── style.css ├── .github ├── assets │ ├── use-sortable.png │ └── storybook-screenshot.png └── workflows │ └── publish.yml ├── .npmrc ├── .npmrc.bak ├── script └── version.js ├── .prettierrc.json ├── .changeset ├── config.json └── README.md ├── index.html ├── .eslintrc.cjs ├── vitest.config.ts ├── .gitignore ├── vite.config.ts ├── tsconfig.json ├── README.md └── package.json /env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /pnpm-workspace.yaml: -------------------------------------------------------------------------------- 1 | packages: 2 | - 'packages/*' -------------------------------------------------------------------------------- /packages/sortable/src/sensors/index.ts: -------------------------------------------------------------------------------- 1 | export * from './keyboard'; 2 | -------------------------------------------------------------------------------- /packages/core/src/utilities/other/index.ts: -------------------------------------------------------------------------------- 1 | export {noop} from './noop'; 2 | -------------------------------------------------------------------------------- /packages/core/src/types/other.ts: -------------------------------------------------------------------------------- 1 | export type UniqueIdentifier = string | number; 2 | -------------------------------------------------------------------------------- /packages/core/src/utilities/other/noop.ts: -------------------------------------------------------------------------------- 1 | export function noop(..._args: any) {} 2 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rashagu/dnd-kit-vue/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /packages/accessibility/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export {useAnnouncement} from './useAnnouncement'; 2 | -------------------------------------------------------------------------------- /packages/accessibility/src/components/HiddenText/index.ts: -------------------------------------------------------------------------------- 1 | export {HiddenText} from './HiddenText'; 2 | -------------------------------------------------------------------------------- /packages/accessibility/src/components/LiveRegion/index.ts: -------------------------------------------------------------------------------- 1 | export {LiveRegion} from './LiveRegion'; 2 | -------------------------------------------------------------------------------- /packages/core/src/utilities/nodes/index.ts: -------------------------------------------------------------------------------- 1 | export {getMeasurableNode} from './getMeasurableNode'; 2 | -------------------------------------------------------------------------------- /packages/utilities/src/focus/index.ts: -------------------------------------------------------------------------------- 1 | export {findFirstFocusableNode} from './findFirstFocusableNode'; 2 | -------------------------------------------------------------------------------- /packages/core/src/components/Accessibility/components/index.ts: -------------------------------------------------------------------------------- 1 | export {RestoreFocus} from './RestoreFocus'; 2 | -------------------------------------------------------------------------------- /packages/core/src/types/direction.ts: -------------------------------------------------------------------------------- 1 | export enum Direction { 2 | Forward = 1, 3 | Backward = -1, 4 | } 5 | -------------------------------------------------------------------------------- /packages/modifiers/src/utilities/index.ts: -------------------------------------------------------------------------------- 1 | export {restrictToBoundingRect} from './restrictToBoundingRect'; 2 | -------------------------------------------------------------------------------- /packages/sortable/src/hooks/utilities/index.ts: -------------------------------------------------------------------------------- 1 | export {useDerivedTransform} from './useDerivedTransform'; 2 | -------------------------------------------------------------------------------- /src/test/sortable/Virtualized.module.css: -------------------------------------------------------------------------------- 1 | .VirtualList { 2 | > div { 3 | overflow: hidden; 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /.github/assets/use-sortable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rashagu/dnd-kit-vue/HEAD/.github/assets/use-sortable.png -------------------------------------------------------------------------------- /packages/utilities/src/coordinates/types.ts: -------------------------------------------------------------------------------- 1 | export type Coordinates = { 2 | x: number; 3 | y: number; 4 | }; 5 | -------------------------------------------------------------------------------- /packages/sortable/src/sensors/keyboard/index.ts: -------------------------------------------------------------------------------- 1 | export {sortableKeyboardCoordinates} from './sortableKeyboardCoordinates'; 2 | -------------------------------------------------------------------------------- /.github/assets/storybook-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rashagu/dnd-kit-vue/HEAD/.github/assets/storybook-screenshot.png -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | # registry=https://registry.npmmirror.com/ 2 | 3 | registry=https://registry.npmjs.org/ 4 | auto-install-peers=true 5 | 6 | -------------------------------------------------------------------------------- /packages/sortable/src/types/disabled.ts: -------------------------------------------------------------------------------- 1 | export interface Disabled { 2 | draggable?: boolean; 3 | droppable?: boolean; 4 | } 5 | -------------------------------------------------------------------------------- /.npmrc.bak: -------------------------------------------------------------------------------- 1 | registry=https://registry.npmmirror.com/ 2 | 3 | # registry=https://registry.npmjs.org/ 4 | auto-install-peers=true 5 | 6 | -------------------------------------------------------------------------------- /packages/accessibility/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export {HiddenText} from './HiddenText'; 2 | export {LiveRegion} from './LiveRegion'; 3 | -------------------------------------------------------------------------------- /packages/accessibility/src/index.ts: -------------------------------------------------------------------------------- 1 | export {HiddenText, LiveRegion} from './components'; 2 | export {useAnnouncement} from './hooks'; 3 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue' 2 | import './style.css' 3 | import App from './App' 4 | 5 | createApp(App).mount('#app') 6 | -------------------------------------------------------------------------------- /packages/core/src/modifiers/index.ts: -------------------------------------------------------------------------------- 1 | export type {Modifier, Modifiers} from './types'; 2 | export {applyModifiers} from './applyModifiers'; 3 | -------------------------------------------------------------------------------- /packages/utilities/src/type-guards/isNode.ts: -------------------------------------------------------------------------------- 1 | export function isNode(node: Object): node is Node { 2 | return 'nodeType' in node; 3 | } 4 | -------------------------------------------------------------------------------- /packages/utilities/src/coordinates/index.ts: -------------------------------------------------------------------------------- 1 | export type {Coordinates} from './types'; 2 | export {getEventCoordinates} from './getEventCoordinates'; 3 | -------------------------------------------------------------------------------- /packages/core/src/components/DragOverlay/components/NullifiedContextProvider/index.ts: -------------------------------------------------------------------------------- 1 | export {NullifiedContextProvider} from './NullifiedContextProvider'; 2 | -------------------------------------------------------------------------------- /packages/core/src/utilities/transform/index.ts: -------------------------------------------------------------------------------- 1 | export {inverseTransform} from './inverseTransform'; 2 | export {parseTransform} from './parseTransform'; 3 | -------------------------------------------------------------------------------- /packages/core/src/sensors/mouse/index.ts: -------------------------------------------------------------------------------- 1 | export {MouseSensor} from './MouseSensor'; 2 | export type {MouseSensorOptions, MouseSensorProps} from './MouseSensor'; 3 | -------------------------------------------------------------------------------- /packages/core/src/sensors/touch/index.ts: -------------------------------------------------------------------------------- 1 | export {TouchSensor} from './TouchSensor'; 2 | export type {TouchSensorOptions, TouchSensorProps} from './TouchSensor'; 3 | -------------------------------------------------------------------------------- /packages/sortable/src/utilities/isValidIndex.ts: -------------------------------------------------------------------------------- 1 | export function isValidIndex(index: number | null): index is number { 2 | return index !== null && index >= 0; 3 | } 4 | -------------------------------------------------------------------------------- /script/version.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | function main() { 4 | const { version } = require("../packages/core/package.json"); 5 | return version; 6 | } 7 | 8 | console.log(main()) 9 | -------------------------------------------------------------------------------- /packages/sortable/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export {SortableContext, Context} from './SortableContext'; 2 | export type {Props as SortableContextProps} from './SortableContext'; 3 | -------------------------------------------------------------------------------- /packages/core/src/components/DragOverlay/components/AnimationManager/index.ts: -------------------------------------------------------------------------------- 1 | export {AnimationManager} from './AnimationManager'; 2 | export type {Animation} from './AnimationManager'; 3 | -------------------------------------------------------------------------------- /packages/utilities/src/execution-context/index.ts: -------------------------------------------------------------------------------- 1 | export {canUseDOM} from './canUseDOM'; 2 | export {getOwnerDocument} from './getOwnerDocument'; 3 | export {getWindow} from './getWindow'; 4 | -------------------------------------------------------------------------------- /packages/core/src/types/rect.ts: -------------------------------------------------------------------------------- 1 | export interface ClientRect { 2 | width: number; 3 | height: number; 4 | top: number; 5 | left: number; 6 | right: number; 7 | bottom: number; 8 | } 9 | -------------------------------------------------------------------------------- /.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 | } -------------------------------------------------------------------------------- /packages/core/src/utilities/coordinates/constants.ts: -------------------------------------------------------------------------------- 1 | import type {Coordinates} from '../../types'; 2 | 3 | export const defaultCoordinates: Coordinates = Object.freeze({ 4 | x: 0, 5 | y: 0, 6 | }); 7 | -------------------------------------------------------------------------------- /packages/core/src/types/react.ts: -------------------------------------------------------------------------------- 1 | import type {Without} from '@dnd-kit-vue/utilities'; 2 | 3 | export type SyntheticEventName = keyof Without< 4 | any, 5 | 'children' | 'dangerouslySetInnerHTML' 6 | >; 7 | -------------------------------------------------------------------------------- /packages/core/src/components/DndContext/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export {useMeasuringConfiguration} from './useMeasuringConfiguration'; 2 | export {useLayoutShiftScrollCompensation} from './useLayoutShiftScrollCompensation'; 3 | -------------------------------------------------------------------------------- /packages/core/src/components/DragOverlay/components/PositionedOverlay/index.ts: -------------------------------------------------------------------------------- 1 | export {PositionedOverlay} from './PositionedOverlay'; 2 | export type {Props as PositionedOverlayProps} from './PositionedOverlay'; 3 | -------------------------------------------------------------------------------- /packages/core/src/sensors/utilities/index.ts: -------------------------------------------------------------------------------- 1 | export {Listeners} from './Listeners'; 2 | export {getEventListenerTarget} from './getEventListenerTarget'; 3 | export {hasExceededDistance} from './hasExceededDistance'; 4 | -------------------------------------------------------------------------------- /packages/utilities/src/event/index.ts: -------------------------------------------------------------------------------- 1 | export {hasViewportRelativeCoordinates} from './hasViewportRelativeCoordinates'; 2 | export {isKeyboardEvent} from './isKeyboardEvent'; 3 | export {isTouchEvent} from './isTouchEvent'; 4 | -------------------------------------------------------------------------------- /packages/sortable/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type {Disabled} from './disabled'; 2 | export type {SortableData} from './data'; 3 | export type {SortingStrategy} from './strategies'; 4 | export {hasSortableData} from './type-guard'; 5 | -------------------------------------------------------------------------------- /src/vite-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | declare module '*.vue' { 4 | import type { DefineComponent } from 'vue' 5 | const component: DefineComponent<{}, {}, any> 6 | export default component 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/src/utilities/coordinates/index.ts: -------------------------------------------------------------------------------- 1 | export {defaultCoordinates} from './constants'; 2 | export {distanceBetween} from './distanceBetweenPoints'; 3 | export {getRelativeTransformOrigin} from './getRelativeTransformOrigin'; 4 | -------------------------------------------------------------------------------- /packages/utilities/src/hooks/useCombinedRefs.ts: -------------------------------------------------------------------------------- 1 | 2 | export function useCombinedRefs( 3 | ...refs: ((node: T) => void)[] 4 | ): (node: T) => void { 5 | return (node: T) => { 6 | refs.forEach((ref) => ref(node)); 7 | }; 8 | } 9 | -------------------------------------------------------------------------------- /packages/core/src/components/DndMonitor/context.ts: -------------------------------------------------------------------------------- 1 | 2 | import DndMonitorContextProvider from "../../CreateContextVueVNode/DndMonitorContextProvider"; 3 | 4 | export const DndMonitorContext = { 5 | Provider: DndMonitorContextProvider, 6 | } 7 | -------------------------------------------------------------------------------- /packages/modifiers/src/restrictToHorizontalAxis.ts: -------------------------------------------------------------------------------- 1 | import type {Modifier} from '@dnd-kit-vue/core'; 2 | 3 | export const restrictToHorizontalAxis: Modifier = ({transform}) => { 4 | return { 5 | ...transform, 6 | y: 0, 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/modifiers/src/restrictToVerticalAxis.ts: -------------------------------------------------------------------------------- 1 | import type {Modifier} from '@dnd-kit-vue/core'; 2 | 3 | export const restrictToVerticalAxis: Modifier = ({transform}) => { 4 | return { 5 | ...transform, 6 | x: 0, 7 | }; 8 | }; 9 | -------------------------------------------------------------------------------- /packages/utilities/README.md: -------------------------------------------------------------------------------- 1 | # @dnd-kit-vue/utilities 2 | 3 | [![Stable release](https://img.shields.io/npm/v/@dnd-kit-vue/utilities.svg)](https://npm.im/@dnd-kit/sortable) 4 | 5 | Internal utilities to bee shared between `@dnd-kit` packages. 6 | -------------------------------------------------------------------------------- /packages/utilities/src/type-guards/isSVGElement.ts: -------------------------------------------------------------------------------- 1 | import {getWindow} from '../execution-context/getWindow'; 2 | 3 | export function isSVGElement(node: Node): node is SVGElement { 4 | return node instanceof getWindow(node).SVGElement; 5 | } 6 | -------------------------------------------------------------------------------- /packages/utilities/src/hooks/useEvent.ts: -------------------------------------------------------------------------------- 1 | import { type ShallowRef, shallowRef } from 'vue' 2 | 3 | export function useEvent(handler: T | undefined):ShallowRef { 4 | return shallowRef(handler) 5 | } 6 | -------------------------------------------------------------------------------- /packages/utilities/src/event/hasViewportRelativeCoordinates.ts: -------------------------------------------------------------------------------- 1 | export function hasViewportRelativeCoordinates( 2 | event: Event 3 | ): event is Event & Pick { 4 | return 'clientX' in event && 'clientY' in event; 5 | } 6 | -------------------------------------------------------------------------------- /packages/utilities/src/type-guards/index.ts: -------------------------------------------------------------------------------- 1 | export {isDocument} from './isDocument'; 2 | export {isHTMLElement} from './isHTMLElement'; 3 | export {isNode} from './isNode'; 4 | export {isSVGElement} from './isSVGElement'; 5 | export {isWindow} from './isWindow'; 6 | -------------------------------------------------------------------------------- /packages/core/src/components/DndContext/index.ts: -------------------------------------------------------------------------------- 1 | export {ActiveDraggableContext, DndContext} from './DndContext'; 2 | export type {CancelDrop, Props as DndContextProps} from './DndContext'; 3 | export type {DraggableMeasuring, MeasuringConfiguration} from './types'; 4 | -------------------------------------------------------------------------------- /packages/utilities/src/type-guards/isDocument.ts: -------------------------------------------------------------------------------- 1 | import {getWindow} from '../execution-context/getWindow'; 2 | 3 | export function isDocument(node: Node): node is Document { 4 | const {Document} = getWindow(node); 5 | 6 | return node instanceof Document; 7 | } 8 | -------------------------------------------------------------------------------- /packages/sortable/src/types/data.ts: -------------------------------------------------------------------------------- 1 | import type {UniqueIdentifier} from '@dnd-kit-vue/core'; 2 | 3 | export type SortableData = { 4 | sortable: { 5 | containerId: UniqueIdentifier; 6 | items: UniqueIdentifier[]; 7 | index: number; 8 | }; 9 | }; 10 | -------------------------------------------------------------------------------- /packages/core/src/components/DndMonitor/index.ts: -------------------------------------------------------------------------------- 1 | export {DndMonitorContext} from './context'; 2 | export type {DndMonitorListener, DndMonitorEvent} from './types'; 3 | export {useDndMonitor} from './useDndMonitor'; 4 | export {useDndMonitorProvider} from './useDndMonitorProvider'; 5 | -------------------------------------------------------------------------------- /packages/sortable/src/strategies/index.ts: -------------------------------------------------------------------------------- 1 | export {horizontalListSortingStrategy} from './horizontalListSorting'; 2 | export {rectSortingStrategy} from './rectSorting'; 3 | export {rectSwappingStrategy} from './rectSwapping'; 4 | export {verticalListSortingStrategy} from './verticalListSorting'; 5 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /packages/core/src/utilities/scroll/isFixed.ts: -------------------------------------------------------------------------------- 1 | import {getWindow} from '@dnd-kit-vue/utilities'; 2 | 3 | export function isFixed( 4 | node: HTMLElement, 5 | computedStyle: CSSStyleDeclaration = getWindow(node).getComputedStyle(node) 6 | ): boolean { 7 | return computedStyle.position === 'fixed'; 8 | } 9 | -------------------------------------------------------------------------------- /packages/core/src/sensors/keyboard/index.ts: -------------------------------------------------------------------------------- 1 | export {KeyboardSensor} from './KeyboardSensor'; 2 | export type { 3 | KeyboardSensorOptions, 4 | KeyboardSensorProps, 5 | } from './KeyboardSensor'; 6 | export type {KeyboardCoordinateGetter, KeyboardCodes} from './types'; 7 | export {KeyboardCode} from './types'; 8 | -------------------------------------------------------------------------------- /packages/sortable/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export {useSortable} from './useSortable'; 2 | export type {Arguments as UseSortableArguments} from './useSortable'; 3 | 4 | export {defaultAnimateLayoutChanges, defaultNewIndexGetter} from './defaults'; 5 | export type {AnimateLayoutChanges, NewIndexGetter} from './types'; 6 | -------------------------------------------------------------------------------- /packages/utilities/src/execution-context/canUseDOM.ts: -------------------------------------------------------------------------------- 1 | // https://github.com/facebook/react/blob/master/packages/shared/ExecutionEnvironment.js 2 | export const canUseDOM = 3 | typeof window !== 'undefined' && 4 | typeof window.document !== 'undefined' && 5 | typeof window.document.createElement !== 'undefined'; 6 | -------------------------------------------------------------------------------- /packages/core/src/components/Accessibility/index.ts: -------------------------------------------------------------------------------- 1 | export {Accessibility} from './Accessibility'; 2 | export {RestoreFocus} from './components'; 3 | export { 4 | defaultAnnouncements, 5 | defaultScreenReaderInstructions, 6 | } from './defaults'; 7 | export type {Announcements, ScreenReaderInstructions} from './types'; 8 | -------------------------------------------------------------------------------- /packages/sortable/src/utilities/index.ts: -------------------------------------------------------------------------------- 1 | export {arrayMove} from './arrayMove'; 2 | export {arraySwap} from './arraySwap'; 3 | export {getSortedRects} from './getSortedRects'; 4 | export {isValidIndex} from './isValidIndex'; 5 | export {itemsEqual} from './itemsEqual'; 6 | export {normalizeDisabled} from './normalizeDisabled'; 7 | -------------------------------------------------------------------------------- /packages/utilities/src/hooks/usePrevious.ts: -------------------------------------------------------------------------------- 1 | 2 | import {type Ref, ref, shallowRef, watchEffect} from "vue"; 3 | 4 | export function usePrevious(value: T):Ref { 5 | const ref1 = shallowRef(value); 6 | 7 | // watchEffect(() => { 8 | // ref1.value = value; 9 | // }); 10 | 11 | return ref1; 12 | } 13 | -------------------------------------------------------------------------------- /packages/core/src/utilities/coordinates/distanceBetweenPoints.ts: -------------------------------------------------------------------------------- 1 | import type {Coordinates} from '../../types'; 2 | 3 | /** 4 | * Returns the distance between two points 5 | */ 6 | export function distanceBetween(p1: Coordinates, p2: Coordinates) { 7 | return Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)); 8 | } 9 | -------------------------------------------------------------------------------- /packages/core/src/utilities/scroll/documentScrollingElement.ts: -------------------------------------------------------------------------------- 1 | import {canUseDOM} from '@dnd-kit-vue/utilities'; 2 | 3 | export function isDocumentScrollingElement(element: Element | null) { 4 | if (!canUseDOM || !element) { 5 | return false; 6 | } 7 | 8 | return element === document.scrollingElement; 9 | } 10 | -------------------------------------------------------------------------------- /packages/core/src/vue/useMemo.ts: -------------------------------------------------------------------------------- 1 | import { type ShallowRef, shallowRef, watch } from 'vue' 2 | 3 | export function useMemo(getValue: (...arg:any[])=>T, sources: any[]):ShallowRef { 4 | const value = shallowRef(getValue()) 5 | watch(sources, ()=>{ 6 | value.value = getValue() 7 | }, {immediate: true}) 8 | return value 9 | } -------------------------------------------------------------------------------- /.changeset/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://unpkg.com/@changesets/config@2.2.0/schema.json", 3 | "changelog": "@changesets/cli/changelog", 4 | "commit": true, 5 | "fixed": [], 6 | "linked": [], 7 | "access": "restricted", 8 | "baseBranch": "main", 9 | "updateInternalDependencies": "patch", 10 | "ignore": [] 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/src/hooks/utilities/useWindowRect.ts: -------------------------------------------------------------------------------- 1 | 2 | import {getWindowClientRect} from '../../utilities/rect'; 3 | import { computed, type ComputedRef, Ref } from 'vue' 4 | 5 | export function useWindowRect(element: Ref) { 6 | return computed(() => (element?.value ? getWindowClientRect(element.value) : null)); 7 | } 8 | -------------------------------------------------------------------------------- /packages/core/test/core.test.tsx: -------------------------------------------------------------------------------- 1 | import { expect, test, describe } from 'vitest' 2 | import Comp from "./demo/CoreTest"; 3 | import {mount} from "@vue/test-utils"; 4 | 5 | test('Draggable Test', async () => { 6 | const wrapper = mount(Comp, {}) 7 | const text = wrapper.get('span').text() 8 | expect(text).toEqual('handle') 9 | }) 10 | -------------------------------------------------------------------------------- /src/components/icons/IconSupport.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /packages/modifiers/src/createSnapModifier.ts: -------------------------------------------------------------------------------- 1 | import type {Modifier} from '@dnd-kit-vue/core'; 2 | 3 | export function createSnapModifier(gridSize: number): Modifier { 4 | return ({transform}) => ({ 5 | ...transform, 6 | x: Math.ceil(transform.x / gridSize) * gridSize, 7 | y: Math.ceil(transform.y / gridSize) * gridSize, 8 | }); 9 | } 10 | -------------------------------------------------------------------------------- /packages/accessibility/README.md: -------------------------------------------------------------------------------- 1 | # @dnd-kit-vue/accessibility 2 | 3 | [![Stable release](https://img.shields.io/npm/v/@dnd-kit-vue/accessibility.svg)](https://npm.im/@dnd-kit-vue/accessibility) 4 | 5 | A generic set of components and hooks to help with live region announcements and screen reader instructions. This package is used internally by `@dnd-kit-vue/core`. 6 | -------------------------------------------------------------------------------- /packages/accessibility/src/hooks/useAnnouncement.ts: -------------------------------------------------------------------------------- 1 | import {ref} from "vue"; 2 | 3 | export function useAnnouncement() { 4 | const announcement = ref('') 5 | const announce = (value: string | undefined) => { 6 | if (value != null) { 7 | announcement.value = value 8 | } 9 | } 10 | 11 | return {announce, announcement} as const; 12 | } 13 | -------------------------------------------------------------------------------- /packages/core/src/components/DragOverlay/components/index.ts: -------------------------------------------------------------------------------- 1 | export {AnimationManager} from './AnimationManager'; 2 | export type {Animation} from './AnimationManager'; 3 | export {NullifiedContextProvider} from './NullifiedContextProvider'; 4 | export {PositionedOverlay} from './PositionedOverlay'; 5 | export type {PositionedOverlayProps} from './PositionedOverlay'; 6 | -------------------------------------------------------------------------------- /packages/sortable/src/utilities/normalizeDisabled.ts: -------------------------------------------------------------------------------- 1 | import type {Disabled} from '../types'; 2 | 3 | export function normalizeDisabled(disabled: boolean | Disabled): Disabled { 4 | if (typeof disabled === 'boolean') { 5 | return { 6 | draggable: disabled, 7 | droppable: disabled, 8 | }; 9 | } 10 | 11 | return disabled; 12 | } 13 | -------------------------------------------------------------------------------- /packages/sortable/src/types/strategies.ts: -------------------------------------------------------------------------------- 1 | import type {ClientRect} from '@dnd-kit-vue/core'; 2 | import type {Transform} from '@dnd-kit-vue/utilities'; 3 | 4 | export type SortingStrategy = (args: { 5 | activeNodeRect: ClientRect | null; 6 | activeIndex: number; 7 | index: number; 8 | rects: ClientRect[]; 9 | overIndex: number; 10 | }) => Transform | null; 11 | -------------------------------------------------------------------------------- /packages/utilities/src/type-guards/isHTMLElement.ts: -------------------------------------------------------------------------------- 1 | import {getWindow} from '../execution-context/getWindow'; 2 | 3 | import {isWindow} from './isWindow'; 4 | 5 | export function isHTMLElement(node: Node | Window): node is HTMLElement { 6 | if (isWindow(node)) { 7 | return false; 8 | } 9 | 10 | return node instanceof getWindow(node).HTMLElement; 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/src/utilities/algorithms/index.ts: -------------------------------------------------------------------------------- 1 | export {closestCenter} from './closestCenter'; 2 | export {closestCorners} from './closestCorners'; 3 | export {rectIntersection} from './rectIntersection'; 4 | export {pointerWithin} from './pointerWithin'; 5 | export type {Collision, CollisionDescriptor, CollisionDetection} from './types'; 6 | export {getFirstCollision} from './helpers'; 7 | -------------------------------------------------------------------------------- /packages/utilities/src/event/isTouchEvent.ts: -------------------------------------------------------------------------------- 1 | import {getWindow} from '../execution-context'; 2 | 3 | export function isTouchEvent( 4 | event: Event | undefined | null 5 | ): event is TouchEvent { 6 | if (!event) { 7 | return false; 8 | } 9 | 10 | const {TouchEvent} = getWindow(event.target); 11 | 12 | return TouchEvent && event instanceof TouchEvent; 13 | } 14 | -------------------------------------------------------------------------------- /packages/utilities/src/type-guards/isWindow.ts: -------------------------------------------------------------------------------- 1 | export function isWindow(element: Object): element is typeof window { 2 | const elementString = Object.prototype.toString.call(element); 3 | return ( 4 | elementString === '[object Window]' || 5 | // In Electron context the Window object serializes to [object global] 6 | elementString === '[object global]' 7 | ); 8 | } 9 | -------------------------------------------------------------------------------- /packages/core/src/hooks/utilities/useInitialRect.ts: -------------------------------------------------------------------------------- 1 | import type {ClientRect} from '../../types'; 2 | import {useInitialValue} from './useInitialValue'; 3 | import type {ComputedRef} from "vue"; 4 | 5 | export function useInitialRect( 6 | node: ComputedRef | null, 7 | measure: (node: HTMLElement) => ClientRect 8 | ) { 9 | return useInitialValue(node, measure); 10 | } 11 | -------------------------------------------------------------------------------- /packages/core/src/utilities/rect/index.ts: -------------------------------------------------------------------------------- 1 | export {adjustScale} from './adjustScale'; 2 | 3 | export {getRectDelta} from './getRectDelta'; 4 | 5 | export {getAdjustedRect} from './rectAdjustment'; 6 | 7 | export {getClientRect, getTransformAgnosticClientRect} from './getRect'; 8 | 9 | export {getWindowClientRect} from './getWindowClientRect'; 10 | 11 | export {Rect} from './Rect'; 12 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /packages/sortable/test/SortableDemo1.spec.tsx: -------------------------------------------------------------------------------- 1 | import { expect, test, describe } from 'vitest' 2 | import Comp from "./demo1/SortableDemo1"; 3 | import {mount} from "@vue/test-utils"; 4 | 5 | test('SortableDemo1 test', async () => { 6 | const wrapper = mount(Comp, {}) 7 | const text = wrapper.findAll('div[aria-roledescription="sortable"]') 8 | expect(text.length).toEqual(10) 9 | }) 10 | -------------------------------------------------------------------------------- /packages/utilities/src/event/isKeyboardEvent.ts: -------------------------------------------------------------------------------- 1 | import {getWindow} from '../execution-context'; 2 | 3 | export function isKeyboardEvent( 4 | event: Event | undefined | null 5 | ): event is KeyboardEvent { 6 | if (!event) { 7 | return false; 8 | } 9 | 10 | const {KeyboardEvent} = getWindow(event.target); 11 | 12 | return KeyboardEvent && event instanceof KeyboardEvent; 13 | } 14 | -------------------------------------------------------------------------------- /packages/sortable/src/utilities/arraySwap.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Swap an array item to a different position. Returns a new array with the item swapped to the new position. 3 | */ 4 | export function arraySwap(array: T[], from: number, to: number): T[] { 5 | const newArray = array.slice(); 6 | 7 | newArray[from] = array[to]; 8 | newArray[to] = array[from]; 9 | 10 | return newArray; 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/src/hooks/useDndContext.ts: -------------------------------------------------------------------------------- 1 | 2 | import {PublicContext, type PublicContextDescriptor} from '../store'; 3 | import {inject, ref} from "vue"; 4 | import {defaultPublicContext} from "../store/context"; 5 | 6 | export function useDndContext() { 7 | return inject('PublicContext', ref(defaultPublicContext)); 8 | } 9 | 10 | export type UseDndContextReturnValue = any; 11 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /packages/core/src/hooks/utilities/useScrollIntoViewIfNeeded.ts: -------------------------------------------------------------------------------- 1 | import {useIsomorphicLayoutEffect} from '@dnd-kit-vue/utilities'; 2 | 3 | import {scrollIntoViewIfNeeded} from '../../utilities/scroll'; 4 | 5 | export function useScrollIntoViewIfNeeded( 6 | element: HTMLElement | null | undefined 7 | ) { 8 | useIsomorphicLayoutEffect(() => { 9 | scrollIntoViewIfNeeded(element); 10 | }); 11 | } 12 | -------------------------------------------------------------------------------- /packages/core/src/components/DragOverlay/index.ts: -------------------------------------------------------------------------------- 1 | export {DragOverlay} from './DragOverlay'; 2 | export type {Props} from './DragOverlay'; 3 | export {defaultDropAnimation, defaultDropAnimationSideEffects} from './hooks'; 4 | export type { 5 | DropAnimation, 6 | DropAnimationFunction, 7 | DropAnimationFunctionArguments, 8 | DropAnimationKeyframeResolver, 9 | DropAnimationSideEffects, 10 | } from './hooks'; 11 | -------------------------------------------------------------------------------- /packages/core/src/utilities/rect/getWindowClientRect.ts: -------------------------------------------------------------------------------- 1 | import type {ClientRect} from '../../types'; 2 | 3 | export function getWindowClientRect(element: typeof window): ClientRect { 4 | const width = element.innerWidth; 5 | const height = element.innerHeight; 6 | 7 | return { 8 | top: 0, 9 | left: 0, 10 | right: width, 11 | bottom: height, 12 | width, 13 | height, 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /src/components/__tests__/HelloWorld.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, it, expect } from 'vitest' 2 | 3 | import { mount } from '@vue/test-utils' 4 | import HelloWorld from '../HelloWorld.vue' 5 | 6 | describe('HelloWorld', () => { 7 | it('renders properly', () => { 8 | const wrapper = mount(HelloWorld, { props: { msg: 'Hello Vitest' } }) 9 | expect(wrapper.text()).toContain('Hello Vitest') 10 | }) 11 | }) 12 | -------------------------------------------------------------------------------- /packages/core/src/utilities/watchRef.ts: -------------------------------------------------------------------------------- 1 | import type { WatchSource } from 'vue' 2 | import { ref, watch } from 'vue' 3 | 4 | 5 | export default function watchRef(callback: ()=> T, dependencies: (WatchSource | object)[]){ 6 | const valueRef = ref(callback()) 7 | watch(dependencies, ()=>{ 8 | // @ts-ignore 9 | valueRef.value = callback() 10 | }, {immediate: true}) 11 | return valueRef 12 | } 13 | -------------------------------------------------------------------------------- /packages/utilities/src/types.ts: -------------------------------------------------------------------------------- 1 | export type Arguments = T extends (...args: infer U) => any ? U : never; 2 | 3 | export type DeepRequired = { 4 | [K in keyof T]-?: Required; 5 | }; 6 | 7 | export type FirstArgument = T extends ( 8 | firstArg: infer U, 9 | ...args: Array 10 | ) => any 11 | ? U 12 | : never; 13 | 14 | export type Without = Pick>; 15 | -------------------------------------------------------------------------------- /packages/accessibility/src/components/HiddenText/HiddenText.tsx: -------------------------------------------------------------------------------- 1 | import {type CSSProperties, h} from "vue"; 2 | 3 | interface Props { 4 | id: string; 5 | value: string; 6 | } 7 | 8 | const hiddenStyles: CSSProperties = { 9 | display: 'none', 10 | }; 11 | 12 | export function HiddenText({id, value}: Props) { 13 | return ( 14 |
15 | {value} 16 |
17 | ); 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/sensors/useSensor.ts: -------------------------------------------------------------------------------- 1 | import type {Sensor, SensorDescriptor, SensorOptions} from './types'; 2 | import {computed, type ComputedRef} from "vue"; 3 | 4 | export function useSensor( 5 | sensor: Sensor, 6 | options?: T 7 | ): ComputedRef> { 8 | return computed(() => ({ 9 | sensor, 10 | options: options ?? ({} as T), 11 | }) 12 | ); 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/src/sensors/pointer/index.ts: -------------------------------------------------------------------------------- 1 | export {AbstractPointerSensor} from './AbstractPointerSensor'; 2 | export type { 3 | PointerActivationConstraint, 4 | PointerEventHandlers, 5 | AbstractPointerSensorOptions, 6 | AbstractPointerSensorProps, 7 | } from './AbstractPointerSensor'; 8 | 9 | export {PointerSensor} from './PointerSensor'; 10 | export type {PointerSensorOptions, PointerSensorProps} from './PointerSensor'; 11 | -------------------------------------------------------------------------------- /packages/utilities/src/hooks/useUniqueId.ts: -------------------------------------------------------------------------------- 1 | import {computed} from "vue"; 2 | 3 | let ids: Record = {}; 4 | 5 | export function useUniqueId(prefix: string, value?: string) { 6 | return computed(() => { 7 | if (value) { 8 | return value; 9 | } 10 | 11 | const id = ids[prefix] == null ? 0 : ids[prefix] + 1; 12 | ids[prefix] = id; 13 | 14 | return `${prefix}-${id}`; 15 | }); 16 | } 17 | -------------------------------------------------------------------------------- /packages/sortable/src/utilities/arrayMove.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Move an array item to a different position. Returns a new array with the item moved to the new position. 3 | */ 4 | export function arrayMove(array: T[], from: number, to: number): T[] { 5 | const newArray = array.slice(); 6 | newArray.splice( 7 | to < 0 ? newArray.length + to : to, 8 | 0, 9 | newArray.splice(from, 1)[0] 10 | ); 11 | 12 | return newArray; 13 | } 14 | -------------------------------------------------------------------------------- /packages/core/src/hooks/utilities/useRectDelta.ts: -------------------------------------------------------------------------------- 1 | import type {ClientRect} from '../../types'; 2 | import {getRectDelta} from '../../utilities'; 3 | 4 | import {useInitialValue} from './useInitialValue'; 5 | import type { ComputedRef, Ref } from 'vue' 6 | 7 | export function useRectDelta(rect: Ref) { 8 | const initialRect = useInitialValue(rect); 9 | return getRectDelta(rect.value, initialRect.value as ClientRect); 10 | } 11 | -------------------------------------------------------------------------------- /packages/utilities/src/focus/findFirstFocusableNode.ts: -------------------------------------------------------------------------------- 1 | const SELECTOR = 2 | 'a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]'; 3 | 4 | export function findFirstFocusableNode( 5 | element: HTMLElement 6 | ): HTMLElement | null { 7 | if (element.matches(SELECTOR)) { 8 | return element; 9 | } 10 | 11 | return element.querySelector(SELECTOR); 12 | } 13 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath } from 'node:url' 2 | import { mergeConfig, defineConfig, configDefaults } from 'vitest/config' 3 | import viteConfig from './vite.config' 4 | 5 | export default mergeConfig( 6 | viteConfig, 7 | defineConfig({ 8 | test: { 9 | environment: 'jsdom', 10 | exclude: [...configDefaults.exclude, 'e2e/**'], 11 | root: fileURLToPath(new URL('./', import.meta.url)) 12 | } 13 | }) 14 | ) 15 | -------------------------------------------------------------------------------- /packages/core/src/utilities/rect/getRectDelta.ts: -------------------------------------------------------------------------------- 1 | import type {Coordinates, ClientRect} from '../../types'; 2 | import {defaultCoordinates} from '../coordinates'; 3 | 4 | export function getRectDelta( 5 | rect1: ClientRect | null, 6 | rect2: ClientRect | null 7 | ): Coordinates { 8 | return rect1 && rect2 9 | ? { 10 | x: rect1.left - rect2.left, 11 | y: rect1.top - rect2.top, 12 | } 13 | : defaultCoordinates; 14 | } 15 | -------------------------------------------------------------------------------- /.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 | .DS_Store 12 | dist 13 | dist-ssr 14 | coverage 15 | *.local 16 | 17 | /cypress/videos/ 18 | /cypress/screenshots/ 19 | 20 | # Editor directories and files 21 | .vscode/* 22 | !.vscode/extensions.json 23 | .idea 24 | *.suo 25 | *.ntvs* 26 | *.njsproj 27 | *.sln 28 | *.sw? 29 | 30 | *.tsbuildinfo 31 | -------------------------------------------------------------------------------- /packages/core/src/components/DragOverlay/hooks/useKey.ts: -------------------------------------------------------------------------------- 1 | 2 | import type {UniqueIdentifier} from '../../../types'; 3 | import {computed, type ComputedRef, ref, watch} from "vue"; 4 | 5 | let key = 0; 6 | 7 | export function useKey(id: ComputedRef) { 8 | const keyShallowRef = ref(key) 9 | // 这里的问题 10 | watch(()=>id?.value, ()=>{ 11 | key++ 12 | keyShallowRef.value = key 13 | }) 14 | return keyShallowRef 15 | } 16 | -------------------------------------------------------------------------------- /packages/core/src/types/coordinates.ts: -------------------------------------------------------------------------------- 1 | import type {Coordinates} from '@dnd-kit-vue/utilities'; 2 | 3 | export type {Coordinates}; 4 | 5 | export type DistanceMeasurement = 6 | | number 7 | | Coordinates 8 | | Pick 9 | | Pick; 10 | 11 | export type Translate = Coordinates; 12 | 13 | export interface ScrollCoordinates { 14 | initial: Coordinates; 15 | current: Coordinates; 16 | delta: Coordinates; 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/src/utilities/nodes/getMeasurableNode.ts: -------------------------------------------------------------------------------- 1 | import {isHTMLElement} from '@dnd-kit-vue/utilities'; 2 | 3 | export function getMeasurableNode( 4 | node: HTMLElement | undefined | null 5 | ): HTMLElement | null { 6 | if (!node) { 7 | return null; 8 | } 9 | 10 | if (node.children.length > 1) { 11 | return node; 12 | } 13 | const firstChild = node.children[0]; 14 | 15 | return isHTMLElement(firstChild) ? firstChild : node; 16 | } 17 | -------------------------------------------------------------------------------- /packages/modifiers/src/restrictToWindowEdges.ts: -------------------------------------------------------------------------------- 1 | import type {Modifier} from '@dnd-kit-vue/core'; 2 | 3 | import {restrictToBoundingRect} from './utilities'; 4 | 5 | export const restrictToWindowEdges: Modifier = ({ 6 | transform, 7 | draggingNodeRect, 8 | windowRect, 9 | }) => { 10 | if (!draggingNodeRect || !windowRect) { 11 | return transform; 12 | } 13 | 14 | return restrictToBoundingRect(transform, draggingNodeRect, windowRect); 15 | }; 16 | -------------------------------------------------------------------------------- /packages/sortable/src/utilities/itemsEqual.ts: -------------------------------------------------------------------------------- 1 | import type {UniqueIdentifier} from '@dnd-kit-vue/core'; 2 | 3 | export function itemsEqual(a: UniqueIdentifier[], b: UniqueIdentifier[]) { 4 | if (a === b) { 5 | return true; 6 | } 7 | 8 | if (a.length !== b.length) { 9 | return false; 10 | } 11 | 12 | for (let i = 0; i < a.length; i++) { 13 | if (a[i] !== b[i]) { 14 | return false; 15 | } 16 | } 17 | 18 | return true; 19 | } 20 | -------------------------------------------------------------------------------- /packages/utilities/src/hooks/useIsomorphicLayoutEffect.ts: -------------------------------------------------------------------------------- 1 | 2 | import {canUseDOM} from '../execution-context'; 3 | import {onMounted, onBeforeMount} from "vue"; 4 | 5 | /** 6 | * A hook that resolves to useEffect on the server and useLayoutEffect on the client 7 | * @param callback {function} Callback function that is invoked when the dependencies of the hook change 8 | */ 9 | export const useIsomorphicLayoutEffect = canUseDOM 10 | ? onBeforeMount 11 | : onMounted; 12 | -------------------------------------------------------------------------------- /packages/modifiers/src/restrictToParentElement.ts: -------------------------------------------------------------------------------- 1 | import type {Modifier} from '@dnd-kit-vue/core'; 2 | import {restrictToBoundingRect} from './utilities'; 3 | 4 | export const restrictToParentElement: Modifier = ({ 5 | containerNodeRect, 6 | draggingNodeRect, 7 | transform, 8 | }) => { 9 | if (!draggingNodeRect || !containerNodeRect) { 10 | return transform; 11 | } 12 | 13 | return restrictToBoundingRect(transform, draggingNodeRect, containerNodeRect); 14 | }; 15 | -------------------------------------------------------------------------------- /packages/core/src/sensors/events.ts: -------------------------------------------------------------------------------- 1 | export enum EventName { 2 | Click = 'click', 3 | DragStart = 'dragstart', 4 | Keydown = 'keydown', 5 | ContextMenu = 'contextmenu', 6 | Resize = 'resize', 7 | SelectionChange = 'selectionchange', 8 | VisibilityChange = 'visibilitychange', 9 | } 10 | 11 | export function preventDefault(event: Event) { 12 | event.preventDefault(); 13 | } 14 | 15 | export function stopPropagation(event: Event) { 16 | event.stopPropagation(); 17 | } 18 | -------------------------------------------------------------------------------- /packages/core/src/utilities/rect/adjustScale.ts: -------------------------------------------------------------------------------- 1 | import type {Transform} from '@dnd-kit-vue/utilities'; 2 | import type {ClientRect} from '../../types'; 3 | 4 | export function adjustScale( 5 | transform: Transform, 6 | rect1: ClientRect | null, 7 | rect2: ClientRect | null 8 | ): Transform { 9 | return { 10 | ...transform, 11 | scaleX: rect1 && rect2 ? rect1.width / rect2.width : 1, 12 | scaleY: rect1 && rect2 ? rect1.height / rect2.height : 1, 13 | }; 14 | } 15 | -------------------------------------------------------------------------------- /packages/core/src/components/DragOverlay/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | useDropAnimation, 3 | defaultDropAnimationConfiguration as defaultDropAnimation, 4 | defaultDropAnimationSideEffects, 5 | } from './useDropAnimation'; 6 | export type { 7 | DropAnimation, 8 | DropAnimationFunction, 9 | DropAnimationFunctionArguments, 10 | KeyframeResolver as DropAnimationKeyframeResolver, 11 | DropAnimationSideEffects, 12 | } from './useDropAnimation'; 13 | export {useKey} from './useKey'; 14 | -------------------------------------------------------------------------------- /packages/utilities/src/execution-context/getWindow.ts: -------------------------------------------------------------------------------- 1 | import {isWindow} from '../type-guards/isWindow'; 2 | import {isNode} from '../type-guards/isNode'; 3 | 4 | export function getWindow(target: Event['target']): typeof window { 5 | if (!target) { 6 | return window; 7 | } 8 | 9 | if (isWindow(target)) { 10 | return target; 11 | } 12 | 13 | if (!isNode(target)) { 14 | return window; 15 | } 16 | 17 | return target.ownerDocument?.defaultView ?? window; 18 | } 19 | -------------------------------------------------------------------------------- /packages/utilities/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export {useCombinedRefs} from './useCombinedRefs'; 2 | export {useEvent} from './useEvent'; 3 | export {useIsomorphicLayoutEffect} from './useIsomorphicLayoutEffect'; 4 | export {useInterval} from './useInterval'; 5 | export {useLatestValue} from './useLatestValue'; 6 | export {useLazyMemo} from './useLazyMemo'; 7 | export {useNodeRef} from './useNodeRef'; 8 | export {usePrevious} from './usePrevious'; 9 | export {useUniqueId} from './useUniqueId'; 10 | -------------------------------------------------------------------------------- /src/assets/vue.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /packages/core/src/sensors/useSensors.ts: -------------------------------------------------------------------------------- 1 | import type {SensorDescriptor, SensorOptions} from './types'; 2 | import {computed, type ComputedRef} from "vue"; 3 | 4 | export function useSensors( 5 | ...sensors: ComputedRef<(SensorDescriptor | undefined | null)>[] 6 | ): ComputedRef[]> { 7 | return computed(() => 8 | [...sensors].map(item=>item.value).filter( 9 | (sensor): sensor is SensorDescriptor => sensor != null 10 | ) 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /packages/core/src/types/index.ts: -------------------------------------------------------------------------------- 1 | export type { 2 | Coordinates, 3 | DistanceMeasurement, 4 | Translate, 5 | ScrollCoordinates, 6 | } from './coordinates'; 7 | export {Direction} from './direction'; 8 | export type { 9 | DragStartEvent, 10 | DragCancelEvent, 11 | DragEndEvent, 12 | DragMoveEvent, 13 | DragOverEvent, 14 | } from './events'; 15 | export type {UniqueIdentifier} from './other'; 16 | export type {SyntheticEventName} from './react'; 17 | export type {ClientRect} from './rect'; 18 | -------------------------------------------------------------------------------- /packages/modifiers/src/index.ts: -------------------------------------------------------------------------------- 1 | export {createSnapModifier} from './createSnapModifier'; 2 | export {restrictToHorizontalAxis} from './restrictToHorizontalAxis'; 3 | export {restrictToParentElement} from './restrictToParentElement'; 4 | export {restrictToFirstScrollableAncestor} from './restrictToFirstScrollableAncestor'; 5 | export {restrictToVerticalAxis} from './restrictToVerticalAxis'; 6 | export {restrictToWindowEdges} from './restrictToWindowEdges'; 7 | export {snapCenterToCursor} from './snapCenterToCursor'; 8 | -------------------------------------------------------------------------------- /packages/utilities/src/hooks/useLatestValue.ts: -------------------------------------------------------------------------------- 1 | import {ref, shallowRef, watch } from 'vue'; 2 | import type {ComputedRef, Ref } from 'vue'; 3 | 4 | 5 | export function useLatestValue( 6 | value: T, 7 | dependencies: any[] = [value] 8 | ) { 9 | const valueRef = shallowRef(value.value); 10 | 11 | watch(dependencies, ()=>{ 12 | if (valueRef.value !== value.value) { 13 | valueRef.value = value.value; 14 | } 15 | }, {immediate: true}) 16 | 17 | return valueRef; 18 | } 19 | -------------------------------------------------------------------------------- /packages/utilities/src/hooks/useInterval.ts: -------------------------------------------------------------------------------- 1 | import {ref} from 'vue'; 2 | 3 | export function useInterval() { 4 | const intervalRef = ref(null); 5 | 6 | const set = (listener: Function, duration: number) => { 7 | intervalRef.value = setInterval(listener, duration); 8 | } 9 | 10 | const clear = () => { 11 | if (intervalRef.value !== null) { 12 | clearInterval(intervalRef.value); 13 | intervalRef.value = null; 14 | } 15 | } 16 | 17 | return [set, clear] as const; 18 | } 19 | -------------------------------------------------------------------------------- /packages/utilities/src/hooks/useLazyMemo.ts: -------------------------------------------------------------------------------- 1 | import { type ShallowRef, shallowRef, watch } from "vue"; 2 | 3 | export function useLazyMemo( 4 | callback: (prevValue: T | undefined) => T, 5 | dependencies: any[] 6 | ): ShallowRef { 7 | const valueRef = shallowRef(); 8 | 9 | watch( 10 | dependencies, 11 | () => { 12 | const newValue = callback(valueRef.value); 13 | valueRef.value = newValue; 14 | return newValue; 15 | }, 16 | { immediate: true } 17 | ); 18 | return valueRef; 19 | } 20 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { fileURLToPath, URL } from 'node:url' 2 | 3 | import { defineConfig } from 'vite' 4 | import vue from '@vitejs/plugin-vue' 5 | import vueJsx from '@vitejs/plugin-vue-jsx' 6 | import VueDevTools from 'vite-plugin-vue-devtools' 7 | 8 | // https://vitejs.dev/config/ 9 | export default defineConfig({ 10 | plugins: [ 11 | vue(), 12 | vueJsx(), 13 | // VueDevTools(), 14 | ], 15 | resolve: { 16 | alias: { 17 | '@': fileURLToPath(new URL('./src', import.meta.url)) 18 | } 19 | } 20 | }) 21 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /packages/core/src/store/index.ts: -------------------------------------------------------------------------------- 1 | export {Action} from './actions'; 2 | export { 3 | PublicContext, 4 | InternalContext, 5 | defaultInternalContext, 6 | } from './context'; 7 | export {reducer, getInitialState} from './reducer'; 8 | export type { 9 | Active, 10 | Data, 11 | DataRef, 12 | DraggableElement, 13 | DraggableNode, 14 | DraggableNodes, 15 | DroppableContainer, 16 | DroppableContainers, 17 | PublicContextDescriptor, 18 | InternalContextDescriptor, 19 | RectMap, 20 | Over, 21 | State, 22 | } from './types'; 23 | -------------------------------------------------------------------------------- /packages/core/README.md: -------------------------------------------------------------------------------- 1 | # @dnd-kit-vue/core 2 | 3 | [![Stable release](https://img.shields.io/npm/v/@dnd-kit-vue/core.svg)](https://npm.im/@dnd-kit-vue/core) 4 | 5 | @dnd-kit – a lightweight React library for building performant and accessible drag and drop experiences. 6 | 7 | ## Installation 8 | 9 | To get started, install the `@dnd-kit-vue/core` package via npm or yarn: 10 | 11 | ``` 12 | npm install @dnd-kit-vue/core 13 | ``` 14 | 15 | ## Usage 16 | 17 | Visit [docs.dndkit.com](https://docs.dndkit.com) to learn how to get started with @dnd-kit. 18 | -------------------------------------------------------------------------------- /packages/sortable/src/utilities/getSortedRects.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | ClientRect, 3 | UniqueIdentifier, 4 | UseDndContextReturnValue, 5 | } from '@dnd-kit-vue/core'; 6 | 7 | export function getSortedRects( 8 | items: UniqueIdentifier[], 9 | rects: UseDndContextReturnValue['droppableRects'] 10 | ) { 11 | return items.reduce((accumulator, id, index) => { 12 | const rect = rects.get(id); 13 | 14 | if (rect) { 15 | accumulator[index] = rect; 16 | } 17 | 18 | return accumulator; 19 | }, Array(items.length)); 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/src/modifiers/applyModifiers.ts: -------------------------------------------------------------------------------- 1 | import type {FirstArgument, Transform} from '@dnd-kit-vue/utilities'; 2 | 3 | import type {Modifiers, Modifier} from './types'; 4 | 5 | export function applyModifiers( 6 | modifiers: Modifiers | undefined, 7 | {transform, ...args}: FirstArgument 8 | ): Transform { 9 | return modifiers?.length 10 | ? modifiers.reduce((accumulator, modifier) => { 11 | return modifier({ 12 | transform: accumulator, 13 | ...args, 14 | }); 15 | }, transform) 16 | : transform; 17 | } 18 | -------------------------------------------------------------------------------- /packages/utilities/src/execution-context/getOwnerDocument.ts: -------------------------------------------------------------------------------- 1 | import {isWindow, isHTMLElement, isDocument, isNode} from '../type-guards'; 2 | 3 | export function getOwnerDocument(target: Event['target']): Document { 4 | if (!target) { 5 | return document; 6 | } 7 | 8 | if (isWindow(target)) { 9 | return target.document; 10 | } 11 | 12 | if (!isNode(target)) { 13 | return document; 14 | } 15 | 16 | if (isDocument(target)) { 17 | return target; 18 | } 19 | 20 | if (isHTMLElement(target)) { 21 | return target.ownerDocument; 22 | } 23 | 24 | return document; 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/hooks/utilities/useSensorSetup.ts: -------------------------------------------------------------------------------- 1 | import {canUseDOM} from '@dnd-kit-vue/utilities'; 2 | 3 | import type {SensorDescriptor} from '../../sensors'; 4 | import {watchEffect} from "vue"; 5 | 6 | export function useSensorSetup(sensors: SensorDescriptor[]) { 7 | watchEffect( 8 | () => { 9 | if (!canUseDOM) { 10 | return; 11 | } 12 | 13 | const teardownFns = sensors.map(({sensor}) => sensor.setup?.()); 14 | 15 | return () => { 16 | for (const teardown of teardownFns) { 17 | teardown?.(); 18 | } 19 | }; 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /packages/core/src/utilities/scroll/isScrollable.ts: -------------------------------------------------------------------------------- 1 | import {getWindow} from '@dnd-kit-vue/utilities'; 2 | 3 | export function isScrollable( 4 | element: HTMLElement, 5 | computedStyle: CSSStyleDeclaration = getWindow(element).getComputedStyle( 6 | element 7 | ) 8 | ): boolean { 9 | const overflowRegex = /(auto|scroll|overlay)/; 10 | const properties = ['overflow', 'overflowX', 'overflowY']; 11 | 12 | return properties.some((property) => { 13 | const value = computedStyle[property as keyof CSSStyleDeclaration]; 14 | 15 | return typeof value === 'string' ? overflowRegex.test(value) : false; 16 | }); 17 | } 18 | -------------------------------------------------------------------------------- /packages/modifiers/src/restrictToFirstScrollableAncestor.ts: -------------------------------------------------------------------------------- 1 | import type {Modifier} from '@dnd-kit-vue/core'; 2 | import {restrictToBoundingRect} from './utilities'; 3 | 4 | export const restrictToFirstScrollableAncestor: Modifier = ({ 5 | draggingNodeRect, 6 | transform, 7 | scrollableAncestorRects, 8 | }) => { 9 | const firstScrollableAncestorRect = scrollableAncestorRects[0]; 10 | 11 | if (!draggingNodeRect || !firstScrollableAncestorRect) { 12 | return transform; 13 | } 14 | 15 | return restrictToBoundingRect( 16 | transform, 17 | draggingNodeRect, 18 | firstScrollableAncestorRect 19 | ); 20 | }; 21 | -------------------------------------------------------------------------------- /packages/core/src/components/DndContext/types.ts: -------------------------------------------------------------------------------- 1 | import type {ClientRect} from '../../types'; 2 | import type {DroppableMeasuring} from '../../hooks/utilities'; 3 | 4 | export type MeasuringFunction = (node: HTMLElement) => ClientRect; 5 | 6 | interface Measuring { 7 | measure: MeasuringFunction; 8 | } 9 | 10 | export interface DraggableMeasuring extends Measuring {} 11 | 12 | export interface DragOverlayMeasuring extends Measuring {} 13 | 14 | export interface MeasuringConfiguration { 15 | draggable?: Partial; 16 | droppable?: Partial; 17 | dragOverlay?: Partial; 18 | } 19 | -------------------------------------------------------------------------------- /packages/utilities/src/hooks/useNodeRef.ts: -------------------------------------------------------------------------------- 1 | 2 | import {useEvent} from './useEvent'; 3 | import {ref} from "vue"; 4 | 5 | export function useNodeRef( 6 | onChange?: ( 7 | newElement: HTMLElement | null, 8 | previousElement: HTMLElement | null 9 | ) => void 10 | ) { 11 | const onChangeHandler = useEvent(onChange); 12 | const node = ref(null); 13 | const setNodeRef = (element: HTMLElement | null) => { 14 | if (element !== node.value) { 15 | onChangeHandler?.value?.(element, node.value); 16 | } 17 | 18 | node.value = element; 19 | } 20 | 21 | return [node, setNodeRef] as const; 22 | } 23 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import {defineComponent, ref, h, Fragment} from 'vue' 2 | import CoreTest from "../packages/core/test/demo/CoreTest"; 3 | import SortableDemo1 from "../packages/sortable/test/demo1/SortableDemo1"; 4 | 5 | interface CoreTestProps { 6 | name?: string 7 | } 8 | 9 | export const vuePropsType = { 10 | name: String 11 | } 12 | const App = defineComponent((props, {slots}) => { 13 | 14 | 15 | return () => ( 16 |
17 | 18 | 19 | {/**/} 20 |
21 | ) 22 | }) 23 | 24 | App.props = vuePropsType 25 | App.name = 'App' 26 | export default App 27 | 28 | -------------------------------------------------------------------------------- /packages/core/src/components/Accessibility/types.ts: -------------------------------------------------------------------------------- 1 | import type {Active, Over} from '../../store'; 2 | 3 | export interface Arguments { 4 | active: Active; 5 | over: Over | null; 6 | } 7 | 8 | export interface Announcements { 9 | onDragStart({active}: Pick): string | undefined; 10 | onDragMove?({active, over}: Arguments): string | undefined; 11 | onDragOver({active, over}: Arguments): string | undefined; 12 | onDragEnd({active, over}: Arguments): string | undefined; 13 | onDragCancel({active, over}: Arguments): string | undefined; 14 | } 15 | 16 | export interface ScreenReaderInstructions { 17 | draggable: string; 18 | } 19 | -------------------------------------------------------------------------------- /packages/core/src/components/DndMonitor/useDndMonitorProvider.tsx: -------------------------------------------------------------------------------- 1 | 2 | 3 | import type {DndMonitorListener, DndMonitorEvent} from './types'; 4 | import {ref} from "vue"; 5 | 6 | export function useDndMonitorProvider() { 7 | const listeners = ref(new Set()); 8 | 9 | const registerListener = (listener:any) => { 10 | listeners.value.add(listener); 11 | return () => listeners.value.delete(listener); 12 | }; 13 | 14 | const dispatch = ({type, event}: DndMonitorEvent) => { 15 | listeners.value.forEach((listener) => listener[type]?.(event as any)); 16 | }; 17 | 18 | return [dispatch, registerListener] as const; 19 | } 20 | -------------------------------------------------------------------------------- /packages/core/src/hooks/index.ts: -------------------------------------------------------------------------------- 1 | export {useDraggable} from './useDraggable'; 2 | export type { 3 | DraggableAttributes, 4 | DraggableSyntheticListeners, 5 | UseDraggableArguments, 6 | } from './useDraggable'; 7 | export {useDndContext} from './useDndContext'; 8 | export type {UseDndContextReturnValue} from './useDndContext'; 9 | export {useDroppable} from './useDroppable'; 10 | export type {UseDroppableArguments} from './useDroppable'; 11 | 12 | export { 13 | AutoScrollActivator, 14 | MeasuringStrategy, 15 | MeasuringFrequency, 16 | TraversalOrder, 17 | } from './utilities'; 18 | export type {AutoScrollOptions, DroppableMeasuring} from './utilities'; 19 | -------------------------------------------------------------------------------- /src/assets/main.css: -------------------------------------------------------------------------------- 1 | @import './base.css'; 2 | 3 | #app { 4 | max-width: 1280px; 5 | margin: 0 auto; 6 | padding: 2rem; 7 | font-weight: normal; 8 | } 9 | 10 | a, 11 | .green { 12 | text-decoration: none; 13 | color: hsla(160, 100%, 37%, 1); 14 | transition: 0.4s; 15 | padding: 3px; 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 | -------------------------------------------------------------------------------- /packages/core/src/utilities/scroll/getScrollElementRect.ts: -------------------------------------------------------------------------------- 1 | export function getScrollElementRect(element: Element) { 2 | if (element === document.scrollingElement) { 3 | const {innerWidth, innerHeight} = window; 4 | 5 | return { 6 | top: 0, 7 | left: 0, 8 | right: innerWidth, 9 | bottom: innerHeight, 10 | width: innerWidth, 11 | height: innerHeight, 12 | }; 13 | } 14 | 15 | const {top, left, right, bottom} = element.getBoundingClientRect(); 16 | 17 | return { 18 | top, 19 | left, 20 | right, 21 | bottom, 22 | width: element.clientWidth, 23 | height: element.clientHeight, 24 | }; 25 | } 26 | -------------------------------------------------------------------------------- /packages/utilities/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @dnd-kit-vue/utilities 2 | 3 | ## 0.1.2 4 | 5 | ### Patch Changes 6 | 7 | - ff2942d: fix 0.1.1 ts5.5.1 8 | 9 | ## 0.1.1 10 | 11 | ### Patch Changes 12 | 13 | - 23dd1f2: fix 0.1.0 14 | 15 | ## 0.1.0 16 | 17 | ### Minor Changes 18 | 19 | - f221a5c: vue 3.4 20 | 21 | ## 0.0.5 22 | 23 | ### Patch Changes 24 | 25 | - 30fce38: scrollableAncestorRects 26 | 27 | ## 0.0.4 28 | 29 | ### Patch Changes 30 | 31 | - fdf35bb: fix build 32 | - 7c16d6d: a 33 | - 7ae3e8f: 合并 34 | 35 | ## 0.0.2 36 | 37 | ### Patch Changes 38 | 39 | - aca2662: DragOverlay 40 | 41 | ## 0.0.1 42 | 43 | ### Patch Changes 44 | 45 | - 98942bd: first version 46 | -------------------------------------------------------------------------------- /packages/accessibility/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @dnd-kit-vue/accessibility 2 | 3 | ## 0.1.2 4 | 5 | ### Patch Changes 6 | 7 | - ff2942d: fix 0.1.1 ts5.5.1 8 | 9 | ## 0.1.1 10 | 11 | ### Patch Changes 12 | 13 | - 23dd1f2: fix 0.1.0 14 | 15 | ## 0.1.0 16 | 17 | ### Minor Changes 18 | 19 | - f221a5c: vue 3.4 20 | 21 | ## 0.0.5 22 | 23 | ### Patch Changes 24 | 25 | - 30fce38: scrollableAncestorRects 26 | 27 | ## 0.0.4 28 | 29 | ### Patch Changes 30 | 31 | - fdf35bb: fix build 32 | - 7c16d6d: a 33 | - 7ae3e8f: 合并 34 | 35 | ## 0.0.2 36 | 37 | ### Patch Changes 38 | 39 | - aca2662: DragOverlay 40 | 41 | ## 0.0.1 42 | 43 | ### Patch Changes 44 | 45 | - 98942bd: first version 46 | -------------------------------------------------------------------------------- /packages/core/src/utilities/coordinates/getRelativeTransformOrigin.ts: -------------------------------------------------------------------------------- 1 | import {getEventCoordinates} from '@dnd-kit-vue/utilities'; 2 | import type {ClientRect} from '../../types'; 3 | 4 | export function getRelativeTransformOrigin( 5 | event: MouseEvent | TouchEvent | KeyboardEvent, 6 | rect: ClientRect 7 | ) { 8 | const eventCoordinates = getEventCoordinates(event); 9 | 10 | if (!eventCoordinates) { 11 | return '0 0'; 12 | } 13 | 14 | const transformOrigin = { 15 | x: ((eventCoordinates.x - rect.left) / rect.width) * 100, 16 | y: ((eventCoordinates.y - rect.top) / rect.height) * 100, 17 | }; 18 | 19 | return `${transformOrigin.x}% ${transformOrigin.y}%`; 20 | } 21 | -------------------------------------------------------------------------------- /packages/core/src/modifiers/types.ts: -------------------------------------------------------------------------------- 1 | import type {Transform} from '@dnd-kit-vue/utilities'; 2 | import type {Active, Over} from '../store'; 3 | import type {ClientRect} from '../types'; 4 | 5 | export type Modifier = (args: { 6 | activatorEvent: Event | null; 7 | active: Active | null; 8 | activeNodeRect: ClientRect | null; 9 | draggingNodeRect: ClientRect | null; 10 | containerNodeRect: ClientRect | null; 11 | over: Over | null; 12 | overlayNodeRect: ClientRect | null; 13 | scrollableAncestors: Element[]; 14 | scrollableAncestorRects: ClientRect[]; 15 | transform: Transform; 16 | windowRect: ClientRect | null; 17 | }) => Transform; 18 | 19 | export type Modifiers = Modifier[]; 20 | -------------------------------------------------------------------------------- /packages/core/src/types/events.ts: -------------------------------------------------------------------------------- 1 | import type {Active, Over} from '../store'; 2 | import type {Collision} from '../utilities/algorithms'; 3 | 4 | import type {Translate} from './coordinates'; 5 | 6 | interface DragEvent { 7 | activatorEvent: Event; 8 | active: Active; 9 | collisions: Collision[] | null; 10 | delta: Translate; 11 | over: Over | null; 12 | } 13 | 14 | export interface DragStartEvent extends Pick {} 15 | 16 | export interface DragMoveEvent extends DragEvent {} 17 | 18 | export interface DragOverEvent extends DragMoveEvent {} 19 | 20 | export interface DragEndEvent extends DragEvent {} 21 | 22 | export interface DragCancelEvent extends DragEndEvent {} 23 | -------------------------------------------------------------------------------- /packages/sortable/src/strategies/rectSorting.ts: -------------------------------------------------------------------------------- 1 | import {arrayMove} from '../utilities'; 2 | import type {SortingStrategy} from '../types'; 3 | 4 | export const rectSortingStrategy: SortingStrategy = ({ 5 | rects, 6 | activeIndex, 7 | overIndex, 8 | index, 9 | }) => { 10 | const newRects = arrayMove(rects, overIndex, activeIndex); 11 | 12 | const oldRect = rects[index]; 13 | const newRect = newRects[index]; 14 | 15 | if (!newRect || !oldRect) { 16 | return null; 17 | } 18 | 19 | return { 20 | x: newRect.left - oldRect.left, 21 | y: newRect.top - oldRect.top, 22 | scaleX: newRect.width / oldRect.width, 23 | scaleY: newRect.height / oldRect.height, 24 | }; 25 | }; 26 | -------------------------------------------------------------------------------- /packages/sortable/src/createContext/index.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | import {defineComponent, provide, Ref, ref} from "vue"; 4 | import type {Transform} from '@dnd-kit-vue/utilities'; 5 | 6 | 7 | 8 | 9 | 10 | export function createContext(defaultValue:T){ 11 | 12 | const DndContextProvider = defineComponent<{value:Ref}>((props, {slots}) => { 13 | //console.log(props) 14 | // const DndContext = ref(props.value || {}); 15 | 16 | provide('DndContext', props.value || ref(defaultValue)) 17 | return ()=>slots.default?slots.default(props.value):null 18 | }, { 19 | props: { 20 | value:Object 21 | } 22 | }) 23 | 24 | 25 | return { 26 | Provider: DndContextProvider 27 | } 28 | } 29 | 30 | -------------------------------------------------------------------------------- /packages/core/src/components/DndMonitor/useDndMonitor.ts: -------------------------------------------------------------------------------- 1 | 2 | import {DndMonitorContext} from './context'; 3 | import type {DndMonitorListener} from './types'; 4 | import {inject, ref, watchEffect} from "vue"; 5 | import type {RegisterListener} from "./types"; 6 | 7 | export function useDndMonitor(listener: DndMonitorListener) { 8 | const registerListener = inject('DndMonitorContext', ref(null)) 9 | 10 | watchEffect(() => { 11 | if (!registerListener.value) { 12 | throw new Error( 13 | 'useDndMonitor must be used within a children of ' 14 | ); 15 | } 16 | 17 | const unsubscribe = registerListener.value(listener); 18 | 19 | return unsubscribe; 20 | }); 21 | } 22 | -------------------------------------------------------------------------------- /packages/core/src/CreateContextVueVNode/DndMonitorContextConsumer.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, inject, ref, useSlots } from 'vue' 2 | import type { RegisterListener } from '../components/DndMonitor/types' 3 | 4 | interface ExampleProps { 5 | } 6 | 7 | export const vuePropsType = { 8 | } 9 | 10 | export function useDndMonitorContext() { 11 | return inject('DndMonitorContext', ref(null)) 12 | } 13 | const DndMonitorContextConsumer = defineComponent((props, {}) => { 14 | const slots = useSlots() 15 | const config = useDndMonitorContext() 16 | return ()=>slots.default?slots.default(config):null 17 | }, { 18 | props: vuePropsType 19 | }) 20 | 21 | 22 | export default DndMonitorContextConsumer 23 | 24 | -------------------------------------------------------------------------------- /packages/core/src/utilities/algorithms/types.ts: -------------------------------------------------------------------------------- 1 | import type {Active, Data, DroppableContainer, RectMap} from '../../store'; 2 | import type {Coordinates, ClientRect, UniqueIdentifier} from '../../types'; 3 | 4 | export interface Collision { 5 | id: UniqueIdentifier; 6 | data?: Data; 7 | } 8 | 9 | export interface CollisionDescriptor extends Collision { 10 | data: { 11 | droppableContainer: DroppableContainer; 12 | value: number; 13 | [key: string]: any; 14 | }; 15 | } 16 | 17 | export type CollisionDetection = (args: { 18 | active: Active; 19 | collisionRect: ClientRect; 20 | droppableRects: RectMap; 21 | droppableContainers: DroppableContainer[]; 22 | pointerCoordinates: Coordinates | null; 23 | }) => Collision[]; 24 | -------------------------------------------------------------------------------- /packages/sortable/src/components/context/Provider.tsx: -------------------------------------------------------------------------------- 1 | import {defineComponent, ref, h, Fragment, provide, watch} from 'vue' 2 | import {ContextDescriptor} from "../SortableContext"; 3 | 4 | 5 | export const vuePropsType = { 6 | value: Object 7 | } 8 | const Provider = defineComponent<{value:ContextDescriptor}>((props, {slots}) => { 9 | const ConfigContext = ref(props.value); 10 | 11 | watch(()=>props.value, ()=>{ 12 | ConfigContext.value = props.value 13 | }, { deep: true}) 14 | provide('SortableContext', ConfigContext) 15 | return ()=>slots.default?slots.default(ConfigContext.value):null 16 | }, { 17 | props: vuePropsType, 18 | name: 'SortableProvider' 19 | }) 20 | 21 | 22 | export default Provider 23 | 24 | -------------------------------------------------------------------------------- /packages/core/src/utilities/rect/rectAdjustment.ts: -------------------------------------------------------------------------------- 1 | import type {Coordinates, ClientRect} from '../../types'; 2 | 3 | export function createRectAdjustmentFn(modifier: number) { 4 | return function adjustClientRect( 5 | rect: ClientRect, 6 | ...adjustments: Coordinates[] 7 | ): ClientRect { 8 | return adjustments.reduce( 9 | (acc, adjustment) => ({ 10 | ...acc, 11 | top: acc.top + modifier * adjustment.y, 12 | bottom: acc.bottom + modifier * adjustment.y, 13 | left: acc.left + modifier * adjustment.x, 14 | right: acc.right + modifier * adjustment.x, 15 | }), 16 | {...rect} 17 | ); 18 | }; 19 | } 20 | 21 | export const getAdjustedRect = createRectAdjustmentFn(1); 22 | -------------------------------------------------------------------------------- /packages/core/src/store/constructors.ts: -------------------------------------------------------------------------------- 1 | import type {UniqueIdentifier} from '../types'; 2 | import type {DroppableContainer} from './types'; 3 | 4 | type Identifier = UniqueIdentifier | null | undefined; 5 | 6 | export class DroppableContainersMap extends Map< 7 | UniqueIdentifier, 8 | DroppableContainer 9 | > { 10 | get(id: Identifier) { 11 | return id != null ? super.get(id) ?? undefined : undefined; 12 | } 13 | 14 | toArray(): DroppableContainer[] { 15 | return Array.from(this.values()); 16 | } 17 | 18 | getEnabled(): DroppableContainer[] { 19 | return this.toArray().filter(({disabled}) => !disabled); 20 | } 21 | 22 | getNodeFor(id: Identifier) { 23 | return this.get(id)?.node.current ?? undefined; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/utilities/scroll/getScrollableElement.ts: -------------------------------------------------------------------------------- 1 | import { 2 | canUseDOM, 3 | isHTMLElement, 4 | isDocument, 5 | getOwnerDocument, 6 | isNode, 7 | isWindow, 8 | } from '@dnd-kit-vue/utilities'; 9 | 10 | export function getScrollableElement(element: EventTarget | null) { 11 | if (!canUseDOM || !element) { 12 | return null; 13 | } 14 | 15 | if (isWindow(element)) { 16 | return element; 17 | } 18 | 19 | if (!isNode(element)) { 20 | return null; 21 | } 22 | 23 | if ( 24 | isDocument(element) || 25 | element === getOwnerDocument(element).scrollingElement 26 | ) { 27 | return window; 28 | } 29 | 30 | if (isHTMLElement(element)) { 31 | return element; 32 | } 33 | 34 | return null; 35 | } 36 | -------------------------------------------------------------------------------- /packages/core/src/sensors/utilities/hasExceededDistance.ts: -------------------------------------------------------------------------------- 1 | import type {Coordinates, DistanceMeasurement} from '../../types'; 2 | 3 | export function hasExceededDistance( 4 | delta: Coordinates, 5 | measurement: DistanceMeasurement 6 | ): boolean { 7 | const dx = Math.abs(delta.x); 8 | const dy = Math.abs(delta.y); 9 | 10 | if (typeof measurement === 'number') { 11 | return Math.sqrt(dx ** 2 + dy ** 2) > measurement; 12 | } 13 | 14 | if ('x' in measurement && 'y' in measurement) { 15 | return dx > measurement.x && dy > measurement.y; 16 | } 17 | 18 | if ('x' in measurement) { 19 | return dx > measurement.x; 20 | } 21 | 22 | if ('y' in measurement) { 23 | return dy > measurement.y; 24 | } 25 | 26 | return false; 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/src/CreateContextVueVNode/InternalContextConsumer.tsx: -------------------------------------------------------------------------------- 1 | import {defineComponent, ref, h, Fragment, useSlots, inject} from 'vue' 2 | import {defaultInternalContext, type InternalContextDescriptor} from "../store"; 3 | 4 | interface ExampleProps { 5 | } 6 | 7 | export const vuePropsType = { 8 | } 9 | export function useInternalContext() { 10 | return inject('InternalContext', ref(defaultInternalContext)) 11 | } 12 | const InternalContextConsumer = defineComponent((props, {}) => { 13 | const slots = useSlots() 14 | const config = useInternalContext() 15 | return ()=>slots.default?slots.default(config):null 16 | }, { 17 | props: vuePropsType, 18 | }) 19 | 20 | 21 | export default InternalContextConsumer 22 | 23 | -------------------------------------------------------------------------------- /packages/core/src/sensors/keyboard/types.ts: -------------------------------------------------------------------------------- 1 | import type {Coordinates, UniqueIdentifier} from '../../types'; 2 | import type {SensorContext} from '../types'; 3 | 4 | export enum KeyboardCode { 5 | Space = 'Space', 6 | Down = 'ArrowDown', 7 | Right = 'ArrowRight', 8 | Left = 'ArrowLeft', 9 | Up = 'ArrowUp', 10 | Esc = 'Escape', 11 | Enter = 'Enter', 12 | } 13 | 14 | export type KeyboardCodes = { 15 | start: KeyboardEvent['code'][]; 16 | cancel: KeyboardEvent['code'][]; 17 | end: KeyboardEvent['code'][]; 18 | }; 19 | 20 | export type KeyboardCoordinateGetter = ( 21 | event: KeyboardEvent, 22 | args: { 23 | active: UniqueIdentifier; 24 | currentCoordinates: Coordinates; 25 | context: SensorContext; 26 | } 27 | ) => Coordinates | void; 28 | -------------------------------------------------------------------------------- /packages/sortable/test/demo1/styles.module.css: -------------------------------------------------------------------------------- 1 | ._1xrmbrmSFNKCAqjWPMGd2t { 2 | position: relative; 3 | display: flex; 4 | flex-grow: 1; 5 | align-items: center; 6 | padding: 18px 20px; 7 | background-color: #fff; 8 | box-shadow: 0 0 0 calc(1px / var(--scale-x, 1)) rgba(63, 63, 68, 0.05), 0 1px calc(3px / var(--scale-x, 1)) 0 rgba(34, 33, 81, 0.15); 9 | outline: none; 10 | border-radius: calc(4px / var(--scale-x, 1)); 11 | box-sizing: border-box; 12 | list-style: none; 13 | transform-origin: 50% 50%; 14 | -webkit-tap-highlight-color: transparent; 15 | color: #333; 16 | font-weight: 400; 17 | font-size: 1rem; 18 | white-space: nowrap; 19 | transform: scale(var(--scale, 1)); 20 | transition: box-shadow 200ms cubic-bezier(0.18, 0.67, 0.6, 1.22); 21 | } 22 | -------------------------------------------------------------------------------- /packages/core/src/CreateContextVueVNode/DndContextProvider.tsx: -------------------------------------------------------------------------------- 1 | 2 | import {defineComponent, getCurrentInstance, provide, ref, watch} from "vue"; 3 | import type {Transform} from '@dnd-kit-vue/utilities'; 4 | 5 | 6 | 7 | const DndContextProvider = defineComponent<{value:Transform}>((props, {slots}) => { 8 | //console.log(props) 9 | // const DndContext = ref(props.value || {}); 10 | 11 | const context = ref(props.value); 12 | 13 | watch(()=>props.value, ()=>{ 14 | context.value = props.value 15 | }, { deep: true}) 16 | provide('DndContext', context) 17 | return ()=>slots.default?slots.default(context.value):null 18 | }, { 19 | props: { 20 | value:Object 21 | } 22 | }) 23 | 24 | export default DndContextProvider; 25 | -------------------------------------------------------------------------------- /packages/core/src/CreateContextVueVNode/PublicContextConsumer.tsx: -------------------------------------------------------------------------------- 1 | import {defineComponent, ref, h, Fragment, useSlots, inject} from 'vue' 2 | import type {PublicContextDescriptor} from "../store"; 3 | import {defaultPublicContext} from "../store/context"; 4 | 5 | interface ExampleProps { 6 | } 7 | 8 | export const vuePropsType = { 9 | } 10 | 11 | export function usePublicContext() { 12 | return inject('PublicContext', ref(defaultPublicContext)) 13 | } 14 | const PublicContextConsumer = defineComponent((props, {}) => { 15 | const slots = useSlots() 16 | const config = usePublicContext() 17 | return ()=>slots.default?slots.default(config):null 18 | }, { 19 | props: vuePropsType, 20 | }) 21 | 22 | 23 | export default PublicContextConsumer 24 | 25 | -------------------------------------------------------------------------------- /packages/core/src/sensors/utilities/Listeners.ts: -------------------------------------------------------------------------------- 1 | export class Listeners { 2 | private listeners: [ 3 | string, 4 | EventListenerOrEventListenerObject, 5 | AddEventListenerOptions | boolean | undefined 6 | ][] = []; 7 | 8 | constructor(private target: EventTarget | null) {} 9 | 10 | public add( 11 | eventName: string, 12 | handler: (event: T) => void, 13 | options?: AddEventListenerOptions | boolean 14 | ) { 15 | this.target?.addEventListener(eventName, handler as EventListener, options); 16 | this.listeners.push([eventName, handler as EventListener, options]); 17 | } 18 | 19 | public removeAll = () => { 20 | this.listeners.forEach((listener) => 21 | this.target?.removeEventListener(...listener) 22 | ); 23 | }; 24 | } 25 | -------------------------------------------------------------------------------- /packages/core/src/utilities/scroll/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | getFirstScrollableAncestor, 3 | getScrollableAncestors, 4 | } from './getScrollableAncestors'; 5 | export {getScrollableElement} from './getScrollableElement'; 6 | export {getScrollCoordinates} from './getScrollCoordinates'; 7 | export {getScrollDirectionAndSpeed} from './getScrollDirectionAndSpeed'; 8 | export {getScrollElementRect} from './getScrollElementRect'; 9 | export { 10 | getScrollOffsets, 11 | getScrollXOffset, 12 | getScrollYOffset, 13 | } from './getScrollOffsets'; 14 | export {getScrollPosition} from './getScrollPosition'; 15 | export {isDocumentScrollingElement} from './documentScrollingElement'; 16 | export {isScrollable} from './isScrollable'; 17 | export {scrollIntoViewIfNeeded} from './scrollIntoViewIfNeeded'; 18 | -------------------------------------------------------------------------------- /packages/sortable/src/strategies/rectSwapping.ts: -------------------------------------------------------------------------------- 1 | import type {SortingStrategy} from '../types'; 2 | 3 | export const rectSwappingStrategy: SortingStrategy = ({ 4 | activeIndex, 5 | index, 6 | rects, 7 | overIndex, 8 | }) => { 9 | let oldRect; 10 | let newRect; 11 | 12 | if (index === activeIndex) { 13 | oldRect = rects[index]; 14 | newRect = rects[overIndex]; 15 | } 16 | 17 | if (index === overIndex) { 18 | oldRect = rects[index]; 19 | newRect = rects[activeIndex]; 20 | } 21 | 22 | if (!newRect || !oldRect) { 23 | return null; 24 | } 25 | 26 | return { 27 | x: newRect.left - oldRect.left, 28 | y: newRect.top - oldRect.top, 29 | scaleX: newRect.width / oldRect.width, 30 | scaleY: newRect.height / oldRect.height, 31 | }; 32 | }; 33 | -------------------------------------------------------------------------------- /packages/core/src/sensors/utilities/getEventListenerTarget.ts: -------------------------------------------------------------------------------- 1 | import {getOwnerDocument, getWindow} from '@dnd-kit-vue/utilities'; 2 | 3 | export function getEventListenerTarget( 4 | target: EventTarget | null 5 | ): EventTarget | Document { 6 | // If the `event.target` element is removed from the document events will still be targeted 7 | // at it, and hence won't always bubble up to the window or document anymore. 8 | // If there is any risk of an element being removed while it is being dragged, 9 | // the best practice is to attach the event listeners directly to the target. 10 | // https://developer.mozilla.org/en-US/docs/Web/API/EventTarget 11 | 12 | const {EventTarget} = getWindow(target); 13 | 14 | return target instanceof EventTarget ? target : getOwnerDocument(target); 15 | } 16 | -------------------------------------------------------------------------------- /packages/core/src/utilities/scroll/getScrollCoordinates.ts: -------------------------------------------------------------------------------- 1 | import {isWindow} from '@dnd-kit-vue/utilities'; 2 | 3 | import type {Coordinates} from '../../types'; 4 | 5 | export function getScrollXCoordinate(element: Element | typeof window): number { 6 | if (isWindow(element)) { 7 | return element.scrollX; 8 | } 9 | 10 | return element.scrollLeft; 11 | } 12 | 13 | export function getScrollYCoordinate(element: Element | typeof window): number { 14 | if (isWindow(element)) { 15 | return element.scrollY; 16 | } 17 | 18 | return element.scrollTop; 19 | } 20 | 21 | export function getScrollCoordinates( 22 | element: Element | typeof window 23 | ): Coordinates { 24 | return { 25 | x: getScrollXCoordinate(element), 26 | y: getScrollYCoordinate(element), 27 | }; 28 | } 29 | -------------------------------------------------------------------------------- /packages/core/src/CreateContextVueVNode/DndContextConsumer.tsx: -------------------------------------------------------------------------------- 1 | import {defineComponent, ref, h, Fragment, useSlots, inject} from 'vue' 2 | import type {Transform} from '@dnd-kit-vue/utilities'; 3 | import {defaultCoordinates} from "../utilities"; 4 | 5 | interface ExampleProps { 6 | } 7 | 8 | export const vuePropsType = { 9 | } 10 | 11 | export function useDndContext() { 12 | return inject('DndContext', ref({ 13 | ...defaultCoordinates, 14 | scaleX: 1, 15 | scaleY: 1, 16 | })) 17 | } 18 | const DndContextConsumer = defineComponent((props, {}) => { 19 | const slots = useSlots() 20 | const config = useDndContext() 21 | return ()=>slots.default?slots.default(config):null 22 | }, { 23 | props: vuePropsType, 24 | }) 25 | 26 | 27 | export default DndContextConsumer 28 | 29 | -------------------------------------------------------------------------------- /packages/core/src/utilities/transform/parseTransform.ts: -------------------------------------------------------------------------------- 1 | import type {Transform} from '@dnd-kit-vue/utilities'; 2 | 3 | export function parseTransform(transform: string): Transform | null { 4 | if (transform.startsWith('matrix3d(')) { 5 | const transformArray = transform.slice(9, -1).split(/, /); 6 | 7 | return { 8 | x: +transformArray[12], 9 | y: +transformArray[13], 10 | scaleX: +transformArray[0], 11 | scaleY: +transformArray[5], 12 | }; 13 | } else if (transform.startsWith('matrix(')) { 14 | const transformArray = transform.slice(7, -1).split(/, /); 15 | 16 | return { 17 | x: +transformArray[4], 18 | y: +transformArray[5], 19 | scaleX: +transformArray[0], 20 | scaleY: +transformArray[3], 21 | }; 22 | } 23 | 24 | return null; 25 | } 26 | -------------------------------------------------------------------------------- /packages/core/src/hooks/utilities/useInitialValue.ts: -------------------------------------------------------------------------------- 1 | import {useLazyMemo} from '@dnd-kit-vue/utilities'; 2 | import { computed, type ComputedRef, Ref, ref } from 'vue' 3 | 4 | type AnyFunction = (...args: any) => any; 5 | 6 | export function useInitialValue< 7 | T extends Ref, 8 | U extends AnyFunction | undefined = undefined 9 | >( 10 | value: T | null, 11 | computeFn?: U 12 | ): ComputedRef { 13 | const previousValue = ref() 14 | return computed(()=>{ 15 | function getData() { 16 | if (!value?.value) { 17 | return null; 18 | } 19 | 20 | if (previousValue.value) { 21 | return previousValue.value; 22 | } 23 | 24 | 25 | return typeof computeFn === 'function' ? computeFn(value.value) : value.value; 26 | } 27 | return getData() 28 | }) 29 | } 30 | -------------------------------------------------------------------------------- /packages/sortable/src/types/type-guard.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | Active, 3 | Data, 4 | DroppableContainer, 5 | DraggableNode, 6 | Over, 7 | } from '@dnd-kit-vue/core'; 8 | 9 | import type {SortableData} from './data'; 10 | 11 | export function hasSortableData< 12 | T extends Active | Over | DraggableNode | DroppableContainer 13 | >( 14 | entry: T | null | undefined 15 | ): entry is T & {data: {current: Data}} { 16 | if (!entry) { 17 | return false; 18 | } 19 | 20 | const data = entry.data.current; 21 | 22 | if ( 23 | data && 24 | 'sortable' in data && 25 | typeof data.sortable === 'object' && 26 | 'containerId' in data.sortable && 27 | 'items' in data.sortable && 28 | 'index' in data.sortable 29 | ) { 30 | return true; 31 | } 32 | 33 | return false; 34 | } 35 | -------------------------------------------------------------------------------- /packages/core/src/components/DragOverlay/components/NullifiedContextProvider/NullifiedContextProvider.tsx: -------------------------------------------------------------------------------- 1 | 2 | import type {Transform} from '@dnd-kit-vue/utilities'; 3 | 4 | import {InternalContext, defaultInternalContext} from '../../../../store'; 5 | import {ActiveDraggableContext} from '../../../DndContext'; 6 | import {useSlots, h} from "vue"; 7 | 8 | 9 | const defaultTransform: Transform = { 10 | x: 0, 11 | y: 0, 12 | scaleX: 1, 13 | scaleY: 1, 14 | }; 15 | 16 | export function NullifiedContextProvider() { 17 | const slots = useSlots() 18 | return ( 19 | 20 | 21 | {slots.default?.()} 22 | 23 | 24 | ); 25 | } 26 | -------------------------------------------------------------------------------- /packages/accessibility/src/components/LiveRegion/LiveRegion.tsx: -------------------------------------------------------------------------------- 1 | import {type CSSProperties, h} from "vue"; 2 | 3 | export interface Props { 4 | id: string; 5 | announcement: string; 6 | } 7 | 8 | // Hide element visually but keep it readable by screen readers 9 | const visuallyHidden: CSSProperties = { 10 | position: 'fixed', 11 | width: 1, 12 | height: 1, 13 | margin: -1, 14 | border: 0, 15 | padding: 0, 16 | overflow: 'hidden', 17 | clip: 'rect(0 0 0 0)', 18 | clipPath: 'inset(100%)', 19 | whiteSpace: 'nowrap', 20 | }; 21 | 22 | export function LiveRegion({id, announcement}: Props) { 23 | return ( 24 |
31 | {announcement} 32 |
33 | ); 34 | } 35 | -------------------------------------------------------------------------------- /packages/core/src/CreateContextVueVNode/PublicContextProvider.tsx: -------------------------------------------------------------------------------- 1 | 2 | import {defineComponent, provide, ref, watch} from "vue"; 3 | import type {PublicContextDescriptor} from "../store"; 4 | 5 | 6 | 7 | const PublicContextProvider = defineComponent<{value:PublicContextDescriptor}>((props, {slots}) => { 8 | //console.log(props) 9 | // const DndContext = ref(props.value || {}); 10 | 11 | const context = ref(props.value); 12 | 13 | watch(()=>props.value, ()=>{ 14 | // @ts-ignore 15 | context.value = props.value 16 | }, { deep: true}) 17 | provide('PublicContext', context) 18 | return ()=>slots.default?slots.default(context.value):null 19 | }, { 20 | props: { 21 | value:Object 22 | } 23 | }) 24 | 25 | export default PublicContextProvider; 26 | -------------------------------------------------------------------------------- /packages/core/src/CreateContextVueVNode/DndMonitorContextProvider.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { defineComponent, type PropType, provide, ref, watch } from 'vue' 3 | import type {RegisterListener} from "../components/DndMonitor/types"; 4 | 5 | 6 | 7 | const DndMonitorContextProvider = defineComponent<{value:RegisterListener}>((props, {slots}) => { 8 | //console.log(props) 9 | const context = ref(props.value); 10 | 11 | 12 | watch(()=>props.value, ()=>{ 13 | context.value = props.value 14 | }, { deep: true}) 15 | provide('DndMonitorContext', context) 16 | return ()=>slots.default?slots.default(context.value):null 17 | 18 | }, { 19 | props: { 20 | value: Function as PropType 21 | }, 22 | name: 'DndMonitorContextProvider' 23 | }) 24 | 25 | export default DndMonitorContextProvider; 26 | -------------------------------------------------------------------------------- /packages/modifiers/src/snapCenterToCursor.ts: -------------------------------------------------------------------------------- 1 | import type {Modifier} from '@dnd-kit-vue/core'; 2 | import {getEventCoordinates} from '@dnd-kit-vue/utilities'; 3 | 4 | export const snapCenterToCursor: Modifier = ({ 5 | activatorEvent, 6 | draggingNodeRect, 7 | transform, 8 | }) => { 9 | if (draggingNodeRect && activatorEvent) { 10 | const activatorCoordinates = getEventCoordinates(activatorEvent); 11 | 12 | if (!activatorCoordinates) { 13 | return transform; 14 | } 15 | 16 | const offsetX = activatorCoordinates.x - draggingNodeRect.left; 17 | const offsetY = activatorCoordinates.y - draggingNodeRect.top; 18 | 19 | return { 20 | ...transform, 21 | x: transform.x + offsetX - draggingNodeRect.width / 2, 22 | y: transform.y + offsetY - draggingNodeRect.height / 2, 23 | }; 24 | } 25 | 26 | return transform; 27 | }; 28 | -------------------------------------------------------------------------------- /packages/core/src/components/DndMonitor/types.ts: -------------------------------------------------------------------------------- 1 | import type { 2 | DragStartEvent, 3 | DragCancelEvent, 4 | DragEndEvent, 5 | DragMoveEvent, 6 | DragOverEvent, 7 | } from '../../types'; 8 | 9 | export interface DndMonitorListener { 10 | onDragStart?(event: DragStartEvent): void; 11 | onDragMove?(event: DragMoveEvent): void; 12 | onDragOver?(event: DragOverEvent): void; 13 | onDragEnd?(event: DragEndEvent): void; 14 | onDragCancel?(event: DragCancelEvent): void; 15 | } 16 | 17 | export interface DndMonitorEvent { 18 | type: keyof DndMonitorListener; 19 | event: 20 | | DragStartEvent 21 | | DragMoveEvent 22 | | DragOverEvent 23 | | DragEndEvent 24 | | DragCancelEvent; 25 | } 26 | 27 | export type UnregisterListener = () => void; 28 | 29 | export type RegisterListener = ( 30 | listener: DndMonitorListener 31 | ) => UnregisterListener; 32 | -------------------------------------------------------------------------------- /packages/sortable/src/index.ts: -------------------------------------------------------------------------------- 1 | import type {Arguments} from "./hooks/useSortable"; 2 | 3 | export {SortableContext} from './components'; 4 | export type {SortableContextProps} from './components'; 5 | export { 6 | useSortable, 7 | defaultAnimateLayoutChanges, 8 | defaultNewIndexGetter, 9 | } from './hooks'; 10 | export type { 11 | UseSortableArguments, 12 | AnimateLayoutChanges, 13 | NewIndexGetter, 14 | } from './hooks'; 15 | export { 16 | horizontalListSortingStrategy, 17 | rectSortingStrategy, 18 | rectSwappingStrategy, 19 | verticalListSortingStrategy, 20 | } from './strategies'; 21 | export {sortableKeyboardCoordinates} from './sensors'; 22 | export {arrayMove, arraySwap} from './utilities'; 23 | export {hasSortableData} from './types'; 24 | export type {SortableData, SortingStrategy} from './types'; 25 | 26 | 27 | export type { 28 | Arguments 29 | } 30 | -------------------------------------------------------------------------------- /packages/core/src/CreateContextVueVNode/InternalContextProvider.tsx: -------------------------------------------------------------------------------- 1 | 2 | import {defineComponent, provide, ref, watch} from "vue"; 3 | import type {InternalContextDescriptor} from "../store"; 4 | 5 | 6 | 7 | const InternalContextProvider = defineComponent<{value:InternalContextDescriptor}>((props, {slots}) => { 8 | //console.log(props) 9 | // const DndContext = ref(props.value || {}); 10 | 11 | const context = ref(props.value); 12 | 13 | watch(()=>props.value, ()=>{ 14 | context.value = props.value 15 | }, { deep: true}) 16 | provide('InternalContext', context) 17 | return ()=>slots.default?slots.default(context.value):null 18 | 19 | }, { 20 | props: { 21 | value:Object 22 | }, 23 | name: 'InternalContextProvider' 24 | }) 25 | 26 | export default InternalContextProvider; 27 | -------------------------------------------------------------------------------- /packages/core/src/components/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | defaultAnnouncements, 3 | defaultScreenReaderInstructions, 4 | } from './Accessibility'; 5 | export type {Announcements, ScreenReaderInstructions} from './Accessibility'; 6 | export {DndContext} from './DndContext'; 7 | export type { 8 | CancelDrop, 9 | DndContextProps, 10 | DraggableMeasuring, 11 | MeasuringConfiguration, 12 | } from './DndContext'; 13 | export {useDndMonitor} from './DndMonitor'; 14 | export type {DndMonitorListener} from './DndMonitor'; 15 | export { 16 | DragOverlay, 17 | defaultDropAnimation, 18 | defaultDropAnimationSideEffects, 19 | } from './DragOverlay'; 20 | export type { 21 | DropAnimation, 22 | DropAnimationFunction, 23 | DropAnimationFunctionArguments, 24 | DropAnimationKeyframeResolver, 25 | DropAnimationSideEffects, 26 | Props as DragOverlayProps, 27 | } from './DragOverlay'; 28 | -------------------------------------------------------------------------------- /packages/utilities/src/adjustment.ts: -------------------------------------------------------------------------------- 1 | function createAdjustmentFn(modifier: number) { 2 | return , U extends string>( 3 | object: T, 4 | ...adjustments: Partial[] 5 | ): T => { 6 | const value = adjustments.reduce( 7 | (accumulator, adjustment) => { 8 | const entries = Object.entries(adjustment) as [U, number][]; 9 | 10 | for (const [key, valueAdjustment] of entries) { 11 | const value = accumulator[key]; 12 | 13 | if (value != null) { 14 | accumulator[key] = (value + modifier * valueAdjustment) as T[U]; 15 | } 16 | } 17 | 18 | return accumulator; 19 | }, 20 | { 21 | ...object, 22 | } 23 | ); 24 | return value 25 | }; 26 | } 27 | 28 | export const add = createAdjustmentFn(1); 29 | export const subtract = createAdjustmentFn(-1); 30 | -------------------------------------------------------------------------------- /packages/core/src/utilities/scroll/scrollIntoViewIfNeeded.ts: -------------------------------------------------------------------------------- 1 | import type {ClientRect} from '../../types'; 2 | import {getClientRect} from '../rect/getRect'; 3 | import {getFirstScrollableAncestor} from './getScrollableAncestors'; 4 | 5 | export function scrollIntoViewIfNeeded( 6 | element: HTMLElement | null | undefined, 7 | measure: (node: HTMLElement) => ClientRect = getClientRect 8 | ) { 9 | if (!element) { 10 | return; 11 | } 12 | 13 | const {top, left, bottom, right} = measure(element); 14 | const firstScrollableAncestor = getFirstScrollableAncestor(element); 15 | 16 | if (!firstScrollableAncestor) { 17 | return; 18 | } 19 | 20 | if ( 21 | bottom <= 0 || 22 | right <= 0 || 23 | top >= window.innerHeight || 24 | left >= window.innerWidth 25 | ) { 26 | element.scrollIntoView({ 27 | block: 'center', 28 | inline: 'center', 29 | }); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /packages/core/src/components/DndContext/hooks/useMeasuringConfiguration.ts: -------------------------------------------------------------------------------- 1 | 2 | import type {DeepRequired} from '@dnd-kit-vue/utilities'; 3 | 4 | import {defaultMeasuringConfiguration} from '../defaults'; 5 | import type {MeasuringConfiguration} from '../types'; 6 | import {computed, type ComputedRef} from "vue"; 7 | 8 | export function useMeasuringConfiguration( 9 | config: MeasuringConfiguration | undefined 10 | ): ComputedRef> { 11 | return computed( 12 | () => ({ 13 | draggable: { 14 | ...defaultMeasuringConfiguration.draggable, 15 | ...config?.draggable, 16 | }, 17 | droppable: { 18 | ...defaultMeasuringConfiguration.droppable, 19 | ...config?.droppable, 20 | }, 21 | dragOverlay: { 22 | ...defaultMeasuringConfiguration.dragOverlay, 23 | ...config?.dragOverlay, 24 | }, 25 | })); 26 | } 27 | -------------------------------------------------------------------------------- /packages/core/vite.config.ts: -------------------------------------------------------------------------------- 1 | import {defineConfig} from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | import {resolve} from 'path' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [vue(), vueJsx()], 9 | build: { 10 | lib: { 11 | // Could also be a dictionary or array of multiple entry points 12 | entry: resolve(__dirname, 'src/index.ts'), 13 | formats: ['es'], 14 | // the proper extensions will be added 15 | fileName: 'index' 16 | }, 17 | rollupOptions: { 18 | // make sure to externalize deps that shouldn't be bundled 19 | // into your library 20 | external: ['vue'], 21 | output: { 22 | // Provide global variables to use in the UMD build 23 | // for externalized deps 24 | globals: { 25 | vue: 'Vue' 26 | } 27 | } 28 | } 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /packages/modifiers/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | import {resolve} from 'path' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [vue(),vueJsx()], 9 | build: { 10 | lib: { 11 | // Could also be a dictionary or array of multiple entry points 12 | entry: resolve(__dirname, 'src/index.ts'), 13 | formats: ['es'], 14 | // the proper extensions will be added 15 | fileName: 'index' 16 | }, 17 | rollupOptions: { 18 | // make sure to externalize deps that shouldn't be bundled 19 | // into your library 20 | external: ['vue'], 21 | output: { 22 | // Provide global variables to use in the UMD build 23 | // for externalized deps 24 | globals: { 25 | vue: 'Vue' 26 | } 27 | } 28 | } 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /packages/sortable/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | import {resolve} from 'path' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [vue(),vueJsx()], 9 | build: { 10 | lib: { 11 | // Could also be a dictionary or array of multiple entry points 12 | entry: resolve(__dirname, 'src/index.ts'), 13 | formats: ['es'], 14 | // the proper extensions will be added 15 | fileName: 'index' 16 | }, 17 | rollupOptions: { 18 | // make sure to externalize deps that shouldn't be bundled 19 | // into your library 20 | external: ['vue'], 21 | output: { 22 | // Provide global variables to use in the UMD build 23 | // for externalized deps 24 | globals: { 25 | vue: 'Vue' 26 | } 27 | } 28 | } 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /packages/utilities/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | import {resolve} from 'path' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [vue(),vueJsx()], 9 | build: { 10 | lib: { 11 | // Could also be a dictionary or array of multiple entry points 12 | entry: resolve(__dirname, 'src/index.ts'), 13 | formats: ['es'], 14 | // the proper extensions will be added 15 | fileName: 'index' 16 | }, 17 | rollupOptions: { 18 | // make sure to externalize deps that shouldn't be bundled 19 | // into your library 20 | external: ['vue'], 21 | output: { 22 | // Provide global variables to use in the UMD build 23 | // for externalized deps 24 | globals: { 25 | vue: 'Vue' 26 | } 27 | } 28 | } 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /packages/accessibility/vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import vue from '@vitejs/plugin-vue' 3 | import vueJsx from '@vitejs/plugin-vue-jsx' 4 | import {resolve} from 'path' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [vue(),vueJsx()], 9 | build: { 10 | lib: { 11 | // Could also be a dictionary or array of multiple entry points 12 | entry: resolve(__dirname, 'src/index.ts'), 13 | formats: ['es'], 14 | // the proper extensions will be added 15 | fileName: 'index' 16 | }, 17 | rollupOptions: { 18 | // make sure to externalize deps that shouldn't be bundled 19 | // into your library 20 | external: ['vue'], 21 | output: { 22 | // Provide global variables to use in the UMD build 23 | // for externalized deps 24 | globals: { 25 | vue: 'Vue' 26 | } 27 | } 28 | } 29 | } 30 | }) 31 | -------------------------------------------------------------------------------- /packages/sortable/src/hooks/types.ts: -------------------------------------------------------------------------------- 1 | import type {Active, UniqueIdentifier} from '@dnd-kit-vue/core'; 2 | import type {Transition} from '@dnd-kit-vue/utilities'; 3 | 4 | export type SortableTransition = Pick; 5 | 6 | export type AnimateLayoutChanges = (args: { 7 | active: Active | null; 8 | containerId: UniqueIdentifier; 9 | isDragging: boolean; 10 | isSorting: boolean; 11 | id: UniqueIdentifier; 12 | index: number; 13 | items: UniqueIdentifier[]; 14 | previousItems: UniqueIdentifier[]; 15 | previousContainerId: UniqueIdentifier; 16 | newIndex: number; 17 | transition: SortableTransition | null; 18 | wasDragging: boolean; 19 | }) => boolean; 20 | 21 | export interface NewIndexGetterArguments { 22 | id: UniqueIdentifier; 23 | items: UniqueIdentifier[]; 24 | activeIndex: number; 25 | overIndex: number; 26 | } 27 | 28 | export type NewIndexGetter = (args: NewIndexGetterArguments) => number; 29 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["./**/*.spec.*","./**/_stories/.*","./**/_stories/.*","./**/__stories__/.*", 3 | "./main.ts", "../script/*", 4 | "./Comp1", 5 | "./App.tsx", 6 | "./**/__test__", 7 | "./test/*", 8 | "./*.ts" 9 | ], 10 | "compilerOptions": { 11 | "rootDir": "./src", 12 | "outDir": "./dist/types", 13 | "target": "esnext", 14 | "useDefineForClassFields": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "strict": true, 18 | "jsx": "preserve", 19 | "jsxImportSource": "vue", 20 | "jsxFactory": "h", 21 | "jsxFragmentFactory": "Fragment", 22 | "strictNullChecks": false, 23 | "sourceMap": true, 24 | "declaration": true, 25 | "emitDeclarationOnly": true, 26 | "resolveJsonModule": true, 27 | "esModuleInterop": true, 28 | "types": ["vite/client", "node"], 29 | "lib": ["esnext", "dom"], 30 | "noImplicitAny": false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/utilities/src/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | useCombinedRefs, 3 | useEvent, 4 | useIsomorphicLayoutEffect, 5 | useInterval, 6 | useLatestValue, 7 | useLazyMemo, 8 | useNodeRef, 9 | usePrevious, 10 | useUniqueId, 11 | } from './hooks'; 12 | 13 | export {add, subtract} from './adjustment'; 14 | export type {Coordinates} from './coordinates'; 15 | export {getEventCoordinates} from './coordinates'; 16 | export {CSS} from './css'; 17 | export type {Transform, Transition} from './css'; 18 | export { 19 | hasViewportRelativeCoordinates, 20 | isKeyboardEvent, 21 | isTouchEvent, 22 | } from './event'; 23 | export {canUseDOM, getOwnerDocument, getWindow} from './execution-context'; 24 | export {findFirstFocusableNode} from './focus'; 25 | export { 26 | isDocument, 27 | isHTMLElement, 28 | isNode, 29 | isSVGElement, 30 | isWindow, 31 | } from './type-guards'; 32 | export type {Arguments, DeepRequired, FirstArgument, Without} from './types'; 33 | -------------------------------------------------------------------------------- /packages/core/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["./**/*.spec.*","./**/_stories/.*","./**/_stories/.*","./**/__stories__/.*", 3 | "./main.ts", "../script/*", 4 | "./Comp1", 5 | "./App.tsx", 6 | "./**/__test__", 7 | "./test/*", 8 | "./*.ts" 9 | ], 10 | "compilerOptions": { 11 | "rootDir": "./src", 12 | "outDir": "./dist/types", 13 | "target": "esnext", 14 | "useDefineForClassFields": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "strict": true, 18 | "jsx": "preserve", 19 | "jsxImportSource": "vue", 20 | "jsxFactory": "h", 21 | "jsxFragmentFactory": "Fragment", 22 | "strictNullChecks": false, 23 | "sourceMap": true, 24 | "declaration": true, 25 | "emitDeclarationOnly": true, 26 | "resolveJsonModule": true, 27 | "esModuleInterop": true, 28 | "types": ["vite/client", "node"], 29 | "lib": ["esnext", "dom"], 30 | "noImplicitAny": false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/modifiers/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["./**/*.spec.*","./**/_stories/.*","./**/_stories/.*","./**/__stories__/.*", 3 | "./main.ts", "../script/*", 4 | "./Comp1", 5 | "./App.tsx", 6 | "./**/__test__", 7 | "./test/*", 8 | "./*.ts" 9 | ], 10 | "compilerOptions": { 11 | "rootDir": "./src", 12 | "outDir": "./dist/types", 13 | "target": "esnext", 14 | "useDefineForClassFields": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "strict": true, 18 | "jsx": "preserve", 19 | "jsxImportSource": "vue", 20 | "jsxFactory": "h", 21 | "jsxFragmentFactory": "Fragment", 22 | "strictNullChecks": false, 23 | "sourceMap": true, 24 | "declaration": true, 25 | "emitDeclarationOnly": true, 26 | "resolveJsonModule": true, 27 | "esModuleInterop": true, 28 | "types": ["vite/client", "node"], 29 | "lib": ["esnext", "dom"], 30 | "noImplicitAny": false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/sortable/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["./**/*.spec.*","./**/_stories/.*","./**/_stories/.*","./**/__stories__/.*", 3 | "./main.ts", "../script/*", 4 | "./Comp1", 5 | "./App.tsx", 6 | "./**/__test__", 7 | "./test/*", 8 | "./*.ts" 9 | ], 10 | "compilerOptions": { 11 | "rootDir": "./src", 12 | "outDir": "./dist/types", 13 | "target": "esnext", 14 | "useDefineForClassFields": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "strict": true, 18 | "jsx": "preserve", 19 | "jsxImportSource": "vue", 20 | "jsxFactory": "h", 21 | "jsxFragmentFactory": "Fragment", 22 | "strictNullChecks": false, 23 | "sourceMap": true, 24 | "declaration": true, 25 | "emitDeclarationOnly": true, 26 | "resolveJsonModule": true, 27 | "esModuleInterop": true, 28 | "types": ["vite/client", "node"], 29 | "lib": ["esnext", "dom"], 30 | "noImplicitAny": false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /packages/utilities/src/coordinates/getEventCoordinates.ts: -------------------------------------------------------------------------------- 1 | import type {Coordinates} from './types'; 2 | import {isTouchEvent, hasViewportRelativeCoordinates} from '../event'; 3 | 4 | /** 5 | * Returns the normalized x and y coordinates for mouse and touch events. 6 | */ 7 | export function getEventCoordinates(event: Event): Coordinates | null { 8 | if (isTouchEvent(event)) { 9 | if (event.touches && event.touches.length) { 10 | const {clientX: x, clientY: y} = event.touches[0]; 11 | 12 | return { 13 | x, 14 | y, 15 | }; 16 | } else if (event.changedTouches && event.changedTouches.length) { 17 | const {clientX: x, clientY: y} = event.changedTouches[0]; 18 | 19 | return { 20 | x, 21 | y, 22 | }; 23 | } 24 | } 25 | 26 | if (hasViewportRelativeCoordinates(event)) { 27 | return { 28 | x: event.clientX, 29 | y: event.clientY, 30 | }; 31 | } 32 | 33 | return null; 34 | } 35 | -------------------------------------------------------------------------------- /packages/utilities/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["./**/*.spec.*","./**/_stories/.*","./**/_stories/.*","./**/__stories__/.*", 3 | "./main.ts", "../script/*", 4 | "./Comp1", 5 | "./App.tsx", 6 | "./**/__test__", 7 | "./test/*", 8 | "./*.ts" 9 | ], 10 | "compilerOptions": { 11 | "rootDir": "./src", 12 | "outDir": "./dist/types", 13 | "target": "esnext", 14 | "useDefineForClassFields": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "strict": true, 18 | "jsx": "preserve", 19 | "jsxImportSource": "vue", 20 | "jsxFactory": "h", 21 | "jsxFragmentFactory": "Fragment", 22 | "strictNullChecks": false, 23 | "sourceMap": true, 24 | "declaration": true, 25 | "emitDeclarationOnly": true, 26 | "resolveJsonModule": true, 27 | "esModuleInterop": true, 28 | "types": ["vite/client", "node"], 29 | "lib": ["esnext", "dom"], 30 | "noImplicitAny": false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 33 | 34 | 39 | -------------------------------------------------------------------------------- /packages/accessibility/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "exclude": ["./**/*.spec.*","./**/_stories/.*","./**/_stories/.*","./**/__stories__/.*", 3 | "./main.ts", "../script/*", 4 | "./Comp1", 5 | "./App.tsx", 6 | "./**/__test__", 7 | "./test/*", 8 | "./*.ts" 9 | ], 10 | "compilerOptions": { 11 | "rootDir": "./src", 12 | "outDir": "./dist/types", 13 | "target": "esnext", 14 | "useDefineForClassFields": true, 15 | "module": "esnext", 16 | "moduleResolution": "node", 17 | "strict": true, 18 | "jsx": "preserve", 19 | "jsxImportSource": "vue", 20 | "jsxFactory": "h", 21 | "jsxFragmentFactory": "Fragment", 22 | "strictNullChecks": false, 23 | "sourceMap": true, 24 | "declaration": true, 25 | "emitDeclarationOnly": true, 26 | "resolveJsonModule": true, 27 | "esModuleInterop": true, 28 | "types": ["vite/client", "node"], 29 | "lib": ["esnext", "dom"], 30 | "noImplicitAny": false 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 5 | 6 | 19 | 20 | 48 | -------------------------------------------------------------------------------- /packages/core/src/hooks/utilities/useSyntheticListeners.ts: -------------------------------------------------------------------------------- 1 | 2 | import type {SyntheticEventName, UniqueIdentifier} from '../../types'; 3 | import {computed, type ComputedRef} from "vue"; 4 | 5 | export type SyntheticListener = { 6 | eventName: SyntheticEventName; 7 | handler: (event: any, id: UniqueIdentifier) => void; 8 | }; 9 | 10 | export type SyntheticListeners = SyntheticListener[]; 11 | 12 | export type SyntheticListenerMap = Record; 13 | 14 | export function useSyntheticListeners( 15 | listeners: SyntheticListeners, 16 | id: ComputedRef 17 | ): ComputedRef { 18 | return computed(() => { 19 | return listeners.reduce( 20 | (acc, {eventName, handler}) => { 21 | // @ts-ignore 22 | acc[eventName] = (event: any) => { 23 | handler(event, id.value); 24 | }; 25 | 26 | return acc; 27 | }, 28 | {} as SyntheticListenerMap 29 | ); 30 | }); 31 | } 32 | -------------------------------------------------------------------------------- /packages/modifiers/src/utilities/restrictToBoundingRect.ts: -------------------------------------------------------------------------------- 1 | import type {ClientRect} from '@dnd-kit-vue/core'; 2 | import type {Transform} from '@dnd-kit-vue/utilities'; 3 | 4 | export function restrictToBoundingRect( 5 | transform: Transform, 6 | rect: ClientRect, 7 | boundingRect: ClientRect 8 | ): Transform { 9 | const value = { 10 | ...transform, 11 | }; 12 | 13 | if (rect.top + transform.y <= boundingRect.top) { 14 | value.y = boundingRect.top - rect.top; 15 | } else if ( 16 | rect.bottom + transform.y >= 17 | boundingRect.top + boundingRect.height 18 | ) { 19 | value.y = boundingRect.top + boundingRect.height - rect.bottom; 20 | } 21 | 22 | if (rect.left + transform.x <= boundingRect.left) { 23 | value.x = boundingRect.left - rect.left; 24 | } else if ( 25 | rect.right + transform.x >= 26 | boundingRect.left + boundingRect.width 27 | ) { 28 | value.x = boundingRect.left + boundingRect.width - rect.right; 29 | } 30 | 31 | return value; 32 | } 33 | -------------------------------------------------------------------------------- /packages/core/src/hooks/utilities/useScrollableAncestors.ts: -------------------------------------------------------------------------------- 1 | 2 | import {useLazyMemo} from '@dnd-kit-vue/utilities'; 3 | 4 | import {getScrollableAncestors} from '../../utilities'; 5 | import {ref, watchEffect} from "vue"; 6 | 7 | const defaultValue: Element[] = []; 8 | 9 | export function useScrollableAncestors(node: HTMLElement | null) { 10 | const previousNode = ref(node); 11 | 12 | const ancestors = useLazyMemo( 13 | (previousValue) => { 14 | if (!node) { 15 | return defaultValue; 16 | } 17 | 18 | if ( 19 | previousValue && 20 | previousValue !== defaultValue && 21 | node && 22 | previousNode.value && 23 | node.parentNode === previousNode.value.parentNode 24 | ) { 25 | return previousValue; 26 | } 27 | 28 | return getScrollableAncestors(node); 29 | }, 30 | [()=>node] 31 | ); 32 | 33 | watchEffect(() => { 34 | previousNode.value = node; 35 | }); 36 | 37 | return ancestors; 38 | } 39 | -------------------------------------------------------------------------------- /src/components/icons/IconTooling.vue: -------------------------------------------------------------------------------- 1 | 2 | 20 | -------------------------------------------------------------------------------- /packages/core/src/utilities/scroll/getScrollOffsets.ts: -------------------------------------------------------------------------------- 1 | import {add} from '@dnd-kit-vue/utilities'; 2 | 3 | import type {Coordinates} from '../../types'; 4 | import { 5 | getScrollCoordinates, 6 | getScrollXCoordinate, 7 | getScrollYCoordinate, 8 | } from './getScrollCoordinates'; 9 | import {defaultCoordinates} from '../coordinates'; 10 | 11 | export function getScrollOffsets(scrollableAncestors: Element[]): Coordinates { 12 | return scrollableAncestors.reduce((acc, node) => { 13 | return add(acc, getScrollCoordinates(node)); 14 | }, defaultCoordinates); 15 | } 16 | 17 | export function getScrollXOffset(scrollableAncestors: Element[]): number { 18 | return scrollableAncestors.reduce((acc, node) => { 19 | return acc + getScrollXCoordinate(node); 20 | }, 0); 21 | } 22 | 23 | export function getScrollYOffset(scrollableAncestors: Element[]): number { 24 | return scrollableAncestors.reduce((acc, node) => { 25 | return acc + getScrollYCoordinate(node); 26 | }, 0); 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/src/hooks/utilities/useCombineActivators.ts: -------------------------------------------------------------------------------- 1 | import type {SensorActivatorFunction, SensorDescriptor} from '../../sensors'; 2 | import type { 3 | SyntheticListener, 4 | SyntheticListeners, 5 | } from './useSyntheticListeners'; 6 | import { computed, type ComputedRef, Ref } from 'vue' 7 | 8 | export function useCombineActivators( 9 | sensors: SensorDescriptor[], 10 | getSyntheticHandler: Ref<( 11 | handler: SensorActivatorFunction, 12 | sensor: SensorDescriptor 13 | ) => SyntheticListener['handler']> 14 | ): ComputedRef { 15 | return computed( 16 | () => sensors.reduce((accumulator, sensor) => { 17 | const {sensor: Sensor} = sensor; 18 | 19 | const sensorActivators = Sensor.activators.map((activator) => ({ 20 | eventName: activator.eventName, 21 | handler: getSyntheticHandler.value(activator.handler, sensor), 22 | })); 23 | 24 | 25 | return [...accumulator, ...sensorActivators]; 26 | }, [])); 27 | } 28 | -------------------------------------------------------------------------------- /packages/core/src/utilities/transform/inverseTransform.ts: -------------------------------------------------------------------------------- 1 | import type {ClientRect} from '../../types'; 2 | 3 | import {parseTransform} from './parseTransform'; 4 | 5 | export function inverseTransform( 6 | rect: ClientRect, 7 | transform: string, 8 | transformOrigin: string 9 | ): ClientRect { 10 | const parsedTransform = parseTransform(transform); 11 | 12 | if (!parsedTransform) { 13 | return rect; 14 | } 15 | 16 | const {scaleX, scaleY, x: translateX, y: translateY} = parsedTransform; 17 | 18 | const x = rect.left - translateX - (1 - scaleX) * parseFloat(transformOrigin); 19 | const y = 20 | rect.top - 21 | translateY - 22 | (1 - scaleY) * 23 | parseFloat(transformOrigin.slice(transformOrigin.indexOf(' ') + 1)); 24 | const w = scaleX ? rect.width / scaleX : rect.width; 25 | const h = scaleY ? rect.height / scaleY : rect.height; 26 | 27 | return { 28 | width: w, 29 | height: h, 30 | top: y, 31 | right: x + w, 32 | bottom: y + h, 33 | left: x, 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /src/components/icons/IconCommunity.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /packages/core/src/hooks/utilities/useMutationObserver.ts: -------------------------------------------------------------------------------- 1 | 2 | import {useEvent} from '@dnd-kit-vue/utilities'; 3 | import {computed, watch, watchEffect} from "vue"; 4 | 5 | interface Arguments { 6 | callback: MutationCallback; 7 | disabled?: boolean; 8 | } 9 | 10 | /** 11 | * Returns a new MutationObserver instance. 12 | * If `MutationObserver` is undefined in the execution environment, returns `undefined`. 13 | */ 14 | export function useMutationObserver({callback, disabled}: Arguments) { 15 | const handleMutations = useEvent(callback); 16 | const mutationObserver = computed(() => { 17 | if ( 18 | disabled || 19 | typeof window === 'undefined' || 20 | typeof window.MutationObserver === 'undefined' 21 | ) { 22 | return undefined; 23 | } 24 | 25 | const {MutationObserver} = window; 26 | 27 | return new MutationObserver(handleMutations.value!); 28 | }); 29 | 30 | watch(()=>mutationObserver.value, () => { 31 | return () => mutationObserver.value?.disconnect(); 32 | }, {immediate: true}); 33 | 34 | return mutationObserver; 35 | } 36 | -------------------------------------------------------------------------------- /packages/core/src/components/DndContext/defaults.ts: -------------------------------------------------------------------------------- 1 | import type {DeepRequired} from '@dnd-kit-vue/utilities'; 2 | 3 | import type {DataRef} from '../../store/types'; 4 | import {KeyboardSensor, PointerSensor} from '../../sensors'; 5 | import {MeasuringStrategy, MeasuringFrequency} from '../../hooks/utilities'; 6 | import { 7 | getClientRect, 8 | getTransformAgnosticClientRect, 9 | } from '../../utilities/rect'; 10 | 11 | import type {MeasuringConfiguration} from './types'; 12 | 13 | export const defaultSensors = [ 14 | {sensor: PointerSensor, options: {}}, 15 | {sensor: KeyboardSensor, options: {}}, 16 | ]; 17 | 18 | export const defaultData: DataRef = {current: {}}; 19 | 20 | export const defaultMeasuringConfiguration: DeepRequired = { 21 | draggable: { 22 | measure: getTransformAgnosticClientRect, 23 | }, 24 | droppable: { 25 | measure: getTransformAgnosticClientRect, 26 | strategy: MeasuringStrategy.WhileDragging, 27 | frequency: MeasuringFrequency.Optimized, 28 | }, 29 | dragOverlay: { 30 | measure: getClientRect, 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /packages/core/src/sensors/keyboard/defaults.ts: -------------------------------------------------------------------------------- 1 | import {type KeyboardCoordinateGetter, KeyboardCode, type KeyboardCodes} from './types'; 2 | 3 | export const defaultKeyboardCodes: KeyboardCodes = { 4 | start: [KeyboardCode.Space, KeyboardCode.Enter], 5 | cancel: [KeyboardCode.Esc], 6 | end: [KeyboardCode.Space, KeyboardCode.Enter], 7 | }; 8 | 9 | export const defaultKeyboardCoordinateGetter: KeyboardCoordinateGetter = ( 10 | event, 11 | {currentCoordinates} 12 | ) => { 13 | switch (event.code) { 14 | case KeyboardCode.Right: 15 | return { 16 | ...currentCoordinates, 17 | x: currentCoordinates.x + 25, 18 | }; 19 | case KeyboardCode.Left: 20 | return { 21 | ...currentCoordinates, 22 | x: currentCoordinates.x - 25, 23 | }; 24 | case KeyboardCode.Down: 25 | return { 26 | ...currentCoordinates, 27 | y: currentCoordinates.y + 25, 28 | }; 29 | case KeyboardCode.Up: 30 | return { 31 | ...currentCoordinates, 32 | y: currentCoordinates.y - 25, 33 | }; 34 | } 35 | 36 | return undefined; 37 | }; 38 | -------------------------------------------------------------------------------- /packages/core/src/hooks/utilities/useResizeObserver.ts: -------------------------------------------------------------------------------- 1 | 2 | import {useEvent} from '@dnd-kit-vue/utilities'; 3 | import {computed, onUnmounted, shallowRef, watch} from "vue"; 4 | 5 | interface Arguments { 6 | callback: ResizeObserverCallback; 7 | disabled?: boolean; 8 | } 9 | 10 | /** 11 | * Returns a new ResizeObserver instance bound to the `onResize` callback. 12 | * If `ResizeObserver` is undefined in the execution environment, returns `undefined`. 13 | */ 14 | export function useResizeObserver({callback, disabled}: Arguments) { 15 | const handleResize = useEvent(callback); 16 | const resizeObserver = shallowRef() 17 | watch(()=>disabled, ()=>{ 18 | if ( 19 | disabled || 20 | typeof window === 'undefined' || 21 | typeof window.ResizeObserver === 'undefined' 22 | ) { 23 | return undefined; 24 | } 25 | 26 | const {ResizeObserver} = window; 27 | 28 | return new ResizeObserver(handleResize.value as any); 29 | }, {immediate: true}) 30 | 31 | onUnmounted(() => { 32 | return () => resizeObserver.value?.disconnect(); 33 | }); 34 | 35 | return resizeObserver; 36 | } 37 | -------------------------------------------------------------------------------- /packages/core/src/utilities/scroll/getScrollPosition.ts: -------------------------------------------------------------------------------- 1 | import {isDocumentScrollingElement} from './documentScrollingElement'; 2 | 3 | export function getScrollPosition(scrollingContainer: Element) { 4 | const minScroll = { 5 | x: 0, 6 | y: 0, 7 | }; 8 | const dimensions = isDocumentScrollingElement(scrollingContainer) 9 | ? { 10 | height: window.innerHeight, 11 | width: window.innerWidth, 12 | } 13 | : { 14 | height: scrollingContainer.clientHeight, 15 | width: scrollingContainer.clientWidth, 16 | }; 17 | const maxScroll = { 18 | x: scrollingContainer.scrollWidth - dimensions.width, 19 | y: scrollingContainer.scrollHeight - dimensions.height, 20 | }; 21 | 22 | const isTop = scrollingContainer.scrollTop <= minScroll.y; 23 | const isLeft = scrollingContainer.scrollLeft <= minScroll.x; 24 | const isBottom = scrollingContainer.scrollTop >= maxScroll.y; 25 | const isRight = scrollingContainer.scrollLeft >= maxScroll.x; 26 | 27 | return { 28 | isTop, 29 | isLeft, 30 | isBottom, 31 | isRight, 32 | maxScroll, 33 | minScroll, 34 | }; 35 | } 36 | -------------------------------------------------------------------------------- /packages/sortable/README.md: -------------------------------------------------------------------------------- 1 | # @dnd-kit/sortable 2 | 3 | [![Stable release](https://img.shields.io/npm/v/@dnd-kit/sortable.svg)](https://npm.im/@dnd-kit/sortable) 4 | 5 | The sortable preset provides the building blocks to build sortable interfaces with @dnd-kit. 6 | 7 | ## Installation 8 | 9 | To get started, install the sortable preset via npm or yarn: 10 | 11 | ``` 12 | npm install @dnd-kit/sortable 13 | ``` 14 | 15 | ## Architecture 16 | 17 | The sortable preset builds on top of the primitives exposed by `@dnd-kit-vue/core` to help building sortable interfaces. 18 | 19 | The sortable preset exposes two main concepts: `SortableContext` and the `useSortable` hook: 20 | 21 | - The SortableContext provides information via context that is consumed by the `useSortable` hook. 22 | - The useSortable hook is an abstraction that composes the `useDroppable` and `useDraggable` hooks. 23 | 24 | ![The useSortable hook is an abstraction that composes the useDroppable and useDraggable hooks](/.github/assets/use-sortable.png) 25 | 26 | ## Usage 27 | 28 | Visit [docs.dndkit.com](https://docs.dndkit.com/presets/sortable) to learn how to use the Sortable preset. 29 | -------------------------------------------------------------------------------- /packages/core/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021, Claudéric Demers 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/core/src/store/actions.ts: -------------------------------------------------------------------------------- 1 | import type {Coordinates, UniqueIdentifier} from '../types'; 2 | import type {DroppableContainer} from './types'; 3 | 4 | export enum Action { 5 | DragStart = 'dragStart', 6 | DragMove = 'dragMove', 7 | DragEnd = 'dragEnd', 8 | DragCancel = 'dragCancel', 9 | DragOver = 'dragOver', 10 | RegisterDroppable = 'registerDroppable', 11 | SetDroppableDisabled = 'setDroppableDisabled', 12 | UnregisterDroppable = 'unregisterDroppable', 13 | } 14 | 15 | export type Actions = 16 | | { 17 | type: Action.DragStart; 18 | active: UniqueIdentifier; 19 | initialCoordinates: Coordinates; 20 | } 21 | | {type: Action.DragMove; coordinates: Coordinates} 22 | | {type: Action.DragEnd} 23 | | {type: Action.DragCancel} 24 | | { 25 | type: Action.RegisterDroppable; 26 | element: DroppableContainer; 27 | } 28 | | { 29 | type: Action.SetDroppableDisabled; 30 | id: UniqueIdentifier; 31 | key: UniqueIdentifier; 32 | disabled: boolean; 33 | } 34 | | { 35 | type: Action.UnregisterDroppable; 36 | id: UniqueIdentifier; 37 | key: UniqueIdentifier; 38 | }; 39 | -------------------------------------------------------------------------------- /packages/modifiers/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021, Claudéric Demers 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/sortable/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021, Claudéric Demers 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/utilities/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021, Claudéric Demers 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/accessibility/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021, Claudéric Demers 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/core/src/sensors/index.ts: -------------------------------------------------------------------------------- 1 | export {useSensor} from './useSensor'; 2 | 3 | export {useSensors} from './useSensors'; 4 | 5 | export {AbstractPointerSensor, PointerSensor} from './pointer'; 6 | export type { 7 | AbstractPointerSensorOptions, 8 | AbstractPointerSensorProps, 9 | PointerActivationConstraint, 10 | PointerEventHandlers, 11 | PointerSensorOptions, 12 | PointerSensorProps, 13 | } from './pointer'; 14 | 15 | export {MouseSensor} from './mouse'; 16 | export type {MouseSensorOptions, MouseSensorProps} from './mouse'; 17 | 18 | export {TouchSensor} from './touch'; 19 | export type {TouchSensorOptions, TouchSensorProps} from './touch'; 20 | 21 | export {KeyboardSensor, KeyboardCode} from './keyboard'; 22 | export type { 23 | KeyboardCoordinateGetter, 24 | KeyboardSensorOptions, 25 | KeyboardSensorProps, 26 | KeyboardCodes, 27 | } from './keyboard'; 28 | 29 | export type { 30 | Activator, 31 | Activators, 32 | Response as SensorResponse, 33 | Sensor, 34 | Sensors, 35 | SensorActivatorFunction, 36 | SensorDescriptor, 37 | SensorContext, 38 | SensorHandler, 39 | SensorInstance, 40 | SensorOptions, 41 | SensorProps, 42 | } from './types'; 43 | -------------------------------------------------------------------------------- /packages/core/src/hooks/utilities/useScrollOffsetsDelta.ts: -------------------------------------------------------------------------------- 1 | import {type Coordinates, subtract} from '@dnd-kit-vue/utilities'; 2 | 3 | import {defaultCoordinates} from '../../utilities'; 4 | import {computed, type ComputedRef, ref, shallowRef, watch, watchEffect} from "vue"; 5 | 6 | export function useScrollOffsetsDelta( 7 | scrollOffsets: ComputedRef, 8 | dependencies: any[] = [] 9 | ) { 10 | const initialScrollOffsets = shallowRef(null); 11 | 12 | watch(dependencies, () => { 13 | initialScrollOffsets.value = null; 14 | }, {immediate: true}); 15 | 16 | watch(dependencies, () => { 17 | const hasScrollOffsets = scrollOffsets.value !== defaultCoordinates; 18 | 19 | if (hasScrollOffsets && !initialScrollOffsets.value) { 20 | initialScrollOffsets.value = scrollOffsets.value; 21 | } 22 | 23 | if (!hasScrollOffsets && initialScrollOffsets.value) { 24 | initialScrollOffsets.value = null; 25 | } 26 | }, {immediate: true}); 27 | 28 | return computed(()=>{ 29 | return initialScrollOffsets.value 30 | ? subtract(scrollOffsets.value, initialScrollOffsets.value) 31 | : defaultCoordinates 32 | }); 33 | } 34 | -------------------------------------------------------------------------------- /packages/sortable/src/components/context/Consumer.tsx: -------------------------------------------------------------------------------- 1 | import {defineComponent, h, inject, type Ref, ref, type UnwrapRef, useSlots} from 'vue' 2 | import { type ContextDescriptor, ID_PREFIX} from "../SortableContext"; 3 | import {rectSortingStrategy} from "../../strategies"; 4 | 5 | 6 | 7 | export function useSortableContext (): { context: Ref> } { 8 | const context = inject('SortableContext', ref({ 9 | activeIndex: -1, 10 | containerId: ID_PREFIX, 11 | disableTransforms: false, 12 | items: [], 13 | overIndex: -1, 14 | useDragOverlay: false, 15 | sortedRects: [], 16 | strategy: rectSortingStrategy, 17 | disabled: { 18 | draggable: false, 19 | droppable: false, 20 | }, 21 | })) 22 | return { 23 | context 24 | } 25 | } 26 | export const vuePropsType = { 27 | name: String 28 | } 29 | const Consumer = defineComponent(() => { 30 | const slots = useSlots() 31 | const {context} = useSortableContext() 32 | return () => slots.default ? slots.default(context) : null 33 | }, { 34 | props: vuePropsType, 35 | name: 'SortableConsumer' 36 | }) 37 | 38 | 39 | export default Consumer 40 | 41 | -------------------------------------------------------------------------------- /packages/core/src/components/Accessibility/defaults.ts: -------------------------------------------------------------------------------- 1 | import type {Announcements, ScreenReaderInstructions} from './types'; 2 | 3 | export const defaultScreenReaderInstructions: ScreenReaderInstructions = { 4 | draggable: ` 5 | To pick up a draggable item, press the space bar. 6 | While dragging, use the arrow keys to move the item. 7 | Press space again to drop the item in its new position, or press escape to cancel. 8 | `, 9 | }; 10 | 11 | export const defaultAnnouncements: Announcements = { 12 | onDragStart({active}) { 13 | return `Picked up draggable item ${active.id}.`; 14 | }, 15 | onDragOver({active, over}) { 16 | if (over) { 17 | return `Draggable item ${active.id} was moved over droppable area ${over.id}.`; 18 | } 19 | 20 | return `Draggable item ${active.id} is no longer over a droppable area.`; 21 | }, 22 | onDragEnd({active, over}) { 23 | if (over) { 24 | return `Draggable item ${active.id} was dropped over droppable area ${over.id}`; 25 | } 26 | 27 | return `Draggable item ${active.id} was dropped.`; 28 | }, 29 | onDragCancel({active}) { 30 | return `Dragging was cancelled. Draggable item ${active.id} was dropped.`; 31 | }, 32 | }; 33 | -------------------------------------------------------------------------------- /packages/core/src/sensors/mouse/MouseSensor.ts: -------------------------------------------------------------------------------- 1 | 2 | import {getOwnerDocument} from '@dnd-kit-vue/utilities'; 3 | 4 | import type {SensorProps} from '../types'; 5 | import { 6 | AbstractPointerSensor, 7 | type PointerEventHandlers, 8 | type AbstractPointerSensorOptions, 9 | } from '../pointer'; 10 | 11 | const events: PointerEventHandlers = { 12 | move: {name: 'mousemove'}, 13 | end: {name: 'mouseup'}, 14 | }; 15 | 16 | enum MouseButton { 17 | RightClick = 2, 18 | } 19 | 20 | export interface MouseSensorOptions extends AbstractPointerSensorOptions {} 21 | 22 | export type MouseSensorProps = SensorProps; 23 | 24 | export class MouseSensor extends AbstractPointerSensor { 25 | constructor(props: MouseSensorProps) { 26 | super(props, events, getOwnerDocument(props.event.target)); 27 | } 28 | 29 | static activators = [ 30 | { 31 | eventName: 'onMousedown' as const, 32 | handler: ( 33 | event: MouseEvent, 34 | {onActivation}: MouseSensorOptions 35 | ) => { 36 | if (event.button === MouseButton.RightClick) { 37 | return false; 38 | } 39 | 40 | onActivation?.({event}); 41 | 42 | return true; 43 | }, 44 | }, 45 | ]; 46 | } 47 | -------------------------------------------------------------------------------- /packages/accessibility/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dnd-kit-vue/accessibility", 3 | "version": "0.1.2", 4 | "description": "A generic toolkit to help with accessibility", 5 | "author": "Claudéric Demers", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/rashagu/dnd-kit-vue.git", 10 | "directory": "packages/accessibility" 11 | }, 12 | "scripts": { 13 | "dev": "vite", 14 | "rm:dist": "rimraf dist", 15 | "tsc": "tsc -b --force", 16 | "build": "pnpm rm:dist && vite build && tsc -b --force", 17 | "preview": "vite preview" 18 | }, 19 | "main": "dist/index.mjs", 20 | "module": "dist/index.mjs", 21 | "types": "dist/types/index.d.ts", 22 | "files": [ 23 | "README.md", 24 | "CHANGELOG.md", 25 | "dist" 26 | ], 27 | "peerDependencies": { 28 | "vue": "^3.4.27", 29 | "lodash": "^4.17.21" 30 | }, 31 | "devDependencies": { 32 | "@types/lodash": "^4.17.1", 33 | "@types/node": "^20.12.11", 34 | "@vitejs/plugin-vue": "^5.0.5", 35 | "@vitejs/plugin-vue-jsx": "^4.0.0", 36 | "rimraf": "^3.0.2", 37 | "typescript": "^5.5.1-rc", 38 | "vite": "^5.2.13", 39 | "vue-tsc": "^2.0.21" 40 | }, 41 | "publishConfig": { 42 | "access": "public" 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/icons/IconDocumentation.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /packages/core/src/hooks/utilities/useCachedNode.ts: -------------------------------------------------------------------------------- 1 | import {useLazyMemo} from '@dnd-kit-vue/utilities'; 2 | 3 | import type {DraggableNode, DraggableNodes} from '../../store'; 4 | import type {UniqueIdentifier} from '../../types'; 5 | import {computed, type ComputedRef, type Ref, ref} from "vue"; 6 | 7 | export function useCachedNode( 8 | draggableNodes: ComputedRef | Ref, 9 | id: ComputedRef | Ref 10 | ): ComputedRef { 11 | 12 | const valueRef = ref(); 13 | 14 | return computed( 15 | () => { 16 | const draggableNode = id.value !== null ? draggableNodes.value.get(id.value) : undefined; 17 | const node = draggableNode ? draggableNode.node : null; 18 | 19 | const newValue = () => { 20 | if (id === null) { 21 | return null; 22 | } 23 | // In some cases, the draggable node can unmount while dragging 24 | // This is the case for virtualized lists. In those situations, 25 | // we fall back to the last known value for that node. 26 | return node ?? valueRef.value ?? null; 27 | }; 28 | 29 | valueRef.value = newValue(); 30 | 31 | return newValue(); 32 | }); 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /packages/utilities/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dnd-kit-vue/utilities", 3 | "version": "0.1.2", 4 | "description": "Internal utilities to bee shared between `@dnd-kit` packages", 5 | "author": "Claudéric Demers", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/rashagu/dnd-kit-vue.git", 10 | "directory": "packages/utilities" 11 | }, 12 | "scripts": { 13 | "dev": "vite", 14 | "rm:dist": "rimraf dist", 15 | "tsc": "tsc -b --force", 16 | "build": "pnpm rm:dist && vite build && tsc -b --force", 17 | "preview": "vite preview" 18 | }, 19 | "main": "dist/index.mjs", 20 | "module": "dist/index.mjs", 21 | "types": "dist/types/index.d.ts", 22 | "files": [ 23 | "README.md", 24 | "CHANGELOG.md", 25 | "LICENSE", 26 | "dist" 27 | ], 28 | "peerDependencies": { 29 | "vue": "^3.4.27", 30 | "lodash": "^4.17.21" 31 | }, 32 | "devDependencies": { 33 | "@types/lodash": "^4.17.1", 34 | "@types/node": "^20.12.11", 35 | "@vitejs/plugin-vue": "^5.0.5", 36 | "@vitejs/plugin-vue-jsx": "^4.0.0", 37 | "rimraf": "^3.0.2", 38 | "typescript": "^5.5.1-rc", 39 | "vite": "^5.2.13", 40 | "vue-tsc": "^2.0.21" 41 | }, 42 | "publishConfig": { 43 | "access": "public" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /packages/core/src/utilities/algorithms/closestCorners.ts: -------------------------------------------------------------------------------- 1 | import {distanceBetween} from '../coordinates'; 2 | 3 | import type {CollisionDescriptor, CollisionDetection} from './types'; 4 | import {cornersOfRectangle, sortCollisionsAsc} from './helpers'; 5 | 6 | /** 7 | * Returns the closest rectangles from an array of rectangles to the corners of 8 | * another rectangle. 9 | */ 10 | export const closestCorners: CollisionDetection = ({ 11 | collisionRect, 12 | droppableRects, 13 | droppableContainers, 14 | }) => { 15 | const corners = cornersOfRectangle(collisionRect); 16 | const collisions: CollisionDescriptor[] = []; 17 | 18 | for (const droppableContainer of droppableContainers) { 19 | const {id} = droppableContainer; 20 | const rect = droppableRects.get(id); 21 | 22 | if (rect) { 23 | const rectCorners = cornersOfRectangle(rect); 24 | const distances = corners.reduce((accumulator, corner, index) => { 25 | return accumulator + distanceBetween(rectCorners[index], corner); 26 | }, 0); 27 | const effectiveDistance = Number((distances / 4).toFixed(4)); 28 | 29 | collisions.push({ 30 | id, 31 | data: {droppableContainer, value: effectiveDistance}, 32 | }); 33 | } 34 | } 35 | 36 | return collisions.sort(sortCollisionsAsc); 37 | }; 38 | -------------------------------------------------------------------------------- /packages/utilities/src/css.ts: -------------------------------------------------------------------------------- 1 | export type Transform = { 2 | x: number; 3 | y: number; 4 | scaleX: number; 5 | scaleY: number; 6 | }; 7 | 8 | export interface Transition { 9 | property: string; 10 | easing: string; 11 | duration: number; 12 | } 13 | 14 | export const CSS = Object.freeze({ 15 | Translate: { 16 | toString(transform: Transform | null) { 17 | if (!transform) { 18 | return; 19 | } 20 | 21 | const {x, y} = transform; 22 | 23 | return `translate3d(${x ? Math.round(x) : 0}px, ${ 24 | y ? Math.round(y) : 0 25 | }px, 0)`; 26 | }, 27 | }, 28 | Scale: { 29 | toString(transform: Transform | null) { 30 | if (!transform) { 31 | return; 32 | } 33 | 34 | const {scaleX, scaleY} = transform; 35 | 36 | return `scaleX(${scaleX}) scaleY(${scaleY})`; 37 | }, 38 | }, 39 | Transform: { 40 | toString(transform: Transform | null) { 41 | if (!transform) { 42 | return; 43 | } 44 | 45 | return [ 46 | CSS.Translate.toString(transform), 47 | CSS.Scale.toString(transform), 48 | ].join(' '); 49 | }, 50 | }, 51 | Transition: { 52 | toString({property, duration, easing}: Transition) { 53 | return `${property} ${duration}ms ${easing}`; 54 | }, 55 | }, 56 | }); 57 | -------------------------------------------------------------------------------- /packages/core/test/demo/Draggable.tsx: -------------------------------------------------------------------------------- 1 | import { defineComponent, ref, h, Fragment, computed } from 'vue' 2 | import { useDraggable, type UseDraggableArguments } from '@dnd-kit-vue/core' 3 | import { CSS } from '@dnd-kit-vue/utilities' 4 | 5 | interface ExampleProps { 6 | name?: string 7 | } 8 | 9 | export const vuePropsType = { 10 | name: String 11 | } 12 | const Draggable = defineComponent((props, { slots }) => { 13 | 14 | const { attributes, listeners, setNodeRef, transform, isDragging } = useDraggable({ 15 | 16 | disabled: computed(()=>false), 17 | id: computed(()=>'unique-id') 18 | }) 19 | 20 | return () => { 21 | return ( 22 | 38 | ) 39 | } 40 | }) 41 | 42 | Draggable.props = vuePropsType 43 | 44 | export default Draggable 45 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 官方即将推出无关框架的版本 2 | > [https://next.dndkit.com/overview](https://next.dndkit.com/overview) 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | # dnd-kit-vue 20 | 21 | This template should help get you started developing with Vue 3 in Vite. 22 | 23 | ## Recommended IDE Setup 24 | 25 | [VSCode](https://code.visualstudio.com/) + [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) (and disable Vetur). 26 | 27 | ## Type Support for `.vue` Imports in TS 28 | 29 | TypeScript cannot handle type information for `.vue` imports by default, so we replace the `tsc` CLI with `vue-tsc` for type checking. In editors, we need [Volar](https://marketplace.visualstudio.com/items?itemName=Vue.volar) to make the TypeScript language service aware of `.vue` types. 30 | 31 | ## Customize configuration 32 | 33 | See [Vite Configuration Reference](https://vitejs.dev/config/). 34 | 35 | ## Project Setup 36 | 37 | ```sh 38 | pnpm install 39 | ``` 40 | 41 | ### Compile and Hot-Reload for Development 42 | 43 | ```sh 44 | pnpm dev 45 | ``` 46 | 47 | ### Type-Check, Compile and Minify for Production 48 | 49 | ```sh 50 | pnpm build 51 | ``` 52 | 53 | ### Run Unit Tests with [Vitest](https://vitest.dev/) 54 | 55 | ```sh 56 | pnpm test:unit 57 | ``` 58 | 59 | ### Lint with [ESLint](https://eslint.org/) 60 | 61 | ```sh 62 | pnpm lint 63 | ``` 64 | -------------------------------------------------------------------------------- /packages/core/src/utilities/index.ts: -------------------------------------------------------------------------------- 1 | import {type Ref, ref} from "vue"; 2 | 3 | export { 4 | closestCenter, 5 | closestCorners, 6 | rectIntersection, 7 | getFirstCollision, 8 | pointerWithin, 9 | } from './algorithms'; 10 | export type { 11 | Collision, 12 | CollisionDescriptor, 13 | CollisionDetection, 14 | } from './algorithms'; 15 | 16 | export { 17 | defaultCoordinates, 18 | distanceBetween, 19 | getRelativeTransformOrigin, 20 | } from './coordinates'; 21 | 22 | export { 23 | Rect, 24 | adjustScale, 25 | getAdjustedRect, 26 | getClientRect, 27 | getTransformAgnosticClientRect, 28 | getWindowClientRect, 29 | getRectDelta, 30 | } from './rect'; 31 | 32 | export {noop} from './other'; 33 | 34 | export { 35 | getFirstScrollableAncestor, 36 | getScrollableAncestors, 37 | getScrollableElement, 38 | getScrollCoordinates, 39 | getScrollDirectionAndSpeed, 40 | getScrollElementRect, 41 | getScrollOffsets, 42 | getScrollPosition, 43 | isDocumentScrollingElement, 44 | } from './scroll'; 45 | 46 | 47 | 48 | export function useReducer(reducer:any,initialState:any, init?:(value?:any)=>any):[Ref, (action?:any)=>void] { 49 | const state = ref(initialState); 50 | if (init){ 51 | state.value = init() 52 | } 53 | 54 | let dispatch = (action?:any) => { 55 | state.value = reducer(state.value,action) 56 | } 57 | 58 | return [state,dispatch] 59 | } 60 | -------------------------------------------------------------------------------- /packages/sortable/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dnd-kit-vue/sortable", 3 | "version": "0.1.2", 4 | "description": "Official sortable preset and sensors for dnd kit", 5 | "author": "Claudéric Demers", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/rashagu/dnd-kit-vue.git", 10 | "directory": "packages/sortable" 11 | }, 12 | "scripts": { 13 | "dev": "vite", 14 | "rm:dist": "rimraf dist", 15 | "tsc": "tsc -b --force", 16 | "build": "pnpm rm:dist && vite build && tsc -b --force", 17 | "preview": "vite preview" 18 | }, 19 | "main": "dist/index.mjs", 20 | "module": "dist/index.mjs", 21 | "types": "dist/types/index.d.ts", 22 | "files": [ 23 | "README.md", 24 | "CHANGELOG.md", 25 | "LICENSE", 26 | "dist" 27 | ], 28 | "dependencies": { 29 | "@dnd-kit-vue/utilities": "^0.1.2", 30 | "@dnd-kit-vue/core": "^0.1.2" 31 | }, 32 | "peerDependencies": { 33 | "vue": "^3.4.27", 34 | "lodash": "^4.17.21" 35 | }, 36 | "devDependencies": { 37 | "@types/lodash": "^4.17.1", 38 | "@types/node": "^20.12.11", 39 | "@vitejs/plugin-vue": "^5.0.5", 40 | "@vitejs/plugin-vue-jsx": "^4.0.0", 41 | "rimraf": "^3.0.2", 42 | "typescript": "^5.5.1-rc", 43 | "vite": "^5.2.13", 44 | "vue-tsc": "^2.0.21" 45 | }, 46 | "publishConfig": { 47 | "access": "public" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/core/test/demo/CoreTest.tsx: -------------------------------------------------------------------------------- 1 | import {defineComponent, ref, h, Fragment} from 'vue' 2 | import {defaultCoordinates, DndContext, useSensors} from "@dnd-kit-vue/core"; 3 | import Draggable from "./Draggable"; 4 | import type {Coordinates} from "@dnd-kit-vue/utilities"; 5 | 6 | 7 | 8 | 9 | interface ExampleProps { 10 | name?: string 11 | } 12 | 13 | export const vuePropsType = { 14 | name: String 15 | } 16 | const CoreTest = defineComponent((props, {slots}) => { 17 | 18 | const coordinates = ref(defaultCoordinates); 19 | function setCoordinates(val:any) { 20 | coordinates.value = val 21 | } 22 | const handleDragEnd = ({delta}:any) => { 23 | const {x, y} = coordinates.value 24 | coordinates.value = { 25 | x:x + delta.x, 26 | y:y + delta.y 27 | } 28 | } 29 | const handleDragMove = ({delta}:any) => { 30 | const {x, y} = coordinates.value 31 | // coordinates.value = { 32 | // x:delta.x, 33 | // y:delta.y 34 | // } 35 | } 36 | 37 | return ()=>( 38 | 41 |
42 | 43 |
44 |
45 | ); 46 | }) 47 | 48 | CoreTest.props = vuePropsType 49 | CoreTest.name = 'CoreTest' 50 | 51 | export default CoreTest 52 | 53 | -------------------------------------------------------------------------------- /packages/modifiers/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dnd-kit-vue/modifiers", 3 | "version": "0.1.2", 4 | "description": "Translate modifier presets for use with `@dnd-kit` packages.", 5 | "author": "Claudéric Demers", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/rashagu/dnd-kit-vue.git", 10 | "directory": "packages/modifiers" 11 | }, 12 | "scripts": { 13 | "dev": "vite", 14 | "rm:dist": "rimraf dist", 15 | "tsc": "tsc -b --force", 16 | "build": "pnpm rm:dist && vite build && tsc -b --force", 17 | "preview": "vite preview" 18 | }, 19 | "main": "dist/index.mjs", 20 | "module": "dist/index.mjs", 21 | "types": "dist/types/index.d.ts", 22 | "files": [ 23 | "README.md", 24 | "CHANGELOG.md", 25 | "LICENSE", 26 | "dist" 27 | ], 28 | "dependencies": { 29 | "@dnd-kit-vue/utilities": "^0.1.2", 30 | "@dnd-kit-vue/core": "^0.1.2" 31 | }, 32 | "peerDependencies": { 33 | "vue": "^3.4.27", 34 | "lodash": "^4.17.21" 35 | }, 36 | "devDependencies": { 37 | "@types/lodash": "^4.17.1", 38 | "@types/node": "^20.12.11", 39 | "@vitejs/plugin-vue": "^5.0.5", 40 | "@vitejs/plugin-vue-jsx": "^4.0.0", 41 | "rimraf": "^3.0.2", 42 | "typescript": "^5.5.1-rc", 43 | "vite": "^5.2.13", 44 | "vue-tsc": "^2.0.21" 45 | }, 46 | "publishConfig": { 47 | "access": "public" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/sortable/src/hooks/defaults.ts: -------------------------------------------------------------------------------- 1 | import {CSS} from '@dnd-kit-vue/utilities'; 2 | 3 | import {arrayMove} from '../utilities'; 4 | 5 | import type { 6 | AnimateLayoutChanges, 7 | NewIndexGetter, 8 | SortableTransition, 9 | } from './types'; 10 | 11 | export const defaultNewIndexGetter: NewIndexGetter = ({ 12 | id, 13 | items, 14 | activeIndex, 15 | overIndex, 16 | }) => arrayMove(items, activeIndex, overIndex).indexOf(id); 17 | 18 | export const defaultAnimateLayoutChanges: AnimateLayoutChanges = ({ 19 | containerId, 20 | isSorting, 21 | wasDragging, 22 | index, 23 | items, 24 | newIndex, 25 | previousItems, 26 | previousContainerId, 27 | transition, 28 | }) => { 29 | if (!transition || !wasDragging) { 30 | return false; 31 | } 32 | 33 | if (previousItems !== items && index === newIndex) { 34 | return false; 35 | } 36 | 37 | if (isSorting) { 38 | return true; 39 | } 40 | 41 | return newIndex !== index && containerId === previousContainerId; 42 | }; 43 | 44 | export const defaultTransition: SortableTransition = { 45 | duration: 200, 46 | easing: 'ease', 47 | }; 48 | 49 | export const transitionProperty = 'transform'; 50 | 51 | export const disabledTransition = CSS.Transition.toString({ 52 | property: transitionProperty, 53 | duration: 0, 54 | easing: 'linear', 55 | }); 56 | 57 | export const defaultAttributes = { 58 | roleDescription: 'sortable', 59 | }; 60 | -------------------------------------------------------------------------------- /packages/core/src/sensors/pointer/PointerSensor.ts: -------------------------------------------------------------------------------- 1 | 2 | import {getOwnerDocument} from '@dnd-kit-vue/utilities'; 3 | 4 | import type {SensorProps} from '../types'; 5 | import { 6 | AbstractPointerSensor, 7 | type AbstractPointerSensorOptions, 8 | type PointerEventHandlers, 9 | } from './AbstractPointerSensor'; 10 | 11 | const events: PointerEventHandlers = { 12 | move: {name: 'pointermove'}, 13 | end: {name: 'pointerup'}, 14 | }; 15 | 16 | export interface PointerSensorOptions extends AbstractPointerSensorOptions {} 17 | 18 | export type PointerSensorProps = SensorProps; 19 | 20 | export class PointerSensor extends AbstractPointerSensor { 21 | constructor(props: PointerSensorProps) { 22 | const {event} = props; 23 | // Pointer events stop firing if the target is unmounted while dragging 24 | // Therefore we attach listeners to the owner document instead 25 | const listenerTarget = getOwnerDocument(event.target); 26 | 27 | super(props, events, listenerTarget); 28 | } 29 | 30 | static activators = [ 31 | { 32 | eventName: 'onPointerdown' as const, 33 | handler: ( 34 | event: PointerEvent, 35 | {onActivation}: PointerSensorOptions 36 | ) => { 37 | if (!event.isPrimary || event.button !== 0) { 38 | return false; 39 | } 40 | 41 | onActivation?.({event}); 42 | 43 | return true; 44 | }, 45 | }, 46 | ]; 47 | } 48 | -------------------------------------------------------------------------------- /packages/core/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@dnd-kit-vue/core", 3 | "version": "0.1.2", 4 | "description": "dnd kit – a lightweight React library for building performant and accessible drag and drop experiences", 5 | "author": "Claudéric Demers", 6 | "license": "MIT", 7 | "repository": { 8 | "type": "git", 9 | "url": "git+https://github.com/rashagu/dnd-kit-vue.git", 10 | "directory": "packages/core" 11 | }, 12 | "scripts": { 13 | "dev": "vite", 14 | "rm:dist": "rimraf dist", 15 | "tsc": "tsc -b --force", 16 | "build": "pnpm rm:dist && vite build && tsc -b --force", 17 | "preview": "vite preview" 18 | }, 19 | "main": "dist/index.mjs", 20 | "module": "dist/index.mjs", 21 | "types": "dist/types/index.d.ts", 22 | "files": [ 23 | "README.md", 24 | "CHANGELOG.md", 25 | "LICENSE", 26 | "dist" 27 | ], 28 | "dependencies": { 29 | "@dnd-kit-vue/accessibility": "^0.1.2", 30 | "@dnd-kit-vue/utilities": "^0.1.2" 31 | }, 32 | "peerDependencies": { 33 | "vue": "^3.4.27", 34 | "lodash": "^4.17.21" 35 | }, 36 | "devDependencies": { 37 | "@types/lodash": "^4.17.1", 38 | "@types/node": "^20.12.11", 39 | "@vitejs/plugin-vue": "^5.0.5", 40 | "@vitejs/plugin-vue-jsx": "^4.0.0", 41 | "rimraf": "^3.0.2", 42 | "typescript": "^5.5.1-rc", 43 | "vite": "^5.2.13", 44 | "vue-tsc": "^2.0.21" 45 | }, 46 | "publishConfig": { 47 | "access": "public" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/core/src/hooks/utilities/index.ts: -------------------------------------------------------------------------------- 1 | export { 2 | AutoScrollActivator, 3 | TraversalOrder, 4 | useAutoScroller, 5 | } from './useAutoScroller'; 6 | export type {Options as AutoScrollOptions} from './useAutoScroller'; 7 | export {useCachedNode} from './useCachedNode'; 8 | export {useCombineActivators} from './useCombineActivators'; 9 | export { 10 | useDroppableMeasuring, 11 | MeasuringFrequency, 12 | MeasuringStrategy, 13 | } from './useDroppableMeasuring'; 14 | export type {DroppableMeasuring} from './useDroppableMeasuring'; 15 | export {useInitialValue} from './useInitialValue'; 16 | export {useInitialRect} from './useInitialRect'; 17 | export {useRect} from './useRect'; 18 | export {useRectDelta} from './useRectDelta'; 19 | export {useResizeObserver} from './useResizeObserver'; 20 | export {useScrollableAncestors} from './useScrollableAncestors'; 21 | export {useScrollIntoViewIfNeeded} from './useScrollIntoViewIfNeeded'; 22 | export {useScrollOffsets} from './useScrollOffsets'; 23 | export {useScrollOffsetsDelta} from './useScrollOffsetsDelta'; 24 | export {useSensorSetup} from './useSensorSetup'; 25 | export {useSyntheticListeners} from './useSyntheticListeners'; 26 | export type { 27 | SyntheticListener, 28 | SyntheticListeners, 29 | SyntheticListenerMap, 30 | } from './useSyntheticListeners'; 31 | export {useRects} from './useRects'; 32 | export {useWindowRect} from './useWindowRect'; 33 | export {useDragOverlayMeasuring} from './useDragOverlayMeasuring'; 34 | -------------------------------------------------------------------------------- /packages/core/src/utilities/algorithms/closestCenter.ts: -------------------------------------------------------------------------------- 1 | import {distanceBetween} from '../coordinates'; 2 | import type {Coordinates, ClientRect} from '../../types'; 3 | 4 | import type {CollisionDescriptor, CollisionDetection} from './types'; 5 | import {sortCollisionsAsc} from './helpers'; 6 | 7 | /** 8 | * Returns the coordinates of the center of a given ClientRect 9 | */ 10 | function centerOfRectangle( 11 | rect: ClientRect, 12 | left = rect.left, 13 | top = rect.top 14 | ): Coordinates { 15 | return { 16 | x: left + rect.width * 0.5, 17 | y: top + rect.height * 0.5, 18 | }; 19 | } 20 | 21 | /** 22 | * Returns the closest rectangles from an array of rectangles to the center of a given 23 | * rectangle. 24 | */ 25 | export const closestCenter: CollisionDetection = ({ 26 | collisionRect, 27 | droppableRects, 28 | droppableContainers, 29 | }) => { 30 | const centerRect = centerOfRectangle( 31 | collisionRect, 32 | collisionRect.left, 33 | collisionRect.top 34 | ); 35 | const collisions: CollisionDescriptor[] = []; 36 | 37 | for (const droppableContainer of droppableContainers) { 38 | const {id} = droppableContainer; 39 | const rect = droppableRects.get(id); 40 | 41 | if (rect) { 42 | const distBetween = distanceBetween(centerOfRectangle(rect), centerRect); 43 | 44 | collisions.push({id, data: {droppableContainer, value: distBetween}}); 45 | } 46 | } 47 | 48 | return collisions.sort(sortCollisionsAsc); 49 | }; 50 | -------------------------------------------------------------------------------- /packages/core/src/utilities/rect/getRect.ts: -------------------------------------------------------------------------------- 1 | import {getWindow} from '@dnd-kit-vue/utilities'; 2 | 3 | import type {ClientRect} from '../../types'; 4 | import {inverseTransform} from '../transform'; 5 | 6 | interface Options { 7 | ignoreTransform?: boolean; 8 | } 9 | 10 | const defaultOptions: Options = {ignoreTransform: false}; 11 | 12 | /** 13 | * Returns the bounding client rect of an element relative to the viewport. 14 | */ 15 | export function getClientRect( 16 | element: Element, 17 | options: Options = defaultOptions 18 | ) { 19 | let rect: ClientRect = element.getBoundingClientRect(); 20 | 21 | if (options.ignoreTransform) { 22 | const {getComputedStyle} = getWindow(element); 23 | const {transform, transformOrigin} = getComputedStyle(element); 24 | 25 | if (transform) { 26 | rect = inverseTransform(rect, transform, transformOrigin); 27 | } 28 | } 29 | 30 | const {top, left, width, height, bottom, right} = rect; 31 | 32 | return { 33 | top, 34 | left, 35 | width, 36 | height, 37 | bottom, 38 | right, 39 | }; 40 | } 41 | 42 | /** 43 | * Returns the bounding client rect of an element relative to the viewport. 44 | * 45 | * @remarks 46 | * The ClientRect returned by this method does not take into account transforms 47 | * applied to the element it measures. 48 | * 49 | */ 50 | export function getTransformAgnosticClientRect(element: Element): ClientRect { 51 | return getClientRect(element, {ignoreTransform: true}); 52 | } 53 | -------------------------------------------------------------------------------- /packages/modifiers/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @dnd-kit-vue/modifiers 2 | 3 | ## 0.1.2 4 | 5 | ### Patch Changes 6 | 7 | - ff2942d: fix 0.1.1 ts5.5.1 8 | - Updated dependencies [ff2942d] 9 | - @dnd-kit-vue/utilities@0.1.2 10 | - @dnd-kit-vue/core@0.1.2 11 | 12 | ## 0.1.1 13 | 14 | ### Patch Changes 15 | 16 | - 23dd1f2: fix 0.1.0 17 | - Updated dependencies [23dd1f2] 18 | - @dnd-kit-vue/utilities@0.1.1 19 | - @dnd-kit-vue/core@0.1.1 20 | 21 | ## 0.1.0 22 | 23 | ### Minor Changes 24 | 25 | - f221a5c: vue 3.4 26 | 27 | ### Patch Changes 28 | 29 | - Updated dependencies [f221a5c] 30 | - @dnd-kit-vue/utilities@0.1.0 31 | - @dnd-kit-vue/core@0.1.0 32 | 33 | ## 0.0.5 34 | 35 | ### Patch Changes 36 | 37 | - 30fce38: scrollableAncestorRects 38 | - Updated dependencies [30fce38] 39 | - @dnd-kit-vue/core@0.0.5 40 | - @dnd-kit-vue/utilities@0.0.5 41 | 42 | ## 0.0.4 43 | 44 | ### Patch Changes 45 | 46 | - fdf35bb: fix build 47 | - 7c16d6d: a 48 | - 7ae3e8f: 合并 49 | - Updated dependencies [fdf35bb] 50 | - Updated dependencies [7c16d6d] 51 | - Updated dependencies [7ae3e8f] 52 | - @dnd-kit-vue/core@0.0.4 53 | - @dnd-kit-vue/utilities@0.0.4 54 | 55 | ## 0.0.2 56 | 57 | ### Patch Changes 58 | 59 | - aca2662: DragOverlay 60 | - Updated dependencies [aca2662] 61 | - @dnd-kit-vue/core@0.0.2 62 | - @dnd-kit-vue/utilities@0.0.2 63 | 64 | ## 0.0.1 65 | 66 | ### Patch Changes 67 | 68 | - 98942bd: first version 69 | - Updated dependencies [98942bd] 70 | - @dnd-kit-vue/core@0.0.1 71 | - @dnd-kit-vue/utilities@0.0.1 72 | -------------------------------------------------------------------------------- /packages/sortable/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @dnd-kit-vue/sortable 2 | 3 | ## 0.1.2 4 | 5 | ### Patch Changes 6 | 7 | - ff2942d: fix 0.1.1 ts5.5.1 8 | - Updated dependencies [ff2942d] 9 | - @dnd-kit-vue/utilities@0.1.2 10 | - @dnd-kit-vue/core@0.1.2 11 | 12 | ## 0.1.1 13 | 14 | ### Patch Changes 15 | 16 | - 23dd1f2: fix 0.1.0 17 | - Updated dependencies [23dd1f2] 18 | - @dnd-kit-vue/utilities@0.1.1 19 | - @dnd-kit-vue/core@0.1.1 20 | 21 | ## 0.1.0 22 | 23 | ### Minor Changes 24 | 25 | - f221a5c: vue 3.4 26 | 27 | ### Patch Changes 28 | 29 | - Updated dependencies [f221a5c] 30 | - @dnd-kit-vue/utilities@0.1.0 31 | - @dnd-kit-vue/core@0.1.0 32 | 33 | ## 0.0.5 34 | 35 | ### Patch Changes 36 | 37 | - 30fce38: scrollableAncestorRects 38 | - Updated dependencies [30fce38] 39 | - @dnd-kit-vue/core@0.0.5 40 | - @dnd-kit-vue/utilities@0.0.5 41 | 42 | ## 0.0.4 43 | 44 | ### Patch Changes 45 | 46 | - fdf35bb: fix build 47 | - 7c16d6d: a 48 | - 7ae3e8f: 合并 49 | - Updated dependencies [fdf35bb] 50 | - Updated dependencies [7c16d6d] 51 | - Updated dependencies [7ae3e8f] 52 | - @dnd-kit-vue/core@0.0.4 53 | - @dnd-kit-vue/utilities@0.0.4 54 | 55 | ## 0.0.2 56 | 57 | ### Patch Changes 58 | 59 | - aca2662: DragOverlay 60 | - Updated dependencies [aca2662] 61 | - @dnd-kit-vue/core@0.0.2 62 | - @dnd-kit-vue/utilities@0.0.2 63 | 64 | ## 0.0.1 65 | 66 | ### Patch Changes 67 | 68 | - 98942bd: first version 69 | - Updated dependencies [98942bd] 70 | - @dnd-kit-vue/core@0.0.1 71 | - @dnd-kit-vue/utilities@0.0.1 72 | -------------------------------------------------------------------------------- /packages/sortable/test/demo1/SortableItem.tsx: -------------------------------------------------------------------------------- 1 | import {useSortable} from '@dnd-kit-vue/sortable'; 2 | import {CSS} from '@dnd-kit-vue/utilities'; 3 | import styles from './styles.module.css' 4 | 5 | 6 | import { 7 | defineComponent, 8 | ref, 9 | h, 10 | Fragment, 11 | useSlots, 12 | VNodeRef, 13 | CSSProperties, 14 | getCurrentInstance, 15 | inject, 16 | computed 17 | } from 'vue' 18 | import type {Arguments} from "@dnd-kit-vue/sortable"; 19 | 20 | interface SortableItemProps { 21 | id: number | string, 22 | } 23 | 24 | export const vuePropsType = { 25 | id: [String, Number] 26 | } 27 | 28 | const SortableItem = defineComponent((props, {}) => { 29 | 30 | const slots = useSlots() 31 | 32 | const params:Arguments = {id: computed(()=>props.id) as any} 33 | const { 34 | attributes, 35 | listeners, 36 | setNodeRef, 37 | transform, 38 | transition, 39 | } = useSortable(params); 40 | 41 | return () => { 42 | const style:CSSProperties = { 43 | transform: CSS.Transform.toString(transform.value), 44 | transition:transition.value, 45 | }; 46 | 47 | return ( 48 |
49 |
50 | {/* ... */} 51 | {props.id} 52 |
53 |
54 | ); 55 | } 56 | }) 57 | 58 | SortableItem.props = vuePropsType 59 | SortableItem.name = 'SortableItem' 60 | 61 | export default SortableItem 62 | 63 | -------------------------------------------------------------------------------- /packages/core/CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # @dnd-kit-vue/core 2 | 3 | ## 0.1.2 4 | 5 | ### Patch Changes 6 | 7 | - ff2942d: fix 0.1.1 ts5.5.1 8 | - Updated dependencies [ff2942d] 9 | - @dnd-kit-vue/accessibility@0.1.2 10 | - @dnd-kit-vue/utilities@0.1.2 11 | 12 | ## 0.1.1 13 | 14 | ### Patch Changes 15 | 16 | - 23dd1f2: fix 0.1.0 17 | - Updated dependencies [23dd1f2] 18 | - @dnd-kit-vue/accessibility@0.1.1 19 | - @dnd-kit-vue/utilities@0.1.1 20 | 21 | ## 0.1.0 22 | 23 | ### Minor Changes 24 | 25 | - f221a5c: vue 3.4 26 | 27 | ### Patch Changes 28 | 29 | - Updated dependencies [f221a5c] 30 | - @dnd-kit-vue/accessibility@0.1.0 31 | - @dnd-kit-vue/utilities@0.1.0 32 | 33 | ## 0.0.5 34 | 35 | ### Patch Changes 36 | 37 | - 30fce38: scrollableAncestorRects 38 | - Updated dependencies [30fce38] 39 | - @dnd-kit-vue/accessibility@0.0.5 40 | - @dnd-kit-vue/utilities@0.0.5 41 | 42 | ## 0.0.4 43 | 44 | ### Patch Changes 45 | 46 | - fdf35bb: fix build 47 | - 7c16d6d: a 48 | - 7ae3e8f: 合并 49 | - Updated dependencies [fdf35bb] 50 | - Updated dependencies [7c16d6d] 51 | - Updated dependencies [7ae3e8f] 52 | - @dnd-kit-vue/accessibility@0.0.4 53 | - @dnd-kit-vue/utilities@0.0.4 54 | 55 | ## 0.0.2 56 | 57 | ### Patch Changes 58 | 59 | - aca2662: DragOverlay 60 | - Updated dependencies [aca2662] 61 | - @dnd-kit-vue/accessibility@0.0.2 62 | - @dnd-kit-vue/utilities@0.0.2 63 | 64 | ## 0.0.1 65 | 66 | ### Patch Changes 67 | 68 | - 98942bd: first version 69 | - Updated dependencies [98942bd] 70 | - @dnd-kit-vue/accessibility@0.0.1 71 | - @dnd-kit-vue/utilities@0.0.1 72 | -------------------------------------------------------------------------------- /packages/core/src/utilities/rect/Rect.ts: -------------------------------------------------------------------------------- 1 | import type {ClientRect} from '../../types/rect'; 2 | import { 3 | getScrollableAncestors, 4 | getScrollOffsets, 5 | getScrollXOffset, 6 | getScrollYOffset, 7 | } from '../scroll'; 8 | 9 | const properties = [ 10 | ['x', ['left', 'right'], getScrollXOffset], 11 | ['y', ['top', 'bottom'], getScrollYOffset], 12 | ] as const; 13 | 14 | export class Rect { 15 | constructor(rect: ClientRect, element: Element) { 16 | const scrollableAncestors = getScrollableAncestors(element); 17 | const scrollOffsets = getScrollOffsets(scrollableAncestors); 18 | 19 | this.rect = {...rect}; 20 | this.width = rect.width; 21 | this.height = rect.height; 22 | 23 | for (const [axis, keys, getScrollOffset] of properties) { 24 | for (const key of keys) { 25 | Object.defineProperty(this, key, { 26 | get: () => { 27 | const currentOffsets = getScrollOffset(scrollableAncestors); 28 | const scrollOffsetsDeltla = scrollOffsets[axis] - currentOffsets; 29 | 30 | return this.rect[key] + scrollOffsetsDeltla; 31 | }, 32 | enumerable: true, 33 | }); 34 | } 35 | } 36 | 37 | Object.defineProperty(this, 'rect', {enumerable: false}); 38 | } 39 | 40 | private rect: ClientRect; 41 | 42 | public width: number; 43 | 44 | public height: number; 45 | 46 | // The below properties are set by the `Object.defineProperty` calls in the constructor 47 | // @ts-ignore 48 | public top: number; 49 | // @ts-ignore 50 | public bottom: number; 51 | // @ts-ignore 52 | public right: number; 53 | // @ts-ignore 54 | public left: number; 55 | } 56 | -------------------------------------------------------------------------------- /packages/core/src/hooks/utilities/useDragOverlayMeasuring.ts: -------------------------------------------------------------------------------- 1 | 2 | import {isHTMLElement, useNodeRef} from '@dnd-kit-vue/utilities'; 3 | 4 | import {useResizeObserver} from './useResizeObserver'; 5 | import {getMeasurableNode} from '../../utilities/nodes'; 6 | import type {PublicContextDescriptor} from '../../store'; 7 | import type {ClientRect} from '../../types'; 8 | import {computed, type ComputedRef, type Ref, ref} from "vue"; 9 | 10 | interface Arguments { 11 | measure(element: HTMLElement): ClientRect; 12 | } 13 | 14 | export function useDragOverlayMeasuring({ 15 | measure, 16 | }: Arguments): ComputedRef<{ rect: Ref; setRef: any; nodeRef: any }> { 17 | const rect = ref(null); 18 | const handleResize = (entries: ResizeObserverEntry[]) => { 19 | for (const {target} of entries) { 20 | if (isHTMLElement(target)) { 21 | const newRect = measure(target); 22 | 23 | rect.value = rect.value 24 | ? {...rect.value, width: newRect.width, height: newRect.height} 25 | : newRect; 26 | break; 27 | } 28 | } 29 | }; 30 | const resizeObserver = useResizeObserver({callback: handleResize}); 31 | const handleNodeChange = (element: any) => { 32 | const node = getMeasurableNode(element); 33 | 34 | resizeObserver.value?.disconnect(); 35 | 36 | if (node) { 37 | resizeObserver.value?.observe(node); 38 | } 39 | rect.value = node ? measure(node) : null 40 | }; 41 | 42 | const [nodeRef, setRef] = useNodeRef(handleNodeChange); 43 | 44 | 45 | return computed( 46 | () => ({ 47 | nodeRef, 48 | rect, 49 | setRef, 50 | }) ); 51 | } 52 | -------------------------------------------------------------------------------- /src/style.css: -------------------------------------------------------------------------------- 1 | :root { 2 | font-family: Inter, Avenir, Helvetica, Arial, sans-serif; 3 | font-size: 16px; 4 | line-height: 24px; 5 | font-weight: 400; 6 | 7 | color-scheme: light dark; 8 | color: rgba(255, 255, 255, 0.87); 9 | background-color: #242424; 10 | 11 | font-synthesis: none; 12 | text-rendering: optimizeLegibility; 13 | -webkit-font-smoothing: antialiased; 14 | -moz-osx-font-smoothing: grayscale; 15 | -webkit-text-size-adjust: 100%; 16 | } 17 | 18 | a { 19 | font-weight: 500; 20 | color: #646cff; 21 | text-decoration: inherit; 22 | } 23 | a:hover { 24 | color: #535bf2; 25 | } 26 | 27 | body { 28 | margin: 0; 29 | display: flex; 30 | place-items: center; 31 | min-width: 320px; 32 | min-height: 100vh; 33 | } 34 | 35 | h1 { 36 | font-size: 3.2em; 37 | line-height: 1.1; 38 | } 39 | 40 | button { 41 | border-radius: 8px; 42 | border: 1px solid transparent; 43 | padding: 0.6em 1.2em; 44 | font-size: 1em; 45 | font-weight: 500; 46 | font-family: inherit; 47 | background-color: #1a1a1a; 48 | cursor: pointer; 49 | transition: border-color 0.25s; 50 | } 51 | button:hover { 52 | border-color: #646cff; 53 | } 54 | button:focus, 55 | button:focus-visible { 56 | outline: 4px auto -webkit-focus-ring-color; 57 | } 58 | 59 | .card { 60 | padding: 2em; 61 | } 62 | 63 | #app { 64 | max-width: 1280px; 65 | margin: 0 auto; 66 | padding: 2rem; 67 | text-align: center; 68 | } 69 | 70 | @media (prefers-color-scheme: light) { 71 | :root { 72 | color: #213547; 73 | background-color: #ffffff; 74 | } 75 | a:hover { 76 | color: #747bff; 77 | } 78 | button { 79 | background-color: #f9f9f9; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /packages/core/src/store/context.ts: -------------------------------------------------------------------------------- 1 | import {noop} from '../utilities/other'; 2 | import {defaultMeasuringConfiguration} from '../components/DndContext/defaults'; 3 | import {DroppableContainersMap} from './constructors'; 4 | import type {InternalContextDescriptor, PublicContextDescriptor} from './types'; 5 | import InternalContextProvider from "../CreateContextVueVNode/InternalContextProvider"; 6 | import PublicContextProvider from "../CreateContextVueVNode/PublicContextProvider"; 7 | import {ref} from "vue"; 8 | 9 | export const defaultPublicContext: PublicContextDescriptor = { 10 | activatorEvent: null, 11 | active: null, 12 | activeNode: null, 13 | activeNodeRect: null, 14 | collisions: null, 15 | containerNodeRect: null, 16 | draggableNodes: new Map(), 17 | droppableRects: new Map(), 18 | droppableContainers: new DroppableContainersMap(), 19 | over: null, 20 | dragOverlay: { 21 | nodeRef: ref(null), 22 | rect: ref(null), 23 | setRef: noop, 24 | }, 25 | scrollableAncestors: [], 26 | scrollableAncestorRects: [], 27 | measuringConfiguration: defaultMeasuringConfiguration, 28 | measureDroppableContainers: noop, 29 | windowRect: null, 30 | measuringScheduled: false, 31 | }; 32 | 33 | export const defaultInternalContext: InternalContextDescriptor = { 34 | activatorEvent: null, 35 | activators: [], 36 | active: null, 37 | activeNodeRect: null, 38 | ariaDescribedById: { 39 | draggable: '', 40 | }, 41 | dispatch: noop, 42 | draggableNodes: new Map(), 43 | over: null, 44 | measureDroppableContainers: noop, 45 | }; 46 | 47 | export const InternalContext = { 48 | Provider: InternalContextProvider 49 | }; 50 | 51 | export const PublicContext = { 52 | Provider: PublicContextProvider 53 | }; 54 | -------------------------------------------------------------------------------- /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: publish 4 | 5 | on: 6 | push: 7 | branches: [ main ] 8 | pull_request: 9 | branches: [ main ] 10 | 11 | workflow_dispatch: 12 | 13 | jobs: 14 | test: 15 | runs-on: ubuntu-latest 16 | steps: 17 | - uses: actions/checkout@v3 18 | - uses: actions/setup-node@v3 19 | with: 20 | node-version: '20' 21 | registry-url: 'https://registry.npmjs.org' 22 | 23 | - name: Run install pnpm 24 | run: npm i -g pnpm 25 | 26 | - name: Run install 27 | run: pnpm install --no-frozen-lockfile 28 | 29 | - name: Run build 30 | run: pnpm build:all 31 | 32 | - name: Run test 33 | run: pnpm test:unit 34 | - name: Run codecov 35 | run: npx codecov --token=${{ secrets.CODECOV_TOKEN }} 36 | 37 | - name: Run publish 38 | run: pnpm changeset version 39 | 40 | - name: Run publish 41 | run: pnpm changeset publish 42 | env: 43 | NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} 44 | 45 | - name: get version 46 | run: echo "RELEASE_VERSION="v$(node ./script/version.js)"" >> $GITHUB_ENV 47 | 48 | - name: echo version 49 | run: echo "${{ env.RELEASE_VERSION }}" 50 | 51 | - name: Commit files 52 | run: | 53 | git config --local user.email "kousumwork@outlook.com" 54 | git config --local user.name "rashagu" 55 | git add . 56 | git commit -m "[github action]: ${{ env.RELEASE_VERSION }}" 57 | 58 | - name: Push changes 59 | uses: ad-m/github-push-action@master 60 | with: 61 | github_token: ${{ secrets.PUSH_TOKEN }} 62 | branch: ${{ github.ref }} 63 | tags: true 64 | -------------------------------------------------------------------------------- /packages/core/src/sensors/touch/TouchSensor.ts: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | AbstractPointerSensor, 4 | type PointerSensorProps, 5 | type PointerEventHandlers, 6 | type PointerSensorOptions, 7 | } from '../pointer'; 8 | import type {SensorProps} from '../types'; 9 | 10 | const events: PointerEventHandlers = { 11 | move: {name: 'touchmove'}, 12 | end: {name: 'touchend'}, 13 | }; 14 | 15 | export interface TouchSensorOptions extends PointerSensorOptions {} 16 | 17 | export type TouchSensorProps = SensorProps; 18 | 19 | export class TouchSensor extends AbstractPointerSensor { 20 | constructor(props: PointerSensorProps) { 21 | super(props, events); 22 | } 23 | 24 | static activators = [ 25 | { 26 | eventName: 'onTouchstart' as const, 27 | handler: ( 28 | event: TouchEvent, 29 | {onActivation}: TouchSensorOptions 30 | ) => { 31 | const {touches} = event; 32 | 33 | if (touches.length > 1) { 34 | return false; 35 | } 36 | 37 | onActivation?.({event}); 38 | 39 | return true; 40 | }, 41 | }, 42 | ]; 43 | 44 | static setup() { 45 | // Adding a non-capture and non-passive `touchmove` listener in order 46 | // to force `event.preventDefault()` calls to work in dynamically added 47 | // touchmove event handlers. This is required for iOS Safari. 48 | window.addEventListener(events.move.name, noop, { 49 | capture: false, 50 | passive: false, 51 | }); 52 | 53 | return function teardown() { 54 | window.removeEventListener(events.move.name, noop); 55 | }; 56 | 57 | // We create a new handler because the teardown function of another sensor 58 | // could remove our event listener if we use a referentially equal listener. 59 | function noop() {} 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /src/components/WelcomeItem.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 88 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dnd-kit-vue", 3 | "version": "0.0.0", 4 | "private": true, 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "preview": "vite preview", 9 | "test:unit": "vitest", 10 | "build:all": "pnpm -C ./packages/accessibility build && pnpm -C ./packages/core build && pnpm -C ./packages/modifiers build && pnpm -C ./packages/sortable build && pnpm -C ./packages/utilities build", 11 | "type-check": "vue-tsc --build --force", 12 | "lint": "eslint . --ext .vue,.js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore", 13 | "format": "prettier --write src/", 14 | "change": "pnpm changeset", 15 | "version": "pnpm changeset version", 16 | "publish": "pnpm changeset publish" 17 | }, 18 | "dependencies": { 19 | "@changesets/cli": "^2.27.5", 20 | "@dnd-kit-vue/accessibility": "workspace:*", 21 | "@dnd-kit-vue/core": "workspace:*", 22 | "@dnd-kit-vue/modifiers": "workspace:*", 23 | "@dnd-kit-vue/sortable": "workspace:*", 24 | "@dnd-kit-vue/utilities": "workspace:*", 25 | "vue": "^3.4.27" 26 | }, 27 | "devDependencies": { 28 | "@rushstack/eslint-patch": "^1.8.0", 29 | "@tsconfig/node20": "^20.1.4", 30 | "@types/jsdom": "^21.1.6", 31 | "@types/node": "^20.12.5", 32 | "@vitejs/plugin-vue": "^5.0.5", 33 | "@vitejs/plugin-vue-jsx": "^4.0.0", 34 | "@vue/eslint-config-prettier": "^9.0.0", 35 | "@vue/eslint-config-typescript": "^13.0.0", 36 | "@vue/test-utils": "^2.4.5", 37 | "@vue/tsconfig": "^0.5.1", 38 | "eslint": "^8.57.0", 39 | "eslint-plugin-vue": "^9.23.0", 40 | "jsdom": "^24.0.0", 41 | "npm-run-all2": "^6.1.2", 42 | "prettier": "^3.2.5", 43 | "typescript": "^5.5.1-rc", 44 | "vite": "^5.2.13", 45 | "vite-plugin-vue-devtools": "^7.0.25", 46 | "vitest": "^1.4.0", 47 | "vue-tsc": "^2.0.21" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /packages/core/src/utilities/scroll/getScrollableAncestors.ts: -------------------------------------------------------------------------------- 1 | import { 2 | getWindow, 3 | isDocument, 4 | isHTMLElement, 5 | isSVGElement, 6 | } from '@dnd-kit-vue/utilities'; 7 | 8 | import {isFixed} from './isFixed'; 9 | import {isScrollable} from './isScrollable'; 10 | 11 | export function getScrollableAncestors( 12 | element: Node | null, 13 | limit?: number 14 | ): Element[] { 15 | const scrollParents: Element[] = []; 16 | function findScrollableAncestors(node: Node | null): Element[] { 17 | if (limit != null && scrollParents.length >= limit) { 18 | return scrollParents; 19 | } 20 | 21 | if (!node) { 22 | return scrollParents; 23 | } 24 | 25 | 26 | if ( 27 | isDocument(node) && 28 | node.scrollingElement != null && 29 | !scrollParents.includes(node.scrollingElement) 30 | ) { 31 | scrollParents.push(node.scrollingElement); 32 | 33 | return scrollParents; 34 | } 35 | 36 | if (!isHTMLElement(node) || isSVGElement(node)) { 37 | return scrollParents; 38 | } 39 | 40 | if (scrollParents.includes(node)) { 41 | return scrollParents; 42 | } 43 | 44 | const {getComputedStyle} = getWindow(node); 45 | const computedStyle = getComputedStyle(node); 46 | 47 | if (node !== element) { 48 | if (isScrollable(node, computedStyle)) { 49 | scrollParents.push(node); 50 | } 51 | } 52 | 53 | if (isFixed(node, computedStyle)) { 54 | return scrollParents; 55 | } 56 | 57 | return findScrollableAncestors(node.parentNode); 58 | } 59 | 60 | if (!element) { 61 | return scrollParents; 62 | } 63 | 64 | return findScrollableAncestors(element); 65 | } 66 | 67 | export function getFirstScrollableAncestor(node: Node | null): Element | null { 68 | const [firstScrollableAncestor] = getScrollableAncestors(node, 1); 69 | 70 | return firstScrollableAncestor ?? null; 71 | } 72 | -------------------------------------------------------------------------------- /packages/core/src/hooks/utilities/useRects.ts: -------------------------------------------------------------------------------- 1 | 2 | import {getWindow, useIsomorphicLayoutEffect} from '@dnd-kit-vue/utilities'; 3 | 4 | import type {ClientRect} from '../../types'; 5 | import {Rect, getClientRect} from '../../utilities/rect'; 6 | import {isDocumentScrollingElement, useReducer} from '../../utilities'; 7 | 8 | import {useResizeObserver} from './useResizeObserver'; 9 | import {useWindowRect} from './useWindowRect'; 10 | import { computed, type ComputedRef, ref, type ShallowRef, watch } from 'vue' 11 | 12 | const defaultValue: Rect[] = []; 13 | 14 | export function useRects( 15 | elements: ShallowRef, 16 | measure: (element: Element) => ClientRect = getClientRect 17 | ): ClientRect[] { 18 | const useWindowRectOption = computed(()=>{ 19 | const [firstElement] = elements.value; 20 | return firstElement ? getWindow(firstElement) : null 21 | }) 22 | const windowRect = useWindowRect( 23 | useWindowRectOption 24 | ); 25 | 26 | const [rects, measureRects] = useReducer(reducer, defaultValue); 27 | const resizeObserver = useResizeObserver({callback: measureRects}); 28 | 29 | 30 | 31 | 32 | watch([()=>elements.value], () => { 33 | 34 | if (elements.value.length > 0 && rects.value === defaultValue) { 35 | measureRects(); 36 | } 37 | 38 | 39 | if (elements.value.length) { 40 | elements.value.forEach((element) => resizeObserver.value?.observe(element)); 41 | } else { 42 | resizeObserver.value?.disconnect(); 43 | measureRects(); 44 | } 45 | 46 | 47 | 48 | }, {immediate: true}); 49 | 50 | return rects.value; 51 | 52 | function reducer() { 53 | 54 | if (!elements.value.length) { 55 | return defaultValue; 56 | } 57 | 58 | return elements.value.map((element) => 59 | isDocumentScrollingElement(element) 60 | ? (windowRect.value as ClientRect) 61 | : new Rect(measure(element), element) 62 | ); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/icons/IconEcosystem.vue: -------------------------------------------------------------------------------- 1 | 8 | -------------------------------------------------------------------------------- /packages/core/src/utilities/algorithms/pointerWithin.ts: -------------------------------------------------------------------------------- 1 | import type {Coordinates, ClientRect} from '../../types'; 2 | import {distanceBetween} from '../coordinates'; 3 | 4 | import type {CollisionDescriptor, CollisionDetection} from './types'; 5 | import {cornersOfRectangle, sortCollisionsAsc} from './helpers'; 6 | 7 | /** 8 | * Check if a given point is contained within a bounding rectangle 9 | */ 10 | function isPointWithinRect(point: Coordinates, rect: ClientRect): boolean { 11 | const {top, left, bottom, right} = rect; 12 | 13 | return ( 14 | top <= point.y && point.y <= bottom && left <= point.x && point.x <= right 15 | ); 16 | } 17 | 18 | /** 19 | * Returns the rectangles that the pointer is hovering over 20 | */ 21 | export const pointerWithin: CollisionDetection = ({ 22 | droppableContainers, 23 | droppableRects, 24 | pointerCoordinates, 25 | }) => { 26 | if (!pointerCoordinates) { 27 | return []; 28 | } 29 | 30 | const collisions: CollisionDescriptor[] = []; 31 | 32 | for (const droppableContainer of droppableContainers) { 33 | const {id} = droppableContainer; 34 | const rect = droppableRects.get(id); 35 | 36 | if (rect && isPointWithinRect(pointerCoordinates, rect)) { 37 | /* There may be more than a single rectangle intersecting 38 | * with the pointer coordinates. In order to sort the 39 | * colliding rectangles, we measure the distance between 40 | * the pointer and the corners of the intersecting rectangle 41 | */ 42 | const corners = cornersOfRectangle(rect); 43 | const distances = corners.reduce((accumulator, corner) => { 44 | return accumulator + distanceBetween(pointerCoordinates, corner); 45 | }, 0); 46 | const effectiveDistance = Number((distances / 4).toFixed(4)); 47 | 48 | collisions.push({ 49 | id, 50 | data: {droppableContainer, value: effectiveDistance}, 51 | }); 52 | } 53 | } 54 | 55 | return collisions.sort(sortCollisionsAsc); 56 | }; 57 | -------------------------------------------------------------------------------- /packages/core/src/components/Accessibility/components/RestoreFocus.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { 3 | findFirstFocusableNode, 4 | isKeyboardEvent, 5 | usePrevious, 6 | } from '@dnd-kit-vue/utilities'; 7 | 8 | import {defaultInternalContext, InternalContext, type InternalContextDescriptor} from '../../../store'; 9 | import {inject, ref, watchEffect} from "vue"; 10 | 11 | interface Props { 12 | disabled: boolean; 13 | } 14 | 15 | export function RestoreFocus({disabled}: Props) { 16 | const {active, activatorEvent, draggableNodes} = inject('InternalContext', ref(defaultInternalContext)).value 17 | const previousActivatorEvent = usePrevious(activatorEvent); 18 | const previousActiveId = usePrevious(active?.id); 19 | 20 | // Restore keyboard focus on the activator node 21 | watchEffect(() => { 22 | if (disabled) { 23 | return; 24 | } 25 | 26 | if (!activatorEvent && previousActivatorEvent.value && previousActiveId.value != null) { 27 | if (!isKeyboardEvent(previousActivatorEvent.value)) { 28 | return; 29 | } 30 | 31 | if (document.activeElement === previousActivatorEvent.value.target) { 32 | // No need to restore focus 33 | return; 34 | } 35 | 36 | const draggableNode = draggableNodes.get(previousActiveId.value); 37 | 38 | if (!draggableNode) { 39 | return; 40 | } 41 | 42 | const {activatorNode, node} = draggableNode; 43 | 44 | if (!activatorNode.current && !node.current) { 45 | return; 46 | } 47 | 48 | requestAnimationFrame(() => { 49 | for (const element of [activatorNode.current, node.current]) { 50 | if (!element) { 51 | continue; 52 | } 53 | 54 | const focusableNode = findFirstFocusableNode(element); 55 | 56 | if (focusableNode) { 57 | focusableNode.focus(); 58 | break; 59 | } 60 | } 61 | }); 62 | } 63 | }); 64 | 65 | return null; 66 | } 67 | -------------------------------------------------------------------------------- /packages/sortable/src/hooks/utilities/useDerivedTransform.ts: -------------------------------------------------------------------------------- 1 | 2 | import {getClientRect, type ClientRect} from '@dnd-kit-vue/core'; 3 | import {type Transform, useIsomorphicLayoutEffect} from '@dnd-kit-vue/utilities'; 4 | import {type ComputedRef, type Ref, ref, watch, watchEffect} from "vue"; 5 | 6 | interface Arguments { 7 | rect: Ref; 8 | disabled: Ref; 9 | index: Ref; 10 | node: Ref; 11 | } 12 | 13 | /* 14 | * When the index of an item changes while sorting, 15 | * we need to temporarily disable the transforms 16 | */ 17 | export function useDerivedTransform({disabled, index, node, rect}: Arguments) { 18 | 19 | const derivedTransform = ref(null) 20 | function setDerivedtransform(val:any) { 21 | derivedTransform.value = val 22 | } 23 | 24 | const previousIndex = ref(index.value); 25 | 26 | watch([disabled, index, node, rect], (value, oldValue, onCleanup) => { 27 | 28 | // console.error(index.value, previousIndex.value) 29 | if (disabled.value && index.value !== previousIndex.value && node.value) { 30 | const initial = rect.value; 31 | 32 | if (initial && node.value) { 33 | const current = getClientRect(node.value, { 34 | ignoreTransform: true, 35 | }); 36 | 37 | const delta = { 38 | x: initial.left - current.left, 39 | y: initial.top - current.top, 40 | scaleX: initial.width / current.width, 41 | scaleY: initial.height / current.height, 42 | }; 43 | 44 | if (delta.x || delta.y) { 45 | setDerivedtransform(delta); 46 | } 47 | } 48 | } 49 | 50 | if (index.value !== previousIndex.value) { 51 | previousIndex.value = index.value; 52 | } 53 | 54 | }, {immediate: true}); 55 | 56 | watch(derivedTransform, (value, oldValue, onCleanup) => { 57 | if (derivedTransform.value) { 58 | requestAnimationFrame(() => { 59 | setDerivedtransform(null); 60 | }); 61 | } 62 | }, {immediate: true}); 63 | 64 | return derivedTransform; 65 | } 66 | -------------------------------------------------------------------------------- /packages/core/src/utilities/algorithms/helpers.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-redeclare */ 2 | import type {ClientRect} from '../../types'; 3 | 4 | import type {Collision, CollisionDescriptor} from './types'; 5 | 6 | 7 | 8 | /** 9 | * Sort collisions from smallest to greatest value 10 | */ 11 | export function sortCollisionsAsc( 12 | {data: {value: a}}: CollisionDescriptor, 13 | {data: {value: b}}: CollisionDescriptor 14 | ) { 15 | return a - b; 16 | } 17 | 18 | /** 19 | * Sort collisions from greatest to smallest value 20 | */ 21 | export function sortCollisionsDesc( 22 | {data: {value: a}}: CollisionDescriptor, 23 | {data: {value: b}}: CollisionDescriptor 24 | ) { 25 | return b - a; 26 | } 27 | 28 | /** 29 | * Returns the coordinates of the corners of a given rectangle: 30 | * [TopLeft {x, y}, TopRight {x, y}, BottomLeft {x, y}, BottomRight {x, y}] 31 | */ 32 | export function cornersOfRectangle({left, top, height, width}: ClientRect) { 33 | return [ 34 | { 35 | x: left, 36 | y: top, 37 | }, 38 | { 39 | x: left + width, 40 | y: top, 41 | }, 42 | { 43 | x: left, 44 | y: top + height, 45 | }, 46 | { 47 | x: left + width, 48 | y: top + height, 49 | }, 50 | ]; 51 | } 52 | 53 | /** 54 | * Returns the first collision, or null if there isn't one. 55 | * If a property is specified, returns the specified property of the first collision. 56 | */ 57 | export function getFirstCollision( 58 | collisions: Collision[] | null | undefined, 59 | ): Collision | null; 60 | 61 | export function getFirstCollision( 62 | collisions: Collision[] | null | undefined, 63 | property: T 64 | ): Collision[T] | null; 65 | 66 | export function getFirstCollision( 67 | collisions: Collision[] | null | undefined, 68 | property?: keyof Collision 69 | ) { 70 | 71 | 72 | if (!collisions || collisions.length === 0) { 73 | return null; 74 | } 75 | 76 | const [firstCollision] = collisions; 77 | 78 | return property ? firstCollision[property] : firstCollision; 79 | } 80 | -------------------------------------------------------------------------------- /packages/core/src/utilities/algorithms/rectIntersection.ts: -------------------------------------------------------------------------------- 1 | import type {ClientRect} from '../../types'; 2 | 3 | import type {CollisionDescriptor, CollisionDetection} from './types'; 4 | import {sortCollisionsDesc} from './helpers'; 5 | 6 | /** 7 | * Returns the intersecting rectangle area between two rectangles 8 | */ 9 | export function getIntersectionRatio( 10 | entry: ClientRect, 11 | target: ClientRect 12 | ): number { 13 | const top = Math.max(target.top, entry.top); 14 | const left = Math.max(target.left, entry.left); 15 | const right = Math.min(target.left + target.width, entry.left + entry.width); 16 | const bottom = Math.min(target.top + target.height, entry.top + entry.height); 17 | const width = right - left; 18 | const height = bottom - top; 19 | 20 | if (left < right && top < bottom) { 21 | const targetArea = target.width * target.height; 22 | const entryArea = entry.width * entry.height; 23 | const intersectionArea = width * height; 24 | const intersectionRatio = 25 | intersectionArea / (targetArea + entryArea - intersectionArea); 26 | 27 | return Number(intersectionRatio.toFixed(4)); 28 | } 29 | 30 | // Rectangles do not overlap, or overlap has an area of zero (edge/corner overlap) 31 | return 0; 32 | } 33 | 34 | /** 35 | * Returns the rectangles that has the greatest intersection area with a given 36 | * rectangle in an array of rectangles. 37 | */ 38 | export const rectIntersection: CollisionDetection = ({ 39 | collisionRect, 40 | droppableRects, 41 | droppableContainers, 42 | }) => { 43 | const collisions: CollisionDescriptor[] = []; 44 | 45 | for (const droppableContainer of droppableContainers) { 46 | const {id} = droppableContainer; 47 | const rect = droppableRects.get(id); 48 | 49 | 50 | if (rect) { 51 | const intersectionRatio = getIntersectionRatio(rect, collisionRect); 52 | 53 | if (intersectionRatio > 0) { 54 | collisions.push({ 55 | id, 56 | data: {droppableContainer, value: intersectionRatio}, 57 | }); 58 | } 59 | } 60 | } 61 | 62 | return collisions.sort(sortCollisionsDesc); 63 | }; 64 | -------------------------------------------------------------------------------- /packages/core/src/components/Accessibility/Accessibility.tsx: -------------------------------------------------------------------------------- 1 | import {useUniqueId} from '@dnd-kit-vue/utilities'; 2 | import {HiddenText, LiveRegion, useAnnouncement} from '@dnd-kit-vue/accessibility'; 3 | 4 | import {useDndMonitor} from '../DndMonitor'; 5 | 6 | import type {Announcements, ScreenReaderInstructions} from './types'; 7 | import { 8 | defaultAnnouncements, 9 | defaultScreenReaderInstructions, 10 | } from './defaults'; 11 | import {ref, watchEffect, Teleport, h, Fragment} from "vue"; 12 | 13 | interface Props { 14 | announcements?: Announcements; 15 | container?: Element; 16 | screenReaderInstructions?: ScreenReaderInstructions; 17 | hiddenTextDescribedById: string; 18 | } 19 | 20 | export function Accessibility({ 21 | announcements = defaultAnnouncements, 22 | container, 23 | hiddenTextDescribedById, 24 | screenReaderInstructions = defaultScreenReaderInstructions, 25 | }: Props) { 26 | const {announce, announcement} = useAnnouncement(); 27 | const liveRegionId = useUniqueId(`DndLiveRegion`); 28 | const mounted = ref(false); 29 | 30 | watchEffect(() => { 31 | mounted.value = true 32 | }); 33 | 34 | useDndMonitor({ 35 | onDragStart({active}) { 36 | announce(announcements.onDragStart({active})); 37 | }, 38 | onDragMove({active, over}) { 39 | if (announcements.onDragMove) { 40 | announce(announcements.onDragMove({active, over})); 41 | } 42 | }, 43 | onDragOver({active, over}) { 44 | announce(announcements.onDragOver({active, over})); 45 | }, 46 | onDragEnd({active, over}) { 47 | announce(announcements.onDragEnd({active, over})); 48 | }, 49 | onDragCancel({active, over}) { 50 | announce(announcements.onDragCancel({active, over})); 51 | }, 52 | }); 53 | 54 | if (!mounted) { 55 | return null; 56 | } 57 | 58 | const markup = ( 59 | 60 | 64 | 65 | 66 | ); 67 | 68 | 69 | return container ? {markup} : markup; 70 | } 71 | -------------------------------------------------------------------------------- /packages/core/src/utilities/scroll/getScrollDirectionAndSpeed.ts: -------------------------------------------------------------------------------- 1 | import {Direction, type ClientRect} from '../../types'; 2 | import {getScrollPosition} from './getScrollPosition'; 3 | 4 | interface PositionalCoordinates 5 | extends Pick {} 6 | 7 | const defaultThreshold = { 8 | x: 0.2, 9 | y: 0.2, 10 | }; 11 | 12 | export function getScrollDirectionAndSpeed( 13 | scrollContainer: Element, 14 | scrollContainerRect: ClientRect, 15 | {top, left, right, bottom}: PositionalCoordinates, 16 | acceleration = 10, 17 | thresholdPercentage = defaultThreshold 18 | ) { 19 | const {isTop, isBottom, isLeft, isRight} = getScrollPosition(scrollContainer); 20 | 21 | const direction = { 22 | x: 0, 23 | y: 0, 24 | }; 25 | const speed = { 26 | x: 0, 27 | y: 0, 28 | }; 29 | const threshold = { 30 | height: scrollContainerRect.height * thresholdPercentage.y, 31 | width: scrollContainerRect.width * thresholdPercentage.x, 32 | }; 33 | 34 | if (!isTop && top <= scrollContainerRect.top + threshold.height) { 35 | // Scroll Up 36 | direction.y = Direction.Backward; 37 | speed.y = 38 | acceleration * 39 | Math.abs( 40 | (scrollContainerRect.top + threshold.height - top) / threshold.height 41 | ); 42 | } else if ( 43 | !isBottom && 44 | bottom >= scrollContainerRect.bottom - threshold.height 45 | ) { 46 | // Scroll Down 47 | direction.y = Direction.Forward; 48 | speed.y = 49 | acceleration * 50 | Math.abs( 51 | (scrollContainerRect.bottom - threshold.height - bottom) / 52 | threshold.height 53 | ); 54 | } 55 | 56 | if (!isRight && right >= scrollContainerRect.right - threshold.width) { 57 | // Scroll Right 58 | direction.x = Direction.Forward; 59 | speed.x = 60 | acceleration * 61 | Math.abs( 62 | (scrollContainerRect.right - threshold.width - right) / threshold.width 63 | ); 64 | } else if (!isLeft && left <= scrollContainerRect.left + threshold.width) { 65 | // Scroll Left 66 | direction.x = Direction.Backward; 67 | speed.x = 68 | acceleration * 69 | Math.abs( 70 | (scrollContainerRect.left + threshold.width - left) / threshold.width 71 | ); 72 | } 73 | 74 | return { 75 | direction, 76 | speed, 77 | }; 78 | } 79 | -------------------------------------------------------------------------------- /packages/sortable/src/strategies/horizontalListSorting.ts: -------------------------------------------------------------------------------- 1 | import type {ClientRect} from '@dnd-kit-vue/core'; 2 | import type {SortingStrategy} from '../types'; 3 | 4 | // To-do: We should be calculating scale transformation 5 | const defaultScale = { 6 | scaleX: 1, 7 | scaleY: 1, 8 | }; 9 | 10 | export const horizontalListSortingStrategy: SortingStrategy = ({ 11 | rects, 12 | activeNodeRect: fallbackActiveRect, 13 | activeIndex, 14 | overIndex, 15 | index, 16 | }) => { 17 | const activeNodeRect = rects[activeIndex] ?? fallbackActiveRect; 18 | 19 | if (!activeNodeRect) { 20 | return null; 21 | } 22 | 23 | const itemGap = getItemGap(rects, index, activeIndex); 24 | 25 | if (index === activeIndex) { 26 | const newIndexRect = rects[overIndex]; 27 | 28 | if (!newIndexRect) { 29 | return null; 30 | } 31 | 32 | return { 33 | x: 34 | activeIndex < overIndex 35 | ? newIndexRect.left + 36 | newIndexRect.width - 37 | (activeNodeRect.left + activeNodeRect.width) 38 | : newIndexRect.left - activeNodeRect.left, 39 | y: 0, 40 | ...defaultScale, 41 | }; 42 | } 43 | 44 | if (index > activeIndex && index <= overIndex) { 45 | return { 46 | x: -activeNodeRect.width - itemGap, 47 | y: 0, 48 | ...defaultScale, 49 | }; 50 | } 51 | 52 | if (index < activeIndex && index >= overIndex) { 53 | return { 54 | x: activeNodeRect.width + itemGap, 55 | y: 0, 56 | ...defaultScale, 57 | }; 58 | } 59 | 60 | return { 61 | x: 0, 62 | y: 0, 63 | ...defaultScale, 64 | }; 65 | }; 66 | 67 | function getItemGap(rects: ClientRect[], index: number, activeIndex: number) { 68 | const currentRect: ClientRect | undefined = rects[index]; 69 | const previousRect: ClientRect | undefined = rects[index - 1]; 70 | const nextRect: ClientRect | undefined = rects[index + 1]; 71 | 72 | if (!currentRect || (!previousRect && !nextRect)) { 73 | return 0; 74 | } 75 | 76 | if (activeIndex < index) { 77 | return previousRect 78 | ? currentRect.left - (previousRect.left + previousRect.width) 79 | : nextRect.left - (currentRect.left + currentRect.width); 80 | } 81 | 82 | return nextRect 83 | ? nextRect.left - (currentRect.left + currentRect.width) 84 | : currentRect.left - (previousRect.left + previousRect.width); 85 | } 86 | --------------------------------------------------------------------------------