> aidens.dev_

Latest Posts

Designing Freedeck's UI/UX

October 16, 2025

This article is still being written! This article will explain my thought process behind Freedeck...

Read Post

Creating a CSS grid-swapping JavaScript library for my app.

October 16, 2025

Why and how I created a library for vanilla JS for use with the CSS grid. ## I: Freedeck / The Pr...

Read Post

Posts

Why and how I created a library for vanilla JS for use with the CSS grid.

I: Freedeck / The Problem

Freedeck is my open source alternative to Elgato's Stream Deck.

It's a webapp macro-pad where buttons are aligned in a CSS grid.

II: Designing a Solution

First, we need to see the CSS grid itself: an adjustable row-column display.

Objects usually aren't swappable super easily, or atleast in a way where you can:

  • Highlight the dragged object
  • Disable certain items from being dragged
  • Drag and show context while keeping the real object in its same place
  • Send server events when the action is really completed
  • Re-render upon dropping with server data

This itself isn't hard to code on its own, but fits better in a generic library.

III: gridItemDrag

gridItemDrag is my proposed solution, which takes an HTML element (that uses a css grid) and has items in it.

While dragging, it can set the highlighted position to where an item will be dragged onto, and can disallow you from setting (or picking up) an item.

This code is a heavily modified excerpt of Freedeck's implementation. Most complexities/oddities of how Freedeck handles Tiles have been removed for simplicity sake.

import gridItemDrag from "./lib/gridItemDrag.js";

gridItemDrag.setFilter("#keys .button"); // These are the draggable items (CSS selector)

gridItemDrag.unmovableClass = ".builtin"; // These are not movable (CSS selector)

gridItemDrag.setContext(document.querySelector("#keys")); // This is the grid.

gridItemDrag.on("drop", (event, originalIndex, targetIndex) => {
  // When an item is dropped onto another.
  // originalIndex is the dragged item, targIndex is the one that originalIndex was dropped on.

  // gID works on indexes, not individual elements so we must query them.
  const changed = document.querySelector(`#keys .button.k-${originalIndex}`);

  // Grab the button we dragged and swap it!
  changed.classList.remove(`k-${originalIndex}`);
  changed.classList.add(`k-${targetIndex}`);

  // event.target is a direct reference to the dropped element.
  event.target.classList.remove(`k-${targetIndex}`);
  event.target.classList.add(`k-${originalIndex}`);

  // targetInter is a Freedeck-specific attribute that fully details a tile.

  const targetInter = JSON.parse(changed.getAttribute("data-interaction"));
  universal.send(universal.events.companion.move_tile, { // This will send an event up to the server teling it to swap the tiles.
    name: changed.getAttribute("data-name"),
    item: changed.getAttribute("data-interaction"),
    newIndex: targetIndex,
    oldIndex: originalIndex,
  });

  // Although it does get saved on the server side, the client is expected to do this refresh to stay up-to-date with the configuration.
  changed.pos = targetIndex;
  changed.setAttribute("data-interaction", JSON.stringify(targetInter));
});

IV: Conclusion

The library is open to grab from Freedeck's GitHub repo. It will eventually be placed in its own repository.

gridItemDrag is compatible with any library, as long as the runtime supports document.query, etc..

gridItemDrag can be found in the Freedeck GitHub repository (click here)

An implementation example can be found in Freedeck's code (click here)

Go Back