Skip to content

Conversation

@xerial
Copy link
Member

@xerial xerial commented Feb 5, 2026

Summary

  • Add DragDrop object for building drag-and-drop UIs
  • Add DomNode.group() for combining multiple DomNodes as a single unit
  • Add DomNodeGroup support in DomRenderer

Features

Drag and Drop API

import wvlet.uni.dom.all.*

// Make elements draggable
div(
  DragDrop.draggable("item", "item-123"),
  "Drag me"
)

// Create drop zones
div(
  DragDrop.dropZone("item") { data =>
    println(s"Dropped: ${data.data}")
  },
  cls -> "drop-area",
  "Drop here"
)

// File drop zone (for files from OS)
div(
  DragDrop.fileDropZone { files =>
    files.foreach(f => println(s"File: ${f.name}"))
  },
  "Drop files here"
)

// React to drag state
DragDrop.isDragging.map { dragging =>
  if dragging then div(cls -> "drag-overlay", "Drop anywhere")
  else DomNode.empty
}

DomNode.group

// Combine multiple modifiers as a single DomNode
div(
  DomNode.group(
    attr("data-x")("1"),
    attr("data-y")("2"),
    cls -> "combined"
  ),
  "Content"
)

API Summary

Method Description
DragDrop.draggable(data) Make element draggable with data
DragDrop.dropZone(onDrop) Create drop zone accepting any data
DragDrop.dropZone(kinds...)(onDrop) Create drop zone with type filter
DragDrop.fileDropZone(onFiles) Create zone for file drops
DragDrop.state Reactive stream of DragState
DragDrop.isDragging Reactive boolean for drag status
DomNode.group(nodes...) Combine multiple DomNodes

Test plan

  • All 282 tests pass (25 new drag-drop tests)
  • Compilation successful
  • Code formatted with scalafmtAll

🤖 Generated with Claude Code

Add DragDrop object for building drag-and-drop UIs:
- DragData/DragState for type-safe data transfer and state tracking
- draggable() to make elements draggable with data
- dropZone() for receiving drops with optional type filtering
- fileDropZone() for handling file drops from the OS
- Reactive state tracking with isDragging and state streams
- Event handlers for custom drag behavior

Also adds:
- DomNode.group() for combining multiple DomNodes
- DomNodeGroup support in DomRenderer

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@github-actions github-actions bot added doc Improvements or additions to documentation feature New feature labels Feb 5, 2026
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @xerial, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances uni-dom by integrating robust drag-and-drop capabilities. It provides a user-friendly API for making elements draggable, defining various types of drop zones (including file drop zones), and reacting to drag states in a reactive manner. Additionally, it introduces a mechanism to group DomNode modifiers, improving the expressiveness and organization of UI definitions.

Highlights

  • Drag and Drop API: Introduced a new DragDrop object providing high-level abstractions for HTML5 drag and drop events, simplifying UI development.
  • Reactive State Management: Implemented reactive DragState and isDragging streams to allow UIs to respond dynamically to drag operations.
  • Flexible Drop Zones: Added support for various drop zones, including those accepting specific data kinds and dedicated zones for file drops from the operating system.
  • DomNode Grouping: Introduced DomNode.group() and DomNodeGroup to combine multiple DomNode modifiers into a single unit, enhancing modularity and reusability.
  • Comprehensive Testing: Included 25 new tests specifically for the drag-and-drop functionality, ensuring robustness and correctness.
Changelog
  • plans/2026-02-04-drag-drop.md
    • Added a detailed design document outlining the new drag-and-drop feature, including its goals, API design (DragData, DragState, DragDrop object), usage examples, and implementation notes.
  • uni-dom-test/src/test/scala/wvlet/uni/dom/DragDropTest.scala
    • Introduced a new test suite with 25 new tests to thoroughly validate the functionality of DragData, DragState, DragDrop methods (draggable, dropZone, fileDropZone), reactive state emissions, and the DomNode.group feature.
  • uni/.js/src/main/scala/wvlet/uni/dom/DomNode.scala
    • Added a new DomNode.group factory method and a DomNodeGroup case class, enabling the composition of multiple DomNode instances into a single logical unit.
  • uni/.js/src/main/scala/wvlet/uni/dom/DomRenderer.scala
    • Updated the DomRenderer to correctly process and render the newly introduced DomNodeGroup instances, ensuring that all contained DomNodes are applied to the DOM.
  • uni/.js/src/main/scala/wvlet/uni/dom/DragDrop.scala
    • Implemented the core DragDrop object, defining DragData and DragState for data transfer and state management. This file contains the logic for handling dragstart, dragend, dragover, dragenter, dragleave, and drop events, supporting both internal data and external file drops.
  • uni/.js/src/main/scala/wvlet/uni/dom/all.scala
    • Modified the all.scala file to export the new DragDrop object, DragData, and DragState case classes, making them easily accessible throughout the uni-dom library.
