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 `` 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 `` 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 `` 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 `` 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 `` 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): 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 `` over a surface each viewer looks at through their own * camera (an ``, a ``, 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 `` 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 ``, a ``, 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; color: string; } & import("react").RefAttributes>; /** 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, svg: RefObject, arrow: RefObject); /** 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; 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>; /** * 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>; /** 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 { /** 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 { 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;