Files
landing-ply/kits/sync/docs/components.d.ts
T

317 lines
16 KiB
TypeScript
Raw Normal View History

import type { JsonObject } from "./shared";
import type { CSSProperties, InputHTMLAttributes, ReactElement, ReactNode, RefObject, TextareaHTMLAttributes } from "react";
/** A person, the same shape as the ClickUp kit's `User`. */
interface User {
kind: "user";
id: string;
name: string;
/** Link to the person's profile; empty when the host does not name one. */
url: string;
/** Avatar URL. */
image?: string;
}
/** Where a pointer is, as `<Cursors>` sends it in `presence.cursor` and `cursor:move`: fractions of its region or, with `anchor`, of that element's box, the region it is in and the sender's clock in ms. Over a surface seen through a camera, `space: "page"` and that surface's own units instead. */
type CursorPoint = JsonObject & {
x: number;
y: number;
t?: number;
region?: string;
space?: "page";
/** `#id` of the nearest ancestor with a token id, or `""` for the region itself, then `tag:n` steps down to the element. */
anchor?: string[];
dx?: number;
dy?: number;
};
/** One of the six emoji, sent from where the pointer was. */
type Emote = CursorPoint & {
emoji: string;
};
/** The point two hands meet at, and whose hand the sender's meets. */
type HighFive = CursorPoint & {
with: string;
};
/** The sender's wave starting or ending, in the region it started in. */
interface Wave {
region?: string;
on: boolean;
}
/** Text someone selected inside a `<Cursors>` area, as `presence.selection`: the path of each end from that area (child indices, then the offset in the last node) and a hash of the text, so it is drawn only over the same text. */
type TextSelection = JsonObject & {
from: number[];
to: number[];
hash: string;
region?: string;
};
/** What an `id` must look like to travel: generated ids pass, names and titles do not. */
declare const TOKEN: RegExp;
declare const MAX_STEPS = 24;
declare function fraction(value: number): number;
/** The region key a point or a wave carries, when it has one. */
declare function regionOf(region: string | undefined): JsonObject;
/** The units a `<Cursors>` reads and writes: its region's fractions, or with a `space`, the surface's own units tagged `"page"`. */
type Units = "page" | undefined;
/** `value` when it is a cursor point in `region` and `units` as `<Cursors>` sends one; an anchor this page cannot walk is left out. */
declare function pointOf(value: unknown, region: string | undefined, units?: Units): CursorPoint | undefined;
/** `payload` when it is a `cursor:emote` in `region` and `units`. */
declare function emoteOf(payload: unknown, region: string | undefined, units: Units): Emote | undefined;
/** `payload` when it is a `cursor:high-five` in `region` and `units`. */
declare function highFiveOf(payload: unknown, region: string | undefined, units: Units): HighFive | undefined;
/** `payload` when it is a `cursor:wave` in `region`. */
declare function waveOf(payload: unknown, region: string | undefined): Wave | undefined;
/** The presence's cursor while it is in `region` and `units`. */
declare function cursorOf(presence: JsonObject, region: string | undefined, units?: Units): CursorPoint | undefined;
/** The presence's chat message while they are typing one. */
declare function messageOf(presence: JsonObject): string | undefined;
/** The presence's selection when it has the shape `<Cursors>` writes and is in `region`. */
declare function selectionOf(presence: JsonObject, region: string | undefined): TextSelection | undefined;
/** My three presence keys as I have them now; every publish sends all three and leaves the rest of my presence alone. */
declare const mine: {
cursor?: CursorPoint;
message?: string;
selection?: TextSelection;
};
/** Notes where my pointer is without publishing it; the next publish carries it. */
declare function moved(cursor: CursorPoint): void;
/** Sets the given keys of mine, `undefined` taking one away, and publishes all of them. */
declare function publish(update?: Partial<typeof mine>): void;
/** A position in px of the surface a cursor is drawn on. */
interface Point {
x: number;
y: number;
}
/** The surface a cursor is drawn on: the units of the points it reads and writes, and where such a point falls on it, in px, as it lays out now. */
interface Space {
readonly units: Units;
resolve(sample: CursorPoint): Point;
/** Whether the viewer looks at the surface through a camera of their own; a target that moved since last frame is then followed at once rather than eased. */
readonly camera?: boolean;
}
/**
* Replays one person's cursor samples on the sender's clock, behind by a
* delay that grows when a sample lands late and shrinks every two seconds.
* Between samples it moves straight, and whatever the target moves that the
* pointer did not becomes an offset that decays instead of a jump; through a
* camera the cursor stays on the artwork.
*/
declare class Glide {
private samples;
/** Local clock minus sender clock, the smallest seen: the least delayed delivery. */
private offset;
private delay;
/** The smallest lead the buffer had when a sample landed since the last review. */
private lead;
private reviewedAt;
private lastNow;
/** What was aimed at last frame, to tell a shift of this page from the pointer's own motion. */
private aimed;
/** The offset still to decay and its velocity. */
private residual;
private residualVelocity;
/** Adds a sample as it lands; without a sender clock its arrival time stands in. Starts over when the sender's clock goes backwards or a sample lands more than the longest delay sooner than any before. */
push(point: CursorPoint, arrivedAt: number): void;
/** Where to draw at local time `now` on `space`, or undefined before the first sample. */
draw(now: number, space: Space): Point | undefined;
/** Forgets the offset being decayed. */
rest(): void;
/** The hops of the anchor crossings between sender times `from` and `to`: how far each lies from where the pointer's own step would have carried the cursor. */
private hopped;
/** The position at sender time `T`: linear between the samples around it, inside one anchor along the fractions of its box, across anchors by the pointer's own step from the earlier sample. */
private place;
/** The samples around sender time `T`: `p1` at or before it, `p2` after it when there is one, and how far between them `T` lies. */
private locate;
private playhead;
/** Grows the delay by how far behind the playhead this sample landed, if it did, and reviews the delay every two seconds. */
private adapt;
}
/**
* Replays one collaborator's cursor on a surface, gliding (or jumping under
* reduced motion) and fading `IDLE_MS` after the last sample; each frame's
* position goes to `onFrame`, undefined once faded. `stop()` only stops: the
* host removes what it drew.
*/
declare class Replay {
private readonly space;
private readonly onFrame;
private readonly glide;
private frame;
private idle;
private last;
private faded;
/** The newest sample, drawn as is under reduced motion. */
private rest;
constructor(space: Space, onFrame: (at: Point | undefined) => void);
follow(point: CursorPoint): void;
/** Shows the cursor where it was, draws it every frame, and starts the `IDLE_MS` to fading it. */
wake(): void;
stop(): void;
private readonly fade;
/** Draws where the samples fall on the surface now, each frame while the cursor shows. */
private readonly draw;
}
/**
* How a `<Cursors>` over a surface each viewer looks at through their own
* camera (an `<svg viewBox>`, a `<canvas>`, a panned and zoomed layer) maps
* the viewport to the units positions are shared in, and back; both are pure
* and read as the camera stands at the moment of the call.
*/
export interface CursorSpace {
/** A point of my viewport as the units I publish: the chart, page or world coordinate under it. */
toContent(clientX: number, clientY: number): Point;
/** A published point as client px, where my camera shows it now. */
toClient(x: number, y: number): Point;
}
/** A surface seen through a camera: a wire point falls where the host's camera shows its units now, in px of the overlay. */
declare class CameraSpace implements Space {
private readonly overlay;
private readonly space;
readonly units: Units;
readonly camera = true;
constructor(overlay: () => HTMLElement, space: () => CursorSpace);
resolve(point: CursorPoint): Point;
}
declare function finite(at: Point): Point | undefined;
export interface Cursor {
user: User;
/** Their colour, the same as their `Avatar`. */
color: string;
presence: JsonObject;
/** The default arrow and name, to decorate or to return for some people. */
arrow: ReactNode;
}
export interface CursorsProps {
/** Press `/` to type a message everyone sees at your cursor; Escape closes it. On by default. */
chat?: boolean;
/** Press `1`–`6` to react with an emoji at your cursor (`E` shows which key is which), wiggle the pointer to wave, and rest it on someone's cursor to high five; a hint of the keys shows once. On by default. */
emotes?: boolean;
/** A selector for the element positions are fractions of, inside the element this covers; that element itself by default. A fixed-aspect board centred in a responsive page keeps cursors exact on every screen, and pointers beside it show beside it. */
frame?: string;
/** Names the space this covers, like `slide-${index}`: cursors written here carry it, and only cursors from the same region are drawn. Lets several `<Cursors>` share a page. */
region?: string;
/** Draws one collaborator's cursor in place of the default arrow and name. */
render?: (cursor: Cursor) => ReactNode;
/** Text you select here is highlighted in your colour for everyone else, where their page shows the same text. On by default. */
selection?: boolean;
/** For a canvas each viewer pans and zooms for themselves (an `<svg viewBox>`, a `<canvas>`, a transformed layer): `toContent(clientX, clientY)` maps my pointer to the units positions are shared in and `toClient(x, y)` maps a shared point back to my screen, so everyone's cursors ride the artwork through my camera. Positions then carry no anchor and `frame` is ignored. */
space?: CursorSpace;
}
/**
* Shows everyone else's pointer over the parent element (give it
* `className="relative"`), or over the whole page when no parent is
* positioned; never your own. Each cursor is an arrow with the person's name
* in their colour that follows their movement a moment behind and fades when
* they stop, pointing at the same element on every screen size and scroll
* offset, at the same fraction of `frame`, or on the same artwork through
* each viewer's own camera with a `space`.
*/
export declare function Cursors({ chat, emotes, frame, region, render, selection, space, }: CursorsProps): ReactElement;
/** An open hand in `color`, its wrist on the parent's origin, and the `Puppet` that animates it as the ref; `arrow` is the arrow the hand stands in for. */
declare const HandSlot: import("react").ForwardRefExoticComponent<{
arrow?: RefObject<Element>;
color: string;
} & import("react").RefAttributes<Puppet>>;
/** The hand's pose from the pointer's horizontal velocity: it swings with the motion, leans into it and squashes on each turn. */
declare class Swing {
private angle;
private velocity;
private lastX;
private lastT;
/** A position sample in px at time `t` in ms. */
push(x: number, t: number): void;
/** The transform for this frame; the velocity decays while no sample lands. */
pose(now: number): string;
reset(): void;
}
/** When the hands meet inside the clap clip. */
declare const IMPACT_MS = 440;
/** Drives one cursor's hand and arrow: a small hand beside the tip while a high five is on offer, the hand in place of the arrow while its owner waves, and the high-five clip. */
declare class Puppet {
private readonly slot;
private readonly svg;
private readonly arrow;
private readonly swing;
private readonly running;
private state;
/** The side a high five on offer wants the small hand on, kept through a clap. */
private armed;
private frame;
private bob;
private watchdog;
constructor(slot: RefObject<HTMLDivElement>, svg: RefObject<SVGSVGElement>, arrow: RefObject<Element>);
/** Shows a small hand beside the tip while a high five is on offer, on `side` of it: the side this hand will come in from. */
arm(on: boolean, side?: 1 | -1): void;
wave(on: boolean): void;
/** A position sample of the hand's owner, in px at time `t`, for the swing; while waving it also keeps the hand from timing out. */
track(x: number, t: number): void;
/** Runs the high-five clip: the hand comes in from `side` of the meeting point `to` (relative to the tip, in px), meets the other, recoils and comes back; resolves when it is back. */
clap(to: Point, side: 1 | -1): Promise<void>;
stop(): void;
/** Ends the bob, the swing and its watchdog. */
private still;
private readonly draw;
/** Names the hand's state on its slot, and the glow on the arrow while a high five is on offer. */
private mark;
/** Animates the slot to `to`: from `from` while the hand is hidden, else from wherever its last animation left it. */
private enter;
/** Animates `element` through `frames`, from wherever its last animation left it. */
private run;
}
/** The clap's impact, centred on the parent's origin: dots in the two colours flying out and a ring widening; `onEnd` when they are gone. */
declare function Burst({ colors, onEnd, }: {
colors: string[];
onEnd: () => void;
}): ReactElement;
/** One person's cursor as drawn, in em of the parent: the arrow with its tip at the origin, name and message in a pill of `colors` beside it, and the hand the `Puppet` ref drives. */
declare const CursorGlyph: import("react").ForwardRefExoticComponent<{
colors: {
backgroundColor: string;
color: string;
};
message: string | undefined;
user: User;
} & import("react").RefAttributes<Puppet>>;
/**
* The arrow `Cursors` draws: its tip is the top-left corner, its fill the
* current text colour, its edge white. Size it with `className`, `h-5` say,
* to build a cursor of your own.
*/
export declare function CursorArrow({ className, style, }: {
className?: string;
style?: CSSProperties;
}): ReactElement;
/** A full-size box translated to `at`, px of the surface, or left for the host to place and fade when `at` is not given; its content sits at the box's corner. */
declare const Layer: import("react").ForwardRefExoticComponent<{
at?: Point;
children: ReactNode;
} & import("react").RefAttributes<HTMLDivElement>>;
/** What sits at my pointer while `at` is given, and for LINGER_MS after so it can pop out; `children` is told when it is leaving. */
declare function AtPointer({ at, children, }: {
at: Point | undefined;
children: (leaving: boolean) => ReactNode;
}): ReactElement | null;
export interface SharedTextareaProps extends TextareaHTMLAttributes<HTMLTextAreaElement> {
/** The document path, like `cards/${card.id}`. */
doc: string;
field: string;
}
/**
* A textarea for a `sharedText()` field; read-only for any other field.
* Edits apply to the shared text at once (IME input at composition end),
* `onChange` fires afterwards with the resulting value, the caret survives
* remote edits, and `value` and `defaultValue` are ignored.
*/
export declare function SharedTextarea({ doc, field, ...attributes }: SharedTextareaProps): ReactElement;
export interface SharedInputProps extends InputHTMLAttributes<HTMLInputElement> {
doc: string;
field: string;
}
/** A one-line input for a `sharedText()` field; read-only for any other field. `value`, `defaultValue` and `type` are ignored: it is always `type="text"`, the one input type whose caret the binding can follow. */
export declare function SharedInput({ doc, field, ...attributes }: SharedInputProps): ReactElement;