Reorder

Reorder groups: a list the user reorders by dragging an item, or with the keys, as Motion's Reorder.Group. A Group holds its items by value and runs one drag at a time. A binding registers each item with how to read its box, drives the group from a drag's samples (dragStart, dragMove, dragEnd) or from keys (lift, moveLifted, drop, cancel), and the group calls onReorder with each new order. check is the rule that decides when a dragged item swaps places, and defaultAnnouncements what a screen reader hears. The binding and its Environment supply the boxes, points, keys and scrollers.

import * as Reorder from "@wardic/core/Reorder"

const group = Reorder.make({ values: () => items, onReorder: (next) => (items = next) })
const unregister = Reorder.register(group, item, source)
// As the item is dragged:
Reorder.dragStart(group, item)
Reorder.dragMove(group, item, sample.velocity)
Reorder.dragEnd(group, item)

Interfaces

AnnouncementInfo

What an announcement says about an item.

Type Parameters

V

V

Properties

count
readonly count: number;

How many items the list has.

label
readonly label: string;

What it's spoken as: its name, as the Environment reads it.

position
readonly position: number;

Its place in the list, from 1.

value
readonly value: V;

The item's value.


Announcements

What a group announces: one for each moment of a keyboard reorder, and move for a move in code, an app's own buttons. A group overrides any of them, and its instructions, for translation or tone.

Example

const announcements: Reorder.Announcements<Task> = {
  ...Reorder.defaultAnnouncements,
  drop: ({ label, position }) => `${label} is now number ${position}.`,
}

Type Parameters

V

V

Properties

cancel
readonly cancel: (info) => string;

The reorder cancelled: "Cancelled. Milk is back at position 2 of 5."

Parameters
info

AnnouncementInfo<V>

Returns

string

drop
readonly drop: (info) => string;

The lifted item dropped: "Dropped Milk at position 3 of 5."

Parameters
info

AnnouncementInfo<V>

Returns

string

lift
readonly lift: (info) => string;

An item lifted: "Picked up Milk, position 2 of 5."

Parameters
info

AnnouncementInfo<V>

Returns

string

move
readonly move: (info) => string;

An item moved in code: "Moved Milk to position 1 of 5."

Parameters
info

AnnouncementInfo<V>

Returns

string

step
readonly step: (info) => string;

The lifted item stepped: "Milk, position 3 of 5."

Parameters
info

AnnouncementInfo<V>

Returns

string


Group

A reorder group: its items by value, and one drag at a time. Made by make; its operations take it first, or in a pipe.

  • register adds an item: a value has one source, and the function it returns removes it. A value that isn't registered stays at its index in values.
  • resolveAxis is the group's axis: the axis it's given, else detectAxis over its measured items.
  • dragStart fixes a drag's axis and direction, and gives the axis; dragMove runs check for the dragged item, with the drag sample's velocity, and gives the velocity back for auto-scroll; dragEnd ends it.
  • move moves a value in code, held to the list; a value it doesn't know is left out.
  • lift, moveLifted, drop and cancel reorder one item with the keys, each giving where the item is, for its announcement.

Its state is its own, changed only by those operations.

Extends

  • Pipeable
  • Inspectable

Type Parameters

V

V

Properties

~@wardic/core/Reorder
readonly "~@wardic/core/Reorder": object;
_V
readonly _V: Invariant<V>;
options
readonly options: GroupOptions<V>;

The inputs make took.

sources
readonly sources: ReadonlyMap<V, ItemSource>;

Each registered item's source, by value.


GroupOptions

A group's options: make's.

Type Parameters

V

V

Properties

axis?
readonly axis?: () => Option<Axis>;

The axis, when it's given (Some): read at each drag's start, over the one detectAxis finds.

Returns

Option<Axis>

debug?
readonly debug?: (event, annotations) => void;

Hears what happens, for a debug log: reorder { value, from, to, axis, direction } once per reorder, reorder waits { value } once per wait for the boxes to be measured, reorder start { value, axis, direction }, and move { value, from, to }.

Parameters
event

string

annotations

Record<string, unknown>

Returns

void

direction?
readonly direction?: () => "ltr" | "rtl";

