Layer stack + code · bagon-hooks × solid-primitives

What bagon ships that SP doesn’t

Same stack as before — coral band is the unique layer. Below: real bagon API vs the solid-primitives-shaped workaround for each gap.

Compact layer stack App UI, bagon-hooks, overlap, solid-primitives, Solid core. UI CORE L5 🧩 Your components L4 🐉 bagon-hooks ← unique gaps below L3 🔀 Overlap · clipboard · idle · fullscreen · media L2 🧱 solid-primitives · pointer · keyed · stores L1 ⚡ SolidJS core · signals · effects · JSX SP = many packages · bagon = one Mantine-style set for component authors

Six gaps · bagon API vs SP-shaped workaround

01 · Modal / drawer state

useDisclosure · useDisclosureData

Typed payload + delayed clear so exit anims still see data. Without bagon you’re wiring timers by hand.

bagon-hooks

const [data, isOpen, { open, close }] =
  useDisclosureData<Item>(
    null,
    false,
    {
      dataDisappearDelay: 250,
      onOpen,
      onClose,
    },
  );

open({ id: "42" });
close(); // data survives exit anim

solid-primitives · roll your own

const [opened, setOpened] = createSignal(false);
const [data, setData] = createSignal<Item | null>(null);
let clearTimer: ReturnType<typeof setTimeout>;

const open = (item: Item | null) => {
  clearTimeout(clearTimer); // cancel pending clear
  setData(() => item);
  setOpened(true);
  onOpen?.();
};

const close = () => {
  setOpened(false);
  onClose?.();
  clearTimer = setTimeout(
    () => setData(null),
    250, // must match exit anim ms
  );
};
// reimplement per modal · easy to
// desync delay, leak timers, drop data

02 · Modal a11y

useFocusTrap · useFocusReturn

Trap Tab inside overlays; restore focus on close. SP has autofocus — not trap / restore.

bagon-hooks

const trapRef = useFocusTrap(opened);
useFocusReturn({ opened, shouldReturnFocus: true });

<div ref={trapRef} role="dialog">
  <input data-autofocus />
  <button>OK</button>
</div>

solid-primitives · autofocus only

import { autofocus } from
  "@solid-primitives/autofocus";

<input ref={autofocus} autofocus />

// still need: tab-scope, focusable
// query, restore previous element,
// Escape handling — DIY

03 · Sticky chrome

useHeadroom

Pin / release + scrollProgress for hide-on-scroll headers. No SP equivalent.

bagon-hooks

const { pinned, scrollProgress } =
  useHeadroom({ fixedAt: 80, onPin, onRelease });

<header style={{
  transform: `translateY(${
    (scrollProgress() - 1) * 100
  }%)`,
}} />

solid-primitives · compose yourself

import { createScrollPosition }
  from "@solid-primitives/scroll";

const scroll = createScrollPosition();
// you own: direction change,
// pin threshold, progress lerp,
// onPin / onRelease callbacks

04 · Pointer scrubbing

useMove

Normalized 0–1 drag for sliders & color pickers (RTL-aware). SP pointer/mouse are rawer.

bagon-hooks

const { ref, active } = useMove(
  ({ x, y }) => setValue({ x, y }), // 0–1
  { onScrubStart, onScrubEnd },
  "ltr",
);

<div ref={ref}>{/* track */}</div>

solid-primitives · lower-level

import { createPointerPosition }
  from "@solid-primitives/mouse";

const pos = createPointerPosition();
// still need: getBoundingClientRect,
// clamp to [0,1], RTL flip,
// scrub start/end, rAF batching

05 · Form lists

useListState

Form-friendly mutators. SP list/map/set are reactive collections — different job.

bagon-hooks

const [list, handlers] =
  useListState([{ name: "", qty: 1 }]);

handlers.append({ name: "Tea", qty: 2 });
handlers.setItemProp(0, "qty", 3);
handlers.reorder({ from: 0, to: 1 });
handlers.applyWhere(
  (i) => i.qty > 1,
  (i) => ({ ...i, qty: i.qty + 1 }),
);

solid-primitives · different API

import { reactiveArray }
  from "@solid-primitives/list";
// reactive wrappers / keyed lists
// — not form mutators like
// setItemProp / applyWhere /
// reorder for controlled inputs

06 · Library components

useUncontrolled

Controlled + uncontrolled dual-mode for reusable inputs. SP controlled-props is adjacent, not the same.

bagon-hooks

function TextInput(props) {
  const [value, setValue] = useUncontrolled({
    value: () => props.value,
    defaultValue: props.defaultValue,
    finalValue: "",
    onChange: props.onChange,
  });
  return (
    <input
      value={value()}
      onInput={(e) => setValue(e.currentTarget.value)}
    />
  );
}

solid-primitives · adjacent

import { combineProps }
  from "@solid-primitives/props";
// merges / controls prop objects
// — not value / defaultValue /
// finalValue dual-mode state
// for library component authors