Compartment

Compartment

Using compartments to create isolated plugin environments

Compartments are Neodrag v3’s solution for reactive plugin updates. They provide manual, predictable control over when your draggable behavior changes.

What Are Compartments?

Compartments are reactive containers that hold a single plugin (or null) and update when their dependencies change.

Key Rule: One plugin per compartment - they don’t hold arrays of plugins.

How They Work

Compartments wrap your framework’s reactive system:

Svelte: Uses $effect.pre under the hood

Why Compartments?

V2 had automatic reactivity but caused problems:

  • Performance Issues: Entire plugin arrays recreated on every change
  • Memory Leaks: Old plugin instances weren’t properly cleaned up
  • Unpredictable Updates: Hard to control when updates happened
  • Over-Engineering: Simple static configs got reactive treatment they didn’t need

Compartments solve this with manual control, predictable updates, and better performance.

Basic Usage

<script>
  import { draggable, axis, Compartment } from '@neodrag/svelte';

  let currentAxis = $state('x');
  const axisComp = Compartment.of(() => axis(currentAxis));
</script>

<div {@attach draggable(() => [axisComp])}>Drag me</div>
<button onclick={() => (currentAxis = 'y')}>Switch Axis</button>

Multiple Compartments

Mix static plugins with reactive compartments:

<script>
  let currentAxis = $state('x');
  let gridSize = $state(20);
  let enableBounds = $state(false);

  // Reactive compartments
  const axisComp = Compartment.of(() => axis(currentAxis));
  const gridComp = Compartment.of(() => grid([gridSize, gridSize]));
  const boundsComp = Compartment.of(() =>
    enableBounds ? bounds(BoundsFrom.parent()) : null,
  );

  // Static plugins (never change)
  const staticPlugins = [events({ onDrag: console.log })];
</script>

<div
  {@attach draggable(() => [
    ...staticPlugins,
    axisComp,
    gridComp,
    boundsComp,
  ])}
>
  Drag me
</div>

When to Use Compartments

Use Compartments For:

  • Dynamic values that change from user interaction (sliders, dropdowns, toggles)
  • Conditional plugin logic (enable/disable features based on state)
  • Complex computed values with multiple dependencies

Don’t Use Compartments For:

  • Static values that never change (just use direct plugin arrays)
  • Simple boolean toggles (use direct conditionals instead)

Examples:

<script>
  // ✅ Good - dynamic values
  let userChoice = $state('x');
  let gridSize = $state(20);
  const axisComp = Compartment.of(() => axis(userChoice));
  const gridComp = Compartment.of(() => grid([gridSize, gridSize]));

  // ❌ Bad - static values
  const staticComp = Compartment.of(() =>
    bounds(BoundsFrom.parent()),
  );

  // ✅ Better - direct usage
  const staticPlugins = [bounds(BoundsFrom.parent())];

  // ❌ Bad - simple boolean
  let showGrid = $state(false);
  const simpleComp = Compartment.of(() =>
    showGrid ? grid([10, 10]) : null,
  );

  // ✅ Better - direct conditional
  const plugins = () => [
    axis('x'),
    ...(showGrid ? [grid([10, 10])] : []),
  ];
</script>

Debugging Compartments

Common Issues

Compartment not updating:

<script>
  let currentAxis = $state('x');

  // ❌ Wrong - captures initial value
  const axisComp = Compartment.of(() => axis('x'));

  // ✅ Correct - reactive reference
  const axisComp = Compartment.of(() => axis(currentAxis));
</script>

Debug Logging

Add logging to see when compartments update:

<script>
  const axisComp = Compartment.of(() => {
    console.log('Axis compartment updating:', currentAxis);
    return axis(currentAxis);
  });
</script>