The group's inline direction, read at each drag's start: "ltr" when it's left out.

Returns

"ltr" | "rtl"

onReorder
readonly onReorder: (values) => void;

Hears each new order, which the app sets, as with Motion's.

Parameters
values

V[]

Returns

void

values
readonly values: () => readonly V[];

The app's order, read when it's needed.

Returns

readonly V[]


ItemData

An item as check sees it: its value and its layout box, its place in the list, not where it paints while it's dragged.

Type Parameters

V

V

Properties

layout
readonly layout: Box;

Its layout box: its place in the list.

value
readonly value: V;

The item's value in the list.


ItemSource

What a group reads from one item, when it needs it: a layout projection's handle for the item gives all three.

Properties

dragOffset
readonly dragOffset: () => Point;

How far it's dragged from its place.

Returns

Point

layoutBox
readonly layoutBox: () => Option<Box>;

Its layout box, its place in the list: None before it's measured.

Returns

Option<Box>

layoutVersion
readonly layoutVersion: () => number;

A count that rises each time its box is measured: after a reorder, a drag waits until it does.

Returns

number


Movement

How a dragged item moves, as check reads it.

Properties

axis
readonly axis: Axis;

The axis the list runs on, or "xy" for a grid.

direction?
readonly direction?: "ltr" | "rtl";

Which way a line reads: "ltr", the default, or "rtl".

offset
readonly offset: Point;

Where the item is from where it started.

velocity
readonly velocity: Point;

Its velocity, per second.


Placement

Where an item is after a keyboard reorder's step, for its announcement.

Properties

count
readonly count: number;

How many items the order has.

moved
readonly moved: boolean;

Whether this step moved it: not at an end.

position
readonly position: number;

Its place in the order, from 1.

waiting
readonly waiting: boolean;

Whether the step waited for the boxes to be measured: after a step they're the old order's until they are, so a step read against them could go the wrong way. It moved nothing, and has nothing to announce: a held arrow's repeat in that time is dropped.

Type Aliases

Arrow

type Arrow = "left" | "right" | "up" | "down";

An arrow key, by where it points on screen.


Axis

type Axis = "x" | "y" | "xy";

The axis a group reorders on: a column ("y"), a row ("x"), or a grid that wraps ("xy"), as Motion's Axis.


KeyIntent

type KeyIntent = 
  | {
  _tag: "Toggle";
}
  | {
  _tag: "Cancel";
}
  | {
  _tag: "Step";
  arrow: Arrow;
}
  | {
  _tag: "End";
  end: "first" | "last";
};

What a key does to a keyboard reorder: Space or Enter lifts an item, or drops the lifted one (Toggle); Escape cancels; an arrow steps the lifted item one place on screen; Home and End move it to the first or last place. Any other key does nothing.

Variables

cancel

const cancel: {
  <V>(value): (group) => Option<Placement>;
  <V>(group, value): Option<Placement>;
};

Cancels the keyboard reorder: the order from the lift comes back through onReorder, when it changed, and the item's placement there. None when value isn't lifted.

Call Signature

<V>(value): (group) => Option<Placement>;
Type Parameters
V

V

Parameters
value

V

Returns

(group) => Option<Placement>

Call Signature

<V>(group, value): Option<Placement>;
Type Parameters
V

V

Parameters
group

Group<V>

value

V

Returns

Option<Placement>


DEFAULT_INSTRUCTIONS

const DEFAULT_INSTRUCTIONS: "Press Space or Enter to pick up. Use the arrow keys to move, Space or Enter to drop, Escape to cancel." = "Press Space or Enter to pick up. Use the arrow keys to move, Space or Enter to drop, Escape to cancel.";

The default instructions for an item, in English: the keys. An Environment adds what its own assistive technology needs, as @wardic/dom's DEFAULT_REORDER_INSTRUCTIONS does.


defaultAnnouncements

const defaultAnnouncements: Announcements<unknown>;

The default announcements, in English: each member's example is its wording.


dragEnd

const dragEnd: {
  <V>(value): (group) => void;
  <V>(group, value): void;
};

Ends the drag of value's item; nothing, when it isn't the one dragged.

Call Signature

<V>(value): (group) => void;
Type Parameters
V

