fluid-dnd

repository·main·Indexed 19 days ago

https://github.com/carlosjorger/fluid-dnd

A 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.

Tokens
34.5K
Snippets
100
Records
120
Agent score
63%

What's inside fluid-dnd

  1. Overview of Fluid DnD

    main

    Fluid 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.

  2. How `droppableGroup` membership works with multiple groups

    main

    A list can belong to multiple groups by providing a space-separated string to the droppableGroup parameter.

    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 G1 and G2.
    • 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 to G1 or G2).
    <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>
  3. Understand the Starlight project structure

    main

    A Starlight project follows a specific directory structure for content and assets:

    • src/content/docs/: The primary directory for documentation. Starlight treats .md or .mdx files 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.json
  4. How structured data (JSON-LD) is implemented in Fluid DnD documentation

    main

    The Fluid DnD documentation site uses JSON-LD structured data to improve SEO and provide rich snippets. The implementation is split into two main parts:

    1. src/components/overrides/CustomHead.astro: The component responsible for injecting the generated JSON-LD into the HTML <head>.
    2. 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): Injects WebPage and FAQPage schemas.
    • Guide pages (/guides/): Injects TechArticle schema with detailed about and keywords fields.
    • Other pages: Defaults to a TechArticle schema.

    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
  5. How multiple groups affect drag and drop permissions

    main

    A single list can belong to multiple groups by providing a space-separated string to the droppableGroup parameter.

    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 to group2).

    <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>
  6. How droppableGroup membership works

    main

    The droppableGroup parameter 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 has droppableGroup: '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 both group1 and group2.
    • Asymmetric Movement: If list A belongs only to group1 and list B belongs to group1 and group2, you can drag from A to B, but you cannot drag from B to A (because A does not belong to group2).
    // 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.
  7. Control drag permissions with the isDraggable property

    main

    You can selectively enable or disable dragging for specific elements within a list by providing an isDraggable function to the useDragAndDrop hook. This function receives the element as an argument and should return a boolean: true if the element is allowed to be dragged, and false otherwise.

    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"),
    });
  8. Animate inserted elements using insertingFromClass

    main

    To create a smooth entry effect for new items, use the insertingFromClass option in useDragAndDrop. You can then define CSS transitions for that class.

    1. Set insertingFromClass: "your-class-name" in the hook options.
    2. Define the initial state (e.g., opacity: 0) for .your-class-name.
    3. 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;
    }
  9. Enable type-aware ESLint rules for production

    main

    For production applications, it is recommended to upgrade the default ESLint configuration to use type-aware lint rules. This involves replacing tseslint.configs.recommended with 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 parserOptions to point to your tsconfig files 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,
        },
      },
    })
  10. Apply custom dropping styles using `droppableClass`

    main

    You can visually indicate when a draggable element is being hovered over a droppable area by using the droppableClass option in the useDragAndDrop hook.

    When a draggable item enters the bounds of a droppable element, Fluid DnD will automatically apply the CSS class name provided in droppableClass to that element. This allows you to trigger CSS transitions or style changes (like background color shifts) to provide user feedback.

    To implement this:

    1. Pass the desired class name string to the droppableClass property in the useDragAndDrop options object.
    2. Define a CSS rule for that class (e.g., .your-class-name) to change the element's appearance.
    3. Ensure the droppable element is correctly referenced via the parent ref 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>