trieur.

Throw, don't drop experimental

A drop asks you to be accurate about a coordinate: let go over the right cell, or the card is filed in the wrong folder. A throw asks you to be accurate about a direction, which the hand is far better at. The card keeps the speed it had when you released it, travels a little further on its own, and lands where that carries it โ€” so a distant zone no longer has to be reached, and two neighbouring cells no longer share a border you have to hit.

Where it comes to rest

The same arithmetic a scroll view uses: velocity decays exponentially at ฮป per millisecond, and the trip integrates to v ยท ฮป/(1โˆ’ฮป). That fraction is a duration, which is why the knob is one โ€” 170 ms is ฮป โ‰ˆ 0.994, between iOS's fast scrolling and its normal one. A card is lighter than a scroll view.

The speed at lift-off

Not an average of the drag โ€” a weighted quadratic fit of the last 100 ms, which is what Android's VelocityTracker does. A fling is usually still accelerating when the finger leaves, and an average reports the speed of the gesture's middle instead of its end.

The model pulls

The iPhone keyboard's oldest trick: after kno, the w key's hit area grows while the key itself does not move a pixel. Here the tiles stay put and a confident suggestion quietly catches throws landing up to bias ร— score of a tile wide of it. The dashed circle in the debug view is that pull.

Last release

speed
โ€”
carried
โ€”
verdict
โ€”
filed into
โ€”

Why a mosaic

The zones here are deliberately small and adjacent โ€” the case where dropping is a coin toss. A careful drag still drops exactly where you leave it; only a deliberate flick is projected, which is what the floor below 0.6 px/ms is for.

Turning it on

import { flick } from '@trieur/core/flick';

new Deck(el, {
  items, zones,
  layout: 'voronoi',
  plugins: [flick({
    ms: 170,       // how far ahead the release is projected
    decay: 0.994,  // the same thing as a deceleration rate, if you prefer
    min: 0.6,      // px/ms below which it is an ordinary drop
    bias: 0.4,     // how much wider the suggestion catches, in tiles
    debug: false,  // draw the vector while tuning
  })],
});

It is a plugin, not an option: the projection, the velocity fit and the debug renderer are about a kilobyte gzipped that a page which never throws a card should not be carrying. The deck knows nothing about any of it โ€” it asks whoever is listening where the gesture is pointing, and the first answer wins.

A throw resolves to the nearest tile, not to the region under the landing point: the landing point is a prediction, not a touch, and past the edge of the stage there are no regions left to be in. "The nearest tile in the direction you threw" always has an answer โ€” and a zone the drag was heading away from is never one of the candidates, whatever the arithmetic says.

The projection is capped at the stage diagonal. Past the edge, every extra pixel of reach aims at the same zone anyway, and an uncapped throw off a fast trackpad lands in another postcode. Velocity is sampled on the raw pointer moves rather than the throttled ones โ€” a flick is over in three frames, and averaging across a frame boundary flattens precisely the peak that made it a flick โ€” and a finger that rests before letting go clears the window: a gesture that stopped has thrown nothing, however fast it arrived.

It is marked experimental because the right numbers depend on your zones: a dock of six columns wants little reach, a mosaic of twenty cells wants a lot. The settings panel has all four on sliders, and the debug view draws what they do.

Keyboard and gestures

Sorting

File into a zone
a s d โ€ฆ or drag the card there
Accept the model's suggestion
Skip โ€” back of the pile
Undo, and unlearn it
Fullscreen
the bar button; Esc leaves

Touch

Accept the suggestion
double tap the card