V

Parameters
value

V

Returns

(group) => void

Call Signature

<V>(group, value): void;
Type Parameters
V

V

Parameters
group

Group<V>

value

V

Returns

void


dragMove

const dragMove: {
  <V>(value, velocity): (group) => Point;
  <V>(group, value, velocity): Point;
};

A drag sample of value's item, moving at velocity, the sample's: runs check, and gives velocity back, for auto-scroll; no motion when value isn't the one dragged.

Call Signature

<V>(value, velocity): (group) => Point;
Type Parameters
V

V

Parameters
value

V

velocity

Point

Returns

(group) => Point

Call Signature

<V>(
   group, 
   value, 
   velocity
): Point;
Type Parameters
V

V

Parameters
group

Group<V>

value

V

velocity

Point

Returns

Point


dragStart

const dragStart: {
  <V>(value): (group) => Axis;
  <V>(group, value): Axis;
};

Starts dragging value's item: the drag's axis and direction are fixed now. Gives the axis.

Call Signature

<V>(value): (group) => Axis;
Type Parameters
V

V

Parameters
value

V

Returns

(group) => Axis

Call Signature

<V>(group, value): Axis;
Type Parameters
V

V

Parameters
group

Group<V>

value

V

Returns

Axis


drop

const drop: {
  <V>(value): (group) => Option<Placement>;
  <V>(group, value): Option<Placement>;
};

Drops the lifted item where it is: its placement, None when value isn't lifted.

Call Signature

<V>(value): (group) => Option<Placement>;
Type Parameters
V

V

Parameters
value

V

Returns

(group) => Option<Placement>

Call Signature

<V>(group, value): Option<Placement>;
Type Parameters
V

V

Parameters
group

Group<V>

value

V

Returns

Option<Placement>


lift

const lift: {
  <V>(value): (group) => Option<Placement>;
  <V>(group, value): Option<Placement>;
};

Lifts value's item for a keyboard reorder: its axis and direction are fixed now, as a drag's are at its start, and the order kept for a cancel. Lifting another item drops the one lifted. None for a value not in values.

Call Signature

<V>(value): (group) => Option<Placement>;
Type Parameters
V

V

Parameters
value

V

Returns

(group) => Option<Placement>

Call Signature

<V>(group, value): Option<Placement>;
Type Parameters
V

V

Parameters
group

Group<V>

value

V

Returns

Option<Placement>


move

const move: {
  <V>(value, toIndex): (group) => Option<Placement>;
  <V>(group, value, toIndex): Option<Placement>;
};

Moves value to toIndex in code, through onReorder, held to the list, as an app's own buttons do: where it is then, for its announcement, moved false when it was there already. None, and nothing moved, for a value the group doesn't know.

Call Signature

<V>(value, toIndex): (group) => Option<Placement>;
Type Parameters
V

V

Parameters
value

V

toIndex

number

Returns

(group) => Option<Placement>

Call Signature

<V>(
   group, 
   value, 
   toIndex
): Option<Placement>;
Type Parameters
V

V

Parameters
group

Group<V>

value

V

toIndex

number

Returns

Option<Placement>

Example

Reorder.move(group, item, 0) // to the top

moveLifted

const moveLifted: {
  <V>(value, intent): (group) => Option<Placement>;
  <V>(group, value, intent): Option<Placement>;
};

One key's move of the lifted item: an arrow's step on screen (arrowTarget), or Home's or End's move to the first or last place, through onReorder. moved is false where it had nowhere to go. Until the boxes are measured after its last step, a step waits (waiting, nothing moved), as a drag's next reorder does. None when value isn't the lifted item.

Call Signature

<V>(value, intent): (group) => Option<Placement>;
Type Parameters
V

V

Parameters
value

V

intent

| { _tag: "Step"; arrow: Arrow; } | { _tag: "End"; end: "first" | "last"; }

Returns

(group) => Option<Placement>

Call Signature

<V>(
   group, 
   value, 
   intent
): Option<Placement>;
Type Parameters
V

V

Parameters
group

Group<V>

value

V

intent

| { _tag: "Step"; arrow: Arrow; } | { _tag: "End"; end: "first" | "last"; }

