fluid-dnd
repository·main·Indexed 19 days ago
https://github.com/carlosjorger/fluid-dndA lightweight (~8 Kb gzip), framework-agnostic drag and drop library for smooth list reordering. It features zero dependencies and provides specific support for Vue (>= 3.0.0), React (>= 18.0.0), and Svelte (>= 5.0.0). Optimized for both mouse and touch interactions, it includes utilities like the useDragAndDrop hook for inserting, removing, and transforming coordinates during drag operations.
What's inside fluid-dnd
- Fluid DnD is a fluid, agnostic, and versatile drag-and-drop library designed for lists. It provides smooth, high-quality animations and is built to be used with minimal code. It currently supports Vue 3, React, and Svelte.
Overview of Fluid DnD
mainFluid DnD is a fluid, agnostic, and versatile drag-and-drop library designed specifically for lists. It provides smooth, high-quality animations for drag-and-drop interactions. While the core logic is agnostic, it currently provides official support for the following frameworks:
- Vue 3
- React
- Svelte
The library is designed to minimize the amount of boilerplate code required to implement drag-and-drop functionality while remaining flexible enough for various use cases.
How `droppableGroup` membership works with multiple groups
mainA list can belong to multiple groups by providing a space-separated string to the
droppableGroupparameter.Drag and drop is only permitted between two lists if they share at least one common group.
Example Logic:
- List A belongs to group
G1. - List B belongs to groups
G1andG2. - Result: You can drag elements from A to B (because they share
G1), but you cannot drag from B to A (because A does not belong toG1orG2).
<script setup lang="ts"> const list1 = ref([1, 2, 3, 4]); const [ parent1 ] = useDragAndDrop(list1, { droppableGroup: "group1", }); const list2 = ref([5, 6, 7, 8]); const [ parent2 ] = useDragAndDrop(list2, { droppableGroup: "group1 group2", direction: "horizontal", }); </script>- List A belongs to group
Understand the Starlight project structure
mainA Starlight project follows a specific directory structure for content and assets:
src/content/docs/: The primary directory for documentation. Starlight treats.mdor.mdxfiles here as routes based on their filenames.src/assets/: Place images here to embed them in Markdown using relative links.public/: Place static assets like favicons here.astro.config.mjs: The configuration file for Astro.package.json: Defines project dependencies and scripts.
. ├── public/ ├── src/ │ ├── assets/ │ ├── content/ │ │ ├── docs/ │ │ └── config.ts │ └── env.d.ts ├── astro.config.mjs ├── package.json └── tsconfig.jsonHow structured data (JSON-LD) is implemented in Fluid DnD documentation
mainThe Fluid DnD documentation site uses JSON-LD structured data to improve SEO and provide rich snippets. The implementation is split into two main parts:
src/components/overrides/CustomHead.astro: The component responsible for injecting the generated JSON-LD into the HTML<head>.src/utils/structuredData.ts: A utility function (generateStructuredData) that contains the logic for generating specific schemas based on the current page type and content.
Page-Specific Logic
- Homepage (
/or/vue): InjectsWebPageandFAQPageschemas. - Guide pages (
/guides/): InjectsTechArticleschema with detailedaboutandkeywordsfields. - Other pages: Defaults to a
TechArticleschema.
Framework-Specific Breadcrumbs
The system automatically generates breadcrumb paths for framework-specific guides:
/vue/guides/...→ Home > Documentation > Vue/react/guides/...→ Home > Documentation > React/svelte/guides/...→ Home > Documentation > Svelte
How multiple groups affect drag and drop permissions
mainA single list can belong to multiple groups by providing a space-separated string to the
droppableGroupparameter.Drag and drop is only permitted between two lists if they share at least one common group. This allows you to create directional or restricted movement patterns. For example:
- List A belongs to
group1. - List B belongs to
group1 group2.
In this scenario, you can drag elements from List A to List B (because they share
group1), but you cannot drag elements from List B to List A (because List A does not belong togroup2).<script lang="ts"> const list1 = $state([1, 2, 3, 4]); const [ parent1 ] = useDragAndDrop(list1, { droppableGroup: "group1", }); const list2 = $state([5, 6, 7, 8]); const [ parent2 ] = useDragAndDrop(list2, { droppableGroup: "group1 group2", direction: "horizontal", }); </script>- List A belongs to
Handle type checking for .vue files in TypeScript
mainBecause standardtsccannot process type information for.vueimports, you must usevue-tscinstead oftscfor CLI-based type checking in your project.How droppableGroup membership works
mainThe
droppableGroupparameter controls the compatibility between lists. An element can only be dragged from list A to list B if they share at least one group identifier.- Single Group: If list A has
droppableGroup: 'group1'and list B hasdroppableGroup: 'group1', movement is bidirectional. - Multiple Groups: You can assign multiple groups to a single list by providing a space-separated string. For example,
droppableGroup: 'group1 group2'means the list belongs to bothgroup1andgroup2. - Asymmetric Movement: If list A belongs only to
group1and list B belongs togroup1andgroup2, you can drag from A to B, but you cannot drag from B to A (because A does not belong togroup2).
// List A: Only in group1 const [ parent, listValue ] = useDragAndDrop<number, HTMLUListElement>([1, 2, 3], { droppableGroup: 'group1', }); // List B: In both group1 and group2 const [ parent2, listValue2 ] = useDragAndDrop<number, HTMLDivElement>([4, 5, 6], { droppableGroup: 'group1 group2', direction: "horizontal", }); // Result: A -> B is possible. B -> A is NOT possible.- Single Group: If list A has
Control drag permissions with the isDraggable property
mainYou can selectively enable or disable dragging for specific elements within a list by providing an
isDraggablefunction to theuseDragAndDrophook. This function receives the element as an argument and should return a boolean:trueif the element is allowed to be dragged, andfalseotherwise.Common patterns include checking for specific CSS classes on the element to determine its drag status.
const [ parent, listValue ] = useDragAndDrop<number, HTMLUListElement>([1, 2, 3, 4], { isDraggable: (el) => !el.classList.contains("is-not-draggable"), });Animate inserted elements using insertingFromClass
mainTo create a smooth entry effect for new items, use the
insertingFromClassoption inuseDragAndDrop. You can then define CSS transitions for that class.- Set
insertingFromClass: "your-class-name"in the hook options. - Define the initial state (e.g.,
opacity: 0) for.your-class-name. - Define the target state (e.g.,
opacity: 1) for the base element class.
Example CSS:
.item { opacity: 1; transition: opacity 200ms ease; } .item.inserting { opacity: 0; }.number{ opacity: 1; transition: opacity 200ms ease; } .number.inserting{ opacity: 0; }- Set
Enable type-aware ESLint rules for production
mainFor production applications, it is recommended to upgrade the default ESLint configuration to use type-aware lint rules. This involves replacing
tseslint.configs.recommendedwith one of the following configurations:tseslint.configs.recommendedTypeChecked: Standard recommended type-aware rules.tseslint.configs.strictTypeChecked: Stricter rules for higher code quality.tseslint.configs.stylisticTypeChecked: Adds stylistic type-aware rules.
You must also configure
parserOptionsto point to yourtsconfigfiles so the parser can access type information.export default tseslint.config({ extends: [ // Replace ...tseslint.configs.recommended with one of these: ...tseslint.configs.recommendedTypeChecked, // ...tseslint.configs.strictTypeChecked, // ...tseslint.configs.stylisticTypeChecked, ], languageOptions: { // other options... parserOptions: { project: ['./tsconfig.node.json', './tsconfig.app.json'], tsconfigRootDir: import.meta.dirname, }, }, })Apply custom dropping styles using `droppableClass`
mainYou can visually indicate when a draggable element is being hovered over a droppable area by using the
droppableClassoption in theuseDragAndDrophook.When a draggable item enters the bounds of a droppable element, Fluid DnD will automatically apply the CSS class name provided in
droppableClassto that element. This allows you to trigger CSS transitions or style changes (like background color shifts) to provide user feedback.To implement this:
- Pass the desired class name string to the
droppableClassproperty in theuseDragAndDropoptions object. - Define a CSS rule for that class (e.g.,
.your-class-name) to change the element's appearance. - Ensure the droppable element is correctly referenced via the
parentref returned by the hook.
const [ parent, listValue ] = useDragAndDrop<number, HTMLUListElement>( [1, 2, 3, 4, 5], { droppableGroup: 'group1', droppableClass: 'droppable-hover' } ); // In your JSX, attach the ref to the droppable element <ul ref={parent} className="number-list"> {/* ... items ... */} </ul>- Pass the desired class name string to the