@wonderlandlabs-pixi-ux/observe-drag
Repository: https://github.com/wonderlandlabs-pixi-ux/wonderlandlabs-pixi-ux/tree/main/packages/observe-drag
observe-drag enforces a single active drag owner via a module-level pointer lock by default.
Behavior
- A
pointerdownis accepted only when no active pointer is in progress. - Accepted drags forward matching
pointermoveevents (pointerIdmust match the accepted down). pointerup,pointerupoutside, orpointercancelends the active drag and releases ownership.- Competing
pointerdownevents while busy callonBlocked. - A configurable inactivity watchdog auto-releases ownership if no matching
pointermoveis seen (abortTime, default1000ms; setabortTime: 0to disable). - You can inject your own lock (
activePointer$) at factory creation to override the default module singleton lock. - If an app with
render()is provided, drag moves trigger a throttled render (default30ms, configurable viarenderThrottleMs) and drag terminal events (pointerup,pointerupoutside,pointercancel) force a final render.
Usage
dragDecorator wraps your listeners and handles Pixi container movement with default assumptions:
- target is a Pixi
Container-like object (position, optionalparent.toLocal) - pointer coordinates come from Pixi events (
event.global) - movement is calculated in parent-local space when possible (
parent.toLocal(globalPoint))
1. Simple Dragging
import dragObserverFactory, {dragDecorator} from '@wonderlandlabs-pixi-ux/observe-drag';
const observeDown = dragObserverFactory({stage: app.stage, app});
const sub = observeDown(target, dragDecorator(), {dragTarget: myContainer});
2. Custom Listeners
const sub = observeDown(
target,
dragDecorator({
onStart(event, dragTarget) {
// optional domain setup
},
onMove(event, context, dragTarget) {
// extra side effects after default movement
},
onUp(event, context, dragTarget) {
// drag finished
},
onBlocked(event, dragTarget) {
// another drag currently owns the stage
},
onError(error, phase, event, dragTarget) {
// listener threw; handle safely
},
}),
{
dragTarget: myContainer,
abortTime: 1500,
getDragTarget(downEvent) {
// optional dynamic target resolution
return downEvent.pointerId === 2 ? altContainer : myContainer;
},
debug(source, message, data) {
if (message === 'pointer.busy') {
console.warn(source, message, data);
return;
}
console.log(source, message, data);
},
},
);
3. Snapping / Transform
const sub = observeDown(
target,
dragDecorator({
transformPoint(point) {
return {
x: Math.round(point.x / 10) * 10,
y: Math.round(point.y / 10) * 10,
};
},
}),
{dragTarget: myContainer},
);
Optional Zero-Arg Decorator
const sub = observeDown(target, dragDecorator(), {dragTarget: boxContainer});
sub.unsubscribe();
Notes
- You do not need to re-subscribe after each
pointerup; one subscription handles repeated drag cycles. - Returning context from
onStartis optional. - Receiving context in
onMoveandonUpis optional; ifonStartreturns nothing, context isundefined. - If returned,
onStartcontext can be any object and is passed intoonMoveandonUp. - Core observe-drag does not move targets by itself; use
dragDecorator()for default target motion, or move the target in your own listeners. - Subscription options support
dragTarget(static),getDragTarget(downEvent, context)(dynamic), andabortTime(watchdog timeout in ms;0disables it). - Factory options support
activePointer$so you can provide your own lock instead of using the default module singleton. - Factory options also support
stage(whenappis not provided), optionalappfor drag render calls, andrenderThrottleMsto tune render throttle (default30). dragDecorator()provides default Pixi container dragging using parent-local coordinates, then delegates to your wrapped listeners.dragDecorator()works with no parameters.dragTargetDecorator()is deprecated and remains as a compatibility wrapper.debugis part of the options object ({ debug(source, message, data) {} }), not a separate third-arg overload.onErroris reserved for thrown listener errors and internal callback failures. Busy contention usesonBlocked, notonError.