Returns

Option<Placement>


register

const register: {
  <V>(value, source): (group) => () => void;
  <V>(group, value, source): () => void;
};

Adds value's item, read through source. The function it returns removes it.

Call Signature

<V>(value, source): (group) => () => void;
Type Parameters
V

V

Parameters
value

V

source

ItemSource

Returns

(group) => () => void

Call Signature

<V>(
   group, 
   value, 
   source
): () => void;
Type Parameters
V

V

Parameters
group

Group<V>

value

V

source

ItemSource

Returns

() => void

Functions

arrowTarget()

function arrowTarget<V>(
   order, 
   index, 
   arrow, 
   axis, 
   direction
): number;

Where a lifted item goes for one arrow: its new index in order, by what's on screen, so an rtl row or a reversed list moves the way the arrow points.

  • One axis: only that axis's arrows act. The item takes the place of the neighbour, of the two next to it, whose centre lies the way the arrow points, as check chooses.
  • A grid ("xy"): Left and Right step one place in reading order, flipped in rtl, wrapping across rows; Up and Down take the place of the item in the row above or below whose centre is nearest.

With nowhere to go, at an end or for an arrow off the axis, it's index.

Type Parameters

V

V

Parameters

order

readonly ItemData<V>[]

index

number

arrow

Arrow

axis

Axis

direction

"ltr" | "rtl"

Returns

number


check()

function check<V>(
   order, 
   value, 
   movement
): readonly ItemData<V>[];

The order after the item holding value has been dragged offset from its place, moving at velocity: the rule for when a dragged item swaps places. The same order when nothing moves, so a caller can compare by reference.

  • One axis moves the item one place at most: toward the neighbour, of the two next to it, whose centre lies the way it's moving, once its edge facing that neighbour crosses the neighbour's centre. No velocity on the axis moves nothing.
  • A grid ("xy"), as Motion's: the dragged centre picks the nearest row. In another row, the item goes before the first item whose centre is past it in direction; in its own, it steps one place toward the nearest box.

Type Parameters

V

V

Parameters

order

readonly ItemData<V>[]

value

V

movement

Movement

Returns

readonly ItemData<V>[]


detectAxis()

function detectAxis(layouts): Axis;

The axis a list's item boxes are laid out on, as Motion's detectAxis: "x" if some two don't overlap across, "y" if some two don't overlap down, "xy" if both, a grid that wraps, and "y" otherwise. It stops once it has found both.

Parameters

layouts

readonly Box[]

Returns

Axis


isDragging()

function isDragging<V>(group): boolean;

Whether a drag gesture runs in group.

Type Parameters

V

V

Parameters

group

Group<V>

Returns

boolean


isGroup()

function isGroup(u): u is Group<unknown>;

Whether u is a reorder group, made by make.

Parameters

u

unknown

Returns

u is Group<unknown>


isLifted()

function isLifted<V>(group): boolean;

Whether an item of group is lifted, by its keys (a Keyboard reorder, ADR-0037).

Type Parameters

V

V

Parameters

group

Group<V>

Returns

boolean


keyIntent()

function keyIntent(key): Option<KeyIntent>;

What key does to a keyboard reorder, if anything: see KeyIntent.

Parameters

key

Key

Returns

Option<KeyIntent>


liftedValue()

function liftedValue<V>(group): Option<V>;

The lifted value, while a keyboard reorder runs.

Type Parameters

V

V

Parameters

group

Group<V>

Returns

Option<V>


make()

function make<V>(options): Group<V>;

A reorder group over options' values. Each drag sample reads the measured items' boxes and the dragged item's offset, runs check, and on a new order calls onReorder with it, a value with no box kept at its index. After a reorder it waits until the dragged item's box is measured again, so a sample read against the boxes from before can't cross it back.

Type Parameters

V

V

Parameters

options

GroupOptions<V>

Returns

Group<V>

Example

const group = Reorder.make({ values: () => items, onReorder: (next) => (items = next) })

resolveAxis()

function resolveAxis<V>(group): Axis;

The group's axis: its axis, when it's given one, else detectAxis over the measured items' boxes.

Type Parameters

V

V

Parameters

group

Group<V>

Returns

Axis