# 5.7 Input and drag

> Contract 2.0.0 · package sha256 `a857eca95b35921109faf997094bb4192f12527d9d0fcd4be7e97f06425bad6e` · generated from `examples/section-5/5.07-input-and-drag.ts` by `services/onsite-scripter/scripts/build-contract-docs.mjs`; edit the source, not this file.

Imports: values (`has`, `validate`, the `Holo*Error` classes, the pure helpers) from `@holostep-configurators/runtime-client`, types from `@holostep-configurators/runtime-client/contract`, `/math` and `/testing` names from those entries; `boot` from the runtime module the root's `loadRuntimeAtFloor` resolves to. A type your page defines is your app's: declare it there, never import it from the package.

```ts
import { has } from '@holostep-configurators/runtime-client';
import type { DragMove, DragOptions, Part, Point3, Pose, PoseInput, ScreenPoint, Session } from '@holostep-configurators/runtime-client/contract';
declare const session: Session;
declare const signal: AbortSignal;
declare const armedIds: readonly string[];
declare const ruler: Part;                                          // a 'lines' item of a helper collection
declare const machine: { step(move: DragMove): { pose: PoseInput; rulerPoints: readonly Point3[] } };
// moveGlide returns false when the store keeps the node where it is (an equal sync)
declare const store: { canDrop(part: Part, pose: Pose): boolean; moveGlide(node: string, pose: Pose): boolean };
declare const longPress: { onFire(cb: (e: { pointerId: number; at: ScreenPoint }, part: Part) => void): void };
declare function isWallUnit(part: Part): boolean;
declare function wallKeyOf(part: Part): string;
const dragOptions: DragOptions = {
  on: (part) => (isWallUnit(part) ? { collection: 'room', key: wallKeyOf(part) } : 'floor'),
  commit: 'app',
  constrain: (move, frame) => {
    // App code: collision, snap, spring. It aims with move.ray, never move.pose after a re-yaw
    // (the grab offset doesn't turn); the pressed part comes from its own pointerdown input.pick.
    const step = machine.step(move);
    frame.lines(ruler, step.rulerPoints);            // shows in this frame
    return step.pose;                                // { position, quaternion, scale }: one rotation
  },
  signal,
};
let stopDrag = session.input.drag(armedIds.map((node) => ({ node })), dragOptions);   // arming changes: stopDrag(), register again
session.on('drag:end', ({ part, pose, moved, reason }) => {
  if (!moved || reason !== 'released' || !store.canDrop(part, pose)) {
    if (has(session, 'input.revert')) session.input.revert(part);
    return;
  }
  // the store sync changes the node's pose and ends the held pose; an equal sync releases nothing,
  // unless it superseded the drop's pending sync (a store undo): then it shows the store's pose
  if (!store.moveGlide(part.node!, pose) && has(session, 'input.revert')) session.input.revert(part);
}, { signal });
longPress.onFire((e, part) => {
  if (!has(session, 'input.startDrag') || !part.node) return;
  stopDrag();                                        // the pressed cabinet may be unarmed: register it first
  stopDrag = session.input.drag([...armedIds, part.node].map((node) => ({ node })), dragOptions);
  session.input.startDrag(part, { pointerId: e.pointerId, at: e.at });
});
```

Back to [the examples](README.md) · [llms.txt](../llms.txt)