Activity
  • A new design document for the drag-and-drop feature has been added.
  • 25 new tests were introduced for the drag-and-drop functionality, contributing to a total of 282 passing tests.
  • The code successfully compiles and adheres to scalafmtAll formatting standards.
  • The pull request description indicates that its summary was generated with Claude Code.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces drag and drop functionality to uni-dom, along with a DomNode.group helper. The implementation is well-structured, using a global reactive state for drag operations. I've found a potential bug in the fileDropZone implementation where the drag state is not correctly reset on drop. I've also noted that the DragDrop.stop() method is currently a no-op, which could be misleading. Additionally, the design document in plans/ is out of sync with the final implementation in a few places. My detailed comments provide suggestions to address these points.

Comment on lines 307 to 315
ondropH { (e: dom.DragEvent) =>
e.preventDefault()
instance.setOverElement(None)

val files = e.dataTransfer.files
if files.length > 0 then
val fileSeq = (0 until files.length).map(files(_))
onFiles(fileSeq)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The ondrop handler for fileDropZone is missing a call to instance.endDrag(). This is inconsistent with dropZone and can lead to the isDragging state remaining true after an in-app draggable element is dropped on a file drop zone. This can cause UI inconsistencies, such as a drag overlay not disappearing. You should call instance.endDrag() to ensure the drag state is reset upon drop, for both consistency and correctness.

      ondropH { (e: dom.DragEvent) =>
        e.preventDefault()
        instance.setOverElement(None)

        val files = e.dataTransfer.files
        if files.length > 0 then
          val fileSeq = (0 until files.length).map(files(_))
          onFiles(fileSeq)
        instance.endDrag()
      }

Comment on lines 16 to 59
### Core Types

```scala
// Data being transferred during drag
case class DragData(
kind: String, // Type identifier (e.g., "item", "file", "text")
data: Any, // The actual data
effectAllowed: String = "all" // copy, move, link, all, etc.
)

// Current state of drag operation
case class DragState(
isDragging: Boolean,
data: Option[DragData],
overElement: Option[dom.Element]
)
```

### DragDrop Object

```scala
object DragDrop:
// Make an element draggable
def draggable(data: DragData): DomNode
def draggable(kind: String, data: Any): DomNode

// Create a drop zone
def dropZone(onDrop: DragData => Unit): DomNode
def dropZone(accept: String*)(onDrop: DragData => Unit): DomNode

// File drop zone (for files from OS)
def fileDropZone(onFiles: Seq[dom.File] => Unit): DomNode

// Reactive state
def state: Rx[DragState]
def isDragging: Rx[Boolean]

// Event handlers for custom behavior
def onDragStart(handler: DragData => Unit): DomNode
def onDragEnd(handler: () => Unit): DomNode
def onDragOver(handler: dom.DragEvent => Unit): DomNode
def onDragEnter(handler: dom.DragEvent => Unit): DomNode
def onDragLeave(handler: dom.DragEvent => Unit): DomNode
```
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The API design described in this document has several inconsistencies with the actual implementation in DragDrop.scala. It's important to keep design documents up-to-date to avoid confusion for future developers.

Specifically:

  • DragData.data is defined with type Any, but implemented as String.
  • draggable(kind: String, data: Any) is defined, but implemented as draggable(kind: String, data: String).
  • onDragStart handler is defined as DragData => Unit, but implemented as dom.DragEvent => Unit.
  • onDragEnd handler is defined as () => Unit, but implemented as dom.DragEvent => Unit.

Please update this design document to reflect the implemented API.


def endDrag(): Unit = stateVar := DragState.empty

override def cancel: Unit = ()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The cancel method is currently a no-op, which means DragDrop.stop() does nothing. This is misleading given its name and documentation, which states it should 'Stop tracking drag state'. If the intention of stop() is to reset the drag state, cancel should be implemented to do so. This would also be useful for cleaning up in component unmount lifecycle hooks.

    override def cancel: Unit = stateVar := DragState.empty

xerial and others added 2 commits February 5, 2026 09:39
- Remove hardcoded dropEffect="move" to allow browser to determine
  appropriate effect based on effectAllowed
- Track drag state for external file drags in fileDropZone

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Make cancel method reset drag state instead of no-op
- Update plan document to match actual implementation
  - DragData.data is String, not Any
  - Event handlers take dom.DragEvent, not DragData
  - Add missing API methods to plan

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@xerial xerial merged commit 970e880 into main Feb 5, 2026
13 checks passed
@xerial xerial deleted the feature/drag-drop branch February 5, 2026 18:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

doc Improvements or additions to documentation feature New feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant