feat: bilingual support (FA/EN), IRANSans typography, RTL layout, and interactive product detail pages
This commit is contained in:
Vendored
+316
@@ -0,0 +1,316 @@
|
||||
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;
|
||||
Vendored
+206
@@ -0,0 +1,206 @@
|
||||
/** What a field can hold. Store dates as ISO strings or numbers. */
|
||||
export type JsonValue = string | number | boolean | null | JsonValue[] | JsonObject;
|
||||
export interface JsonObject {
|
||||
[field: string]: JsonValue;
|
||||
}
|
||||
/** A document's fields plus its `id`, the last path segment; read-only. */
|
||||
export interface DocData extends JsonObject {
|
||||
id: string;
|
||||
}
|
||||
export type FieldValue = {
|
||||
"#delete": true;
|
||||
} | {
|
||||
"#text": string;
|
||||
} | {
|
||||
"#opaque": {
|
||||
y: "xml" | "map";
|
||||
of: string;
|
||||
};
|
||||
} | {
|
||||
"#increment": number;
|
||||
} | {
|
||||
"#union": JsonValue[];
|
||||
} | {
|
||||
"#remove": JsonValue[];
|
||||
};
|
||||
/**
|
||||
* The fields to write, each key on its own: a dotted key like `"votes.u1"`
|
||||
* sets one nested value, a plain object replaces the whole nested object, a
|
||||
* marker (`deleteField()`, `sharedText()`, `increment()`, `arrayUnion()`,
|
||||
* `arrayRemove()` or another kit's) creates or changes that kind of field,
|
||||
* and `undefined` is skipped. `id` is ignored, so an item can be spread back
|
||||
* in; only the fields the spread changed are written.
|
||||
*/
|
||||
export interface Fields {
|
||||
[field: string]: JsonValue | FieldValue | undefined;
|
||||
}
|
||||
/** Removes a field: `update({ [`votes.${uid}`]: deleteField() })`. */
|
||||
export declare function deleteField(): FieldValue;
|
||||
/** Creates a text everyone can type in together: `add({ body: sharedText() })`. Edit it with `SharedTextarea` or `SharedInput`; the field reads and writes as a string. */
|
||||
export declare function sharedText(initial?: string): FieldValue;
|
||||
/** Adds to a number without losing anyone else's additions: `update({ likes: increment(1) })`; negative subtracts. The field reads as a number, and writing a plain number to it adjusts it by the difference. */
|
||||
export declare function increment(by: number): FieldValue;
|
||||
/** Adds values to an array, each once, without losing anyone else's: `update({ tags: arrayUnion("bug") })`, or `arrayUnion(...tags)` for several. The field reads as an array in the order values were added, and writing a plain array to it adds and removes the difference. */
|
||||
export declare function arrayUnion(...values: JsonValue[]): FieldValue;
|
||||
/** Removes values from an array: `update({ tags: arrayRemove("bug") })`. */
|
||||
export declare function arrayRemove(...values: JsonValue[]): FieldValue;
|
||||
export interface SharedDoc {
|
||||
/** False until the document is created with `set`; `update` throws before that. */
|
||||
exists: boolean;
|
||||
data: DocData;
|
||||
/** Creates or replaces the whole document. */
|
||||
set(fields: Fields): void;
|
||||
/** Merges into the document; throws if it does not exist. */
|
||||
update(fields: Fields | ((current: DocData) => Fields)): void;
|
||||
/** Deletes the document and its subcollections; nothing happens if it does not exist. */
|
||||
delete(): void;
|
||||
}
|
||||
/**
|
||||
* One document at a path like `game/state` or `cards/k3f9`; `defaults` fill
|
||||
* in missing top-level fields and are never written. A null path gives empty
|
||||
* data and methods that do nothing; suspends until the shared state has
|
||||
* loaded.
|
||||
*/
|
||||
export declare function useSharedDoc(path: string | null | undefined, defaults?: Fields): SharedDoc;
|
||||
export interface SharedCollection {
|
||||
/** The child documents, in order, as of this render; `shared.collection(path).items` is always current. */
|
||||
items: readonly DocData[];
|
||||
/** Adds a document at the end and returns its id. Pass `id` in the fields to choose it. */
|
||||
add(fields: Fields): string;
|
||||
/** Creates or replaces the document with this `id`; returns the id. */
|
||||
set(id: string, fields: Fields): string;
|
||||
/** Merges into the item; throws if the id is unknown. */
|
||||
update(id: string, fields: Fields | ((current: DocData) => Fields)): void;
|
||||
/** Deletes the item and its subcollections; nothing happens if the id is unknown. */
|
||||
delete(id: string): void;
|
||||
/** Moves an item to an index, or before or after another item; `items` returns the new order. Throws if either id is unknown. */
|
||||
move(id: string, to: number | {
|
||||
before: string;
|
||||
} | {
|
||||
after: string;
|
||||
}): void;
|
||||
}
|
||||
/**
|
||||
* The documents at a collection path like `cards` or `cards/k3f9/steps`, in
|
||||
* order, and the methods to change them. A null path gives no items and
|
||||
* methods that do nothing; suspends until the shared state has loaded.
|
||||
*/
|
||||
export declare function useSharedCollection(path: string | null | undefined): SharedCollection;
|
||||
/** One person viewing the artifact right now: who they are and their presence. */
|
||||
export interface Collaborator {
|
||||
user: User;
|
||||
/** Their presence; `{}` until they set one. */
|
||||
presence: JsonObject;
|
||||
}
|
||||
/**
|
||||
* This viewer: `user` is `clickup.viewer`, `presence` is what everyone else
|
||||
* sees of them live, `{}` until set, never saved.
|
||||
*/
|
||||
export declare function useMe(): Collaborator & {
|
||||
/** Sets what everyone else sees of you, like a `useState` setter: pass a new value or a function of the current one. Spread the current one to keep the keys other components write: `cursor`, `message` and `selection` from `Cursors`, `slide` from a slides `Deck`. */
|
||||
setPresence(presence: JsonObject | ((current: JsonObject) => JsonObject)): void;
|
||||
};
|
||||
/**
|
||||
* Everyone else viewing the artifact right now, one entry per person. With
|
||||
* `select`, only what it returns, re-rendering when that changes:
|
||||
* `useCollaborators((all) => all.filter((c) => c.presence.editing === id))`.
|
||||
*/
|
||||
export declare function useCollaborators<T = readonly Collaborator[]>(select?: (collaborators: readonly Collaborator[]) => T): T;
|
||||
/**
|
||||
* Runs `listener` for each `name` broadcast another person sends while
|
||||
* mounted, with the payload and who sent it:
|
||||
* `useBroadcast("buzz", (payload, from) => flash(from.name))`. Broadcasts
|
||||
* are for what happens, documents for what must survive; a listener runs on
|
||||
* every other page, so a write in it lands once per viewer.
|
||||
*/
|
||||
export declare function useBroadcast(name: string, listener: (payload: JsonValue, from: User) => void): void;
|
||||
/**
|
||||
* Undo and redo this viewer's own changes; all writes from one event handler
|
||||
* are one step. While `canEdit` is false, both do nothing and both flags are
|
||||
* false.
|
||||
*/
|
||||
export declare function useUndo(): {
|
||||
undo(): void;
|
||||
redo(): void;
|
||||
canUndo: boolean;
|
||||
canRedo: boolean;
|
||||
};
|
||||
export interface SharedStatus {
|
||||
/** `connecting` until the shared state has loaded, and again while reconnecting; `disconnected` on a host without collaboration; otherwise the host's connection state. */
|
||||
connection: "connecting" | "connected" | "disconnected";
|
||||
/** False while loading, in read-only rooms and in outdated builds. Writes are then ignored. */
|
||||
canEdit: boolean;
|
||||
/** True when a newer build of this artifact is live. Ask the viewer to reload. */
|
||||
outdated: boolean;
|
||||
}
|
||||
/** Whether the shared state is connected, whether you can edit it, and whether a newer build is live. */
|
||||
export declare function useSharedStatus(): SharedStatus;
|
||||
/**
|
||||
* Shared state outside React: the same documents, collections, presence,
|
||||
* broadcasts, undo and status as the hooks, always current. Use it in a draw
|
||||
* loop or in an event listener set up outside a component; in render, use
|
||||
* the hooks.
|
||||
*/
|
||||
export declare const shared: {
|
||||
/** The document at `path`, as `useSharedDoc` gives it; `data` and `exists` read the current state. */
|
||||
doc(path: string | null | undefined, defaults?: Fields): SharedDoc;
|
||||
/** The collection at `path`, as `useSharedCollection` gives it; `items` reads the current state. */
|
||||
collection(path: string | null | undefined): SharedCollection;
|
||||
/** This viewer, as `useMe` gives them. */
|
||||
readonly me: Collaborator & {
|
||||
setPresence(presence: JsonObject | ((current: JsonObject) => JsonObject)): void;
|
||||
};
|
||||
readonly collaborators: readonly Collaborator[];
|
||||
/** Sends `name` with `payload` to everyone else viewing the artifact right now; never saved, never replayed to anyone who arrives later. */
|
||||
broadcast(name: string, payload?: JsonValue): void;
|
||||
/** Calls `listener` with each `name` broadcast another person sends, as `useBroadcast` does. Returns a function that stops the calls. */
|
||||
onBroadcast(name: string, listener: (payload: JsonValue, from: User) => void): () => void;
|
||||
readonly status: SharedStatus;
|
||||
undo(): void;
|
||||
redo(): void;
|
||||
readonly canUndo: boolean;
|
||||
readonly canRedo: boolean;
|
||||
/** Calls `listener` after every change to documents, presence, status or history. Returns a function that stops the calls. */
|
||||
subscribe(listener: () => void): () => void;
|
||||
/** Resolves once the shared state has loaded; until then writes are ignored. */
|
||||
ready(): Promise<void>;
|
||||
};
|
||||
|
||||
/** A person, the same shape as the ClickUp kit's `User`. */
|
||||
export 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;
|
||||
}
|
||||
|
||||
/** A stored value of mine outside React: `get()`, `set()` and `remove()`. */
|
||||
export interface MyState<T> {
|
||||
/** The stored value, or `initial` while nothing is stored. */
|
||||
get(): T;
|
||||
/** Replaces the value; a function receives the current one, like a `useState` setter. */
|
||||
set(next: T | ((current: T) => T)): void;
|
||||
/** Deletes the key; `get()` returns `initial` again. */
|
||||
remove(): void;
|
||||
}
|
||||
/**
|
||||
* A value of mine that survives a reload, `useState` with a key:
|
||||
* `const [tab, setTab] = useMyState("tab", "board")`, stored per viewer per
|
||||
* artifact as JSON, up to 4,000 characters per key (`set` throws past that).
|
||||
* Suspends until the stored value has loaded, so the first render never shows
|
||||
* `initial` over a stored value; a key mounted later needs its own
|
||||
* `<Suspense>`.
|
||||
*/
|
||||
export declare function useMyState<T>(key: string, initial: T): [T, (next: T | ((current: T) => T)) => void];
|
||||
/**
|
||||
* The same value outside React, always current: `myState("tab", "board").get()`
|
||||
* in a draw loop or event listener, `.set(v)` to write it, `.remove()` to
|
||||
* delete the key. Starts loading the value when called.
|
||||
*/
|
||||
export declare function myState<T>(key: string, initial: T): MyState<T>;
|
||||
export declare function myState<T>(key: string): MyState<T | undefined>;
|
||||
/** A value the kit keeps for this viewer under a `#` key, which authors are refused; resolves once the stored value has loaded. */
|
||||
declare function kitState<T>(key: `#${string}`, initial: T): Promise<MyState<T>>;
|
||||
Reference in New Issue
Block a user