Utility module with agnostic helpers for class-name composition, DOM event binding, selection list operations, dragging, textarea auto-resize, keyboard handling, and route dispatch.
Helpers are split across select/utils.js, select/interaction.js, and
select/routing.js for clearer boundaries.
import { add, clsx, next, toggle } from "./utils.js";
import { bind, Keyboard } from "./interaction.js";
import { router } from "./routing.js";
import { browser } from "./browser.js";
const input = document.querySelector("input");
bind(input, {
keydown: (event) => {
if (Keyboard.Code(event) === Keyboard.Codes.ENTER) {
input.className = clsx("field", { submitted: true });
}
},
});
const routes = router({
"/": () => console.log("home"),
"/users/{id:number}": (_path, { id }) => console.log("user", Number(id)),
});
const state = browser();
state.path.sub((path) => {
routes.run(path);
});
let selection = [{ id: 1 }, { id: 2 }];
selection = toggle(selection, { id: 2 });
selection = add(selection, { id: 3 });
console.log(selection[next(selection, 0, -1)]);Builds a dictionary from words to compact base-26 tokens (a, b, ..., aa, ...), sorted by descending frequency.
Compresses text by replacing dictionary words with token values.
Decompresses text produced by shortword. Also supports inline dictionary payloads in the form word1,word2,...=payload.
Returns a stable fallback key:
item.iditem.keyitem.name- otherwise
item
Finds index for item in items.
- if
itemsis nullish, returns-1 - if
key === null, uses strict identity (indexOf) - otherwise compares extracted keys
Returns true when find(...) >= 0.
Returns a new array with item appended only when absent.
Returns a new array without item when found; otherwise returns original input.
Removes item when present, adds it when absent.
Returns wrapped index in the range [0, n), where n is:
itemsOrLengthwhen it is a numberitemsOrLength.lengthwhen array-like
Generator that yields normalized class tokens.
Accepted values:
- string (trimmed)
- number (stringified)
- array (flattened recursively)
- object (
keyemitted whenvalue[key]is truthy)
Ignored values:
- falsy values (
null,undefined,"",0,false) - booleans
[...iclsx("btn", ["primary", null], { active: true, hidden: false })];
// ["btn", "primary", "active"]Returns a space-joined classname string from iclsx(...args).
clsx("btn", ["large"], { disabled: false, active: true });
// "btn large active"Attaches event handlers from { eventName: handler } to one node or an array of
nodes. Returns the original node argument.
bind(button, {
click: onClick,
mouseenter: onEnter,
});Removes event handlers from one node or an array of nodes. Returns the original
node argument.
unbind(button, {
click: onClick,
mouseenter: onEnter,
});Starts a mouse drag interaction from a mousedown event.
move(event, delta)runs onmousemoveend(event, delta)runs onmouseupormouseleave- returns
cancel()function
delta includes:
dx,dy: displacement from drag originox,oy: origin position (pageX,pageY)isFirst: true on first move callbackisLast: true in end callbackstep: number of move stepscontext: mutable shared object for drag lifecycle state
node.addEventListener("mousedown", (event) => {
drag(event, (_event, delta) => {
node.style.transform = `translate(${delta.dx}px, ${delta.dy}px)`;
});
});Walks up ancestors until it finds an element with data-drag.
- no
name: anydata-drag - with
name: exactdata-drag="name"
Generic ancestor walk helper. Returns first matching node or undefined.
Auto-resizes a textarea by setting:
height = autoheight = border + scrollHeight
Use with:
textarea.addEventListener("input", autoresize);Keyboard object provides constants and helper methods.
Keyboard.Down:"keydown"Keyboard.Up:"keyup"Keyboard.Press:"press"
Keyboard.Codes.ENTER,ESC,TAB,SPACE, arrows, modifiers, etc.
Keyboard.Key(event): key label (event.keyfallback)Keyboard.Code(event): numeric key codeKeyboard.Char(event): printable character or newline for EnterKeyboard.IsControl(event): true if key is non-character/controlKeyboard.HasModifier(event): true when Alt or Ctrl is pressed
Parses a route expression into normalized path chunks and dynamic capture slots.
Supported slots:
{name}: any non-empty chunk{name:number}: numeric chunk{name:alpha}: alphabetic chunk{name:string}: alphanumeric plus_and-{name:<regexp>}: custom regexp source
route("/users/{id:number}");Route trie that stores handlers for static and dynamic path chunks.
Registers a handler for the route expression.
Handler signature:
(path, captured, ...args) => any
captured maps slot names to matched string chunks.
Unregisters one handler (when provided) or all handlers for expr.
Returns matching handlers array at the matched leaf, or null.
Runs the best matching handler.
Selection order:
- highest
priority - when equal, latest registered
Returns a serializable object representation of the route tree.
Generator yielding all handlers depth-first.
Factory that creates a Router and registers entries from:
{ "/path": handler, ... }
Factory returning a callable dispatcher function:
- call:
(path, ...args) => any - props:
.router,.match(path)
Browser-backed URL and storage state moved to select.cells.js.
browser(options?): Returns{ path, query, hash, local }path:Cell<string>bound tolocation.pathnamequery:Cell<object>bound tolocation.searchhash:Cell<object>bound tolocation.hashlocal(key, dflt, opts?):Cell<T>backed bylocalStorage