@q9labsai/chalk-react 5.1.0 → 5.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +102 -0
- package/dist/bindings/hooks.d.ts +2 -0
- package/dist/{chunk-QDEKMRH2.js → chunk-CYC2G42K.js} +66 -58
- package/dist/{chunk-XOHNIYSR.js → chunk-G62BKRNX.js} +1534 -948
- package/dist/{chunk-ZFWIMKUC.js → chunk-HWQLW4JR.js} +55 -7
- package/dist/components/board/BoardMenu.d.ts +4 -1
- package/dist/components/board/board-draw-policy.d.ts +15 -0
- package/dist/components/board/board-model.d.ts +5 -1
- package/dist/components/board/board-view-versions.d.ts +1 -7
- package/dist/components/chalk/Chalk.d.ts +4 -0
- package/dist/components/chalk/ReportProblemDialog.d.ts +12 -0
- package/dist/components/chalk/StatusSurface.d.ts +3 -2
- package/dist/components/chalk-ui/ChalkSheet.d.ts +1 -1
- package/dist/components/chalk-ui/ChalkToast.d.ts +7 -1
- package/dist/components/control-bar/command-failure.d.ts +105 -1
- package/dist/components/control-bar/control-bar-behavior.d.ts +5 -1
- package/dist/components/index.js +3 -3
- package/dist/components/offline-actions/OfflineActions.d.ts +2 -1
- package/dist/components/space-header/CallDetails.d.ts +4 -2
- package/dist/components/space-header/space-header-contract.d.ts +5 -0
- package/dist/components/space-view/SpaceView.d.ts +8 -3
- package/dist/components/stage/Stage.d.ts +1 -1
- package/dist/components/stage/stage-items.d.ts +4 -0
- package/dist/components/stage/stage-layout.d.ts +6 -2
- package/dist/components/toast-stack/CallEventIcon.d.ts +1 -1
- package/dist/components/toast-stack/NoticeButton.d.ts +7 -0
- package/dist/components/toast-stack/ToastStack.d.ts +13 -2
- package/dist/components/toast-stack/call-events.d.ts +2 -1
- package/dist/components/toast-stack/notice-announcement.d.ts +4 -0
- package/dist/components/toast-stack/toast-layout-snapshot.d.ts +4 -0
- package/dist/components/toast-stack/useToastCapacity.d.ts +7 -0
- package/dist/headless.js +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.js +24 -3
- package/dist/internal/whiteboard-view-limits.d.ts +1 -0
- package/dist/public-surface.contract.d.ts +1 -1
- package/dist/test-support/index.js +3 -3
- package/package.json +5 -5
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
// src/components/stage/stage-layout.ts
|
|
2
2
|
var WIDESCREEN = 16 / 9;
|
|
3
|
-
var SLIM_STRIP_PX = 52;
|
|
4
3
|
var MORE_TILE_ID = "stage:more";
|
|
5
4
|
var DEFAULT_GRID_OPTIONS = { gap: 8, minTileWidth: 150, minTileHeight: 96, maxPerPage: Number.POSITIVE_INFINITY, minAspect: 0.8, maxAspect: 2 };
|
|
6
5
|
var DEFAULT_SPOTLIGHT_OPTIONS = { gap: 8, stripRatio: 0.17, minStripHeight: 104, maxStripHeight: 150, stripAspect: WIDESCREEN };
|
|
@@ -56,11 +55,13 @@ function fitGrid(box, ids, options, wideIds = []) {
|
|
|
56
55
|
let plan = null;
|
|
57
56
|
let overflowCount = 0;
|
|
58
57
|
const weight = (id) => wideIds.includes(id) ? 2 : 1;
|
|
59
|
-
|
|
58
|
+
const lastContent = ids.reduce((last, id, index2) => wideIds.includes(id) ? index2 : last, -1);
|
|
59
|
+
const fewest = Math.min(ids.length, Math.max(wideIds.includes(ids[0] ?? "") ? 2 : 1, lastContent + 2));
|
|
60
|
+
for (let count = Math.min(ids.length, Math.floor(options.maxPerPage)); count >= 1; count -= 1) {
|
|
60
61
|
overflowCount = count < ids.length ? ids.length - count + 1 : 0;
|
|
61
62
|
keys = overflowCount ? [...ids.slice(0, count - 1), MORE_TILE_ID] : ids;
|
|
62
63
|
plan = rowPlan(box, keys.map(weight), options);
|
|
63
|
-
if (plan && (plan.minWidth >= options.minTileWidth && plan.height >= options.minTileHeight || count <=
|
|
64
|
+
if (plan && (plan.minWidth >= options.minTileWidth && plan.height >= options.minTileHeight || count <= fewest)) break;
|
|
64
65
|
}
|
|
65
66
|
if (!plan) return EMPTY_GEOMETRY;
|
|
66
67
|
let height = plan.height;
|
|
@@ -90,15 +91,52 @@ function contain(box, aspect) {
|
|
|
90
91
|
const width = Math.min(box.width, box.height * aspect);
|
|
91
92
|
return { width, height: width / aspect };
|
|
92
93
|
}
|
|
94
|
+
var FLOAT_SPEAKER = { width: 120, height: 68 };
|
|
95
|
+
var FLOAT_PERSON = { width: 76, height: 44 };
|
|
96
|
+
var FLOAT_MAX_PEOPLE = 3;
|
|
97
|
+
var FLOAT_TOP = 56;
|
|
98
|
+
var FLOAT_BOTTOM = 8 + 64 + 60;
|
|
99
|
+
function floatPeople(box, primaryId, stripIds, options) {
|
|
100
|
+
const content = stripIds.filter((id) => options.wideIds?.includes(id));
|
|
101
|
+
const people = stripIds.filter((id) => !content.includes(id));
|
|
102
|
+
const speaker = options.speakerId && options.speakerId !== options.localId && people.includes(options.speakerId) ? options.speakerId : null;
|
|
103
|
+
const local = options.localId && people.includes(options.localId) ? options.localId : null;
|
|
104
|
+
const others = people.filter((id) => id !== speaker && id !== local);
|
|
105
|
+
const ordered = [...speaker ? [speaker] : [], ...others, ...local ? [local] : []];
|
|
106
|
+
const fold = (max2) => {
|
|
107
|
+
if (content.length + ordered.length <= max2) return [...content, ...ordered];
|
|
108
|
+
const shares = content.slice(0, max2 - 1);
|
|
109
|
+
const slots = max2 - 1 - shares.length;
|
|
110
|
+
const faces = ordered.length <= slots ? ordered : slots > 0 ? [...ordered.slice(0, slots - 1), ...ordered.slice(-1)] : [];
|
|
111
|
+
return [...shares, ...faces, MORE_TILE_ID];
|
|
112
|
+
};
|
|
113
|
+
const sizeOf = (id) => id === speaker || content.includes(id) ? FLOAT_SPEAKER : FLOAT_PERSON;
|
|
114
|
+
const bottomOf = (ids) => ids.reduce((y2, id) => y2 + sizeOf(id).height + options.gap, FLOAT_TOP) - options.gap;
|
|
115
|
+
let max = Math.max(1, Math.min(content.length + FLOAT_MAX_PEOPLE, options.maxPerPage ?? Infinity));
|
|
116
|
+
const minimum = Number.isFinite(options.maxPerPage) ? 1 : content.length + 1;
|
|
117
|
+
while (max > minimum && bottomOf(fold(max)) > box.height - FLOAT_BOTTOM) max -= 1;
|
|
118
|
+
const column = fold(max);
|
|
119
|
+
if (bottomOf(column) > box.height - FLOAT_BOTTOM) return null;
|
|
120
|
+
let y = FLOAT_TOP;
|
|
121
|
+
const floats = column.map((id) => {
|
|
122
|
+
const size = sizeOf(id);
|
|
123
|
+
const frame = { id, role: "float", page: 0, x: box.width - 12 - size.width, y, ...size };
|
|
124
|
+
y += size.height + options.gap;
|
|
125
|
+
return frame;
|
|
126
|
+
});
|
|
127
|
+
const shown = floats.filter((frame) => frame.id !== MORE_TILE_ID).length;
|
|
128
|
+
return { frames: [{ id: primaryId, role: "primary", page: 0, x: 0, y: 0, ...box }, ...floats], pageCount: 1, perPage: floats.length, overflowCount: stripIds.length - shown };
|
|
129
|
+
}
|
|
93
130
|
function fitSpotlight(box, primaryId, stripIds, options) {
|
|
94
131
|
const phone = box.width <= 640;
|
|
132
|
+
const maxPerPage = Math.floor(options.maxPerPage ?? Infinity);
|
|
95
133
|
const aspect = options.primaryAspect === void 0 ? WIDESCREEN : options.primaryAspect;
|
|
96
|
-
if (!stripIds.length) {
|
|
134
|
+
if (!stripIds.length || maxPerPage <= 0) {
|
|
97
135
|
const aspect2 = clamp(box.width / box.height, 4 / 3, WIDESCREEN);
|
|
98
136
|
const fitted = contain(box, aspect2);
|
|
99
137
|
const width = options.content || phone ? box.width : Math.min(box.width * 0.56, fitted.width);
|
|
100
138
|
const height = options.content || phone ? box.height : width / aspect2;
|
|
101
|
-
return { frames: [{ id: primaryId, role: "primary", page: 0, x: (box.width - width) / 2, y: (box.height - height) / 2, width, height }], pageCount: 1, perPage: 0 };
|
|
139
|
+
return { frames: [{ id: primaryId, role: "primary", page: 0, x: (box.width - width) / 2, y: (box.height - height) / 2, width, height }], pageCount: 1, perPage: 0, overflowCount: stripIds.length };
|
|
102
140
|
}
|
|
103
141
|
if (phone && stripIds.length === 1 && !options.content) {
|
|
104
142
|
return {
|
|
@@ -110,15 +148,17 @@ function fitSpotlight(box, primaryId, stripIds, options) {
|
|
|
110
148
|
perPage: 1
|
|
111
149
|
};
|
|
112
150
|
}
|
|
151
|
+
const floating = phone && options.floatingPhonePeople ? floatPeople(box, primaryId, stripIds, { ...options, maxPerPage }) : null;
|
|
152
|
+
if (floating) return floating;
|
|
113
153
|
const slim = phone && options.slimPhoneStrip === true;
|
|
114
|
-
const strip = slim ?
|
|
154
|
+
const strip = slim ? 52 : phone ? Math.round(box.height * 0.3) : clamp(box.height * options.stripRatio, options.minStripHeight, options.maxStripHeight);
|
|
115
155
|
const column = phone && options.content ? 120 : clamp(box.width * 0.15, 180, 260);
|
|
116
156
|
const belowMain = options.content ? contain({ width: box.width, height: box.height - strip - options.gap }, aspect) : { width: box.width, height: box.height - strip - options.gap };
|
|
117
157
|
const besideMain = options.content ? contain({ width: box.width - column - options.gap, height: box.height }, aspect) : { width: box.width - column - options.gap, height: box.height };
|
|
118
158
|
const beside = options.content ? besideMain.width * besideMain.height > belowMain.width * belowMain.height * 1.005 : !phone && Math.abs(Math.log(besideMain.width / besideMain.height / WIDESCREEN)) < Math.abs(Math.log(belowMain.width / belowMain.height / WIDESCREEN));
|
|
119
159
|
const main = { width: beside ? besideMain.width : box.width, height: beside ? box.height : belowMain.height };
|
|
120
160
|
const remaining = { width: beside ? box.width - main.width - options.gap : box.width, height: beside ? box.height : box.height - main.height - options.gap };
|
|
121
|
-
const people = fitGrid(remaining, stripIds, { ...DEFAULT_GRID_OPTIONS, minTileWidth: phone ? 88 : 150, minTileHeight: slim ?
|
|
161
|
+
const people = fitGrid(remaining, stripIds, { ...DEFAULT_GRID_OPTIONS, minTileWidth: phone ? 88 : 150, minTileHeight: slim ? 52 : phone ? 72 : 96, maxPerPage }, options.wideIds);
|
|
122
162
|
return {
|
|
123
163
|
...people,
|
|
124
164
|
frames: [{ id: primaryId, role: "primary", page: 0, x: 0, y: 0, ...main }, ...people.frames.map((frame) => ({ ...frame, role: "strip", x: frame.x + (beside ? main.width + options.gap : 0), y: frame.y + (beside ? 0 : main.height + options.gap) }))]
|
|
@@ -182,6 +222,12 @@ function choosePrimary(items, context) {
|
|
|
182
222
|
if (content) return content;
|
|
183
223
|
return participantWhere(items, (participant) => participant.isActiveSpeaker === true) ?? participantWhere(items, (participant) => participant.isSpeaking === true) ?? (context.lastSpeakerId ? items.find((item) => item.kind === "participant" && item.id === context.lastSpeakerId) : void 0) ?? participantWhere(items, (participant) => !participant.isLocal) ?? participantWhere(items, () => true) ?? items[0] ?? null;
|
|
184
224
|
}
|
|
225
|
+
function speakingParticipantId(items, accept = () => true) {
|
|
226
|
+
return (participantWhere(items, (participant) => accept(participant) && participant.isActiveSpeaker === true) ?? participantWhere(items, (participant) => accept(participant) && participant.isSpeaking === true))?.id;
|
|
227
|
+
}
|
|
228
|
+
function floatingSpeakerId(items, lastRemoteSpeakerId) {
|
|
229
|
+
return speakingParticipantId(items, (participant) => !participant.isLocal) ?? lastRemoteSpeakerId;
|
|
230
|
+
}
|
|
185
231
|
function gridOrder(items, pinnedId) {
|
|
186
232
|
if (!pinnedId) return [...items];
|
|
187
233
|
const pinned = items.find((item) => item.id === pinnedId);
|
|
@@ -201,5 +247,7 @@ export {
|
|
|
201
247
|
isContentItem,
|
|
202
248
|
stabilizeOrder,
|
|
203
249
|
choosePrimary,
|
|
250
|
+
speakingParticipantId,
|
|
251
|
+
floatingSpeakerId,
|
|
204
252
|
gridOrder
|
|
205
253
|
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { WhoCanDraw } from "./board-draw-policy";
|
|
1
2
|
import { type BoardSurface, type IslandPlace } from "./board-model";
|
|
2
3
|
export type BoardMenuProps = {
|
|
3
4
|
readonly surface: BoardSurface;
|
|
@@ -7,9 +8,11 @@ export type BoardMenuProps = {
|
|
|
7
8
|
readonly onPlace: (place: IslandPlace) => void;
|
|
8
9
|
readonly onDownload: () => void;
|
|
9
10
|
readonly onCopy: () => void;
|
|
11
|
+
/** Offered to hosts. */
|
|
12
|
+
readonly whoCanDraw?: WhoCanDraw;
|
|
10
13
|
readonly onBring?: () => void;
|
|
11
14
|
readonly onClear?: () => void;
|
|
12
15
|
readonly onClose?: () => void;
|
|
13
16
|
};
|
|
14
17
|
/** The board's own menu: export, the person's own appearance, and the host's board actions. */
|
|
15
|
-
export declare function BoardMenu({ surface, onSurface, place, onPlace, onDownload, onCopy, onBring, onClear, onClose }: BoardMenuProps): import("react").JSX.Element;
|
|
18
|
+
export declare function BoardMenu({ surface, onSurface, place, onPlace, onDownload, onCopy, whoCanDraw, onBring, onClear, onClose }: BoardMenuProps): import("react").JSX.Element;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { ChalkWhiteboardDrawPolicy } from "@q9labsai/chalk-client";
|
|
2
|
+
import type { BoardToast } from "./board-toasts";
|
|
3
|
+
export type WhoCanDraw = {
|
|
4
|
+
readonly policy: ChalkWhiteboardDrawPolicy;
|
|
5
|
+
/** Everyone in the call who is not a host, because hosts can always draw under hosts only and picked people. */
|
|
6
|
+
readonly people: readonly {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly name: string;
|
|
9
|
+
readonly picked: boolean;
|
|
10
|
+
}[];
|
|
11
|
+
readonly onPolicy: (policy: ChalkWhiteboardDrawPolicy) => void;
|
|
12
|
+
readonly onPick: (id: string, picked: boolean) => void;
|
|
13
|
+
};
|
|
14
|
+
/** The host's call-wide drawing control. Undefined for people who aren't hosts and on Sync servers without draw_policy_v1. */
|
|
15
|
+
export declare function useWhoCanDraw(toast: (toast: BoardToast) => void): WhoCanDraw | undefined;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type ChalkWhiteboardView } from "@q9labsai/chalk-client";
|
|
1
|
+
import { type ChalkWhiteboardDrawPolicy, type ChalkWhiteboardView } from "@q9labsai/chalk-client";
|
|
2
2
|
/** design.md, "The board": every colour, size and key the board's own controls use. */
|
|
3
3
|
export type BoardSurface = "chalkboard" | "paper" | "dots";
|
|
4
4
|
export type IslandPlace = "bottom" | "top" | "left";
|
|
@@ -15,6 +15,10 @@ export declare const ISLAND_PLACES: readonly {
|
|
|
15
15
|
readonly id: IslandPlace;
|
|
16
16
|
readonly label: string;
|
|
17
17
|
}[];
|
|
18
|
+
export declare const DRAW_POLICIES: readonly {
|
|
19
|
+
readonly id: ChalkWhiteboardDrawPolicy;
|
|
20
|
+
readonly label: string;
|
|
21
|
+
}[];
|
|
18
22
|
export declare const PEN_COLORS: readonly {
|
|
19
23
|
readonly id: PenColor;
|
|
20
24
|
readonly label: string;
|
|
@@ -1,7 +1 @@
|
|
|
1
|
-
|
|
2
|
-
/** A bounded record of the latest applied version, not a history of received frames. */
|
|
3
|
-
export declare class ViewVersions {
|
|
4
|
-
#private;
|
|
5
|
-
isNew(view: ChalkWhiteboardView): boolean;
|
|
6
|
-
remember(view: ChalkWhiteboardView): void;
|
|
7
|
-
}
|
|
1
|
+
export { ChalkWhiteboardViewVersions as ViewVersions } from "@q9labsai/chalk-client";
|
|
@@ -43,6 +43,10 @@ export type ChalkProps = SpaceIntegration & SpaceEventCallbacks & {
|
|
|
43
43
|
readonly theme?: ChalkTheme;
|
|
44
44
|
readonly shareUrl?: string;
|
|
45
45
|
readonly onReportProblem?: import("../space-header/SpaceHeader").SpaceHeaderProps["onReportProblem"];
|
|
46
|
+
/** Opens an app-owned dialog that can outlive this call view. */
|
|
47
|
+
readonly onOpenReportProblem?: () => void;
|
|
48
|
+
/** Hides new report actions and disables Send without discarding an open report. */
|
|
49
|
+
readonly reportUnavailableReason?: string;
|
|
46
50
|
readonly onColorSchemeChange?: (colorScheme: ChalkColorScheme) => void;
|
|
47
51
|
readonly pickChatFiles?: () => Promise<readonly ChatUploadFile[]>;
|
|
48
52
|
readonly spaceName?: string;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { RefObject } from "react";
|
|
2
|
+
import type { ChalkProps } from "./Chalk";
|
|
3
|
+
/** The call report form for app-owned terminal screens outside Chalk. */
|
|
4
|
+
export declare function ReportProblemDialog({ open, onClose, onReportProblem, colorScheme, focusScope, diagnostics, unavailableReason, }: {
|
|
5
|
+
readonly diagnostics?: string;
|
|
6
|
+
readonly unavailableReason?: string;
|
|
7
|
+
readonly open: boolean;
|
|
8
|
+
readonly onClose: () => void;
|
|
9
|
+
readonly onReportProblem: NonNullable<ChalkProps["onReportProblem"]>;
|
|
10
|
+
readonly colorScheme: "light" | "dark";
|
|
11
|
+
readonly focusScope: RefObject<HTMLElement | null>;
|
|
12
|
+
}): import("react").JSX.Element;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type React from "react";
|
|
2
|
-
import type
|
|
2
|
+
import { type ClientFailure, type ErrorCode } from "@q9labsai/chalk-client";
|
|
3
3
|
import { type ChalkTheme } from "../../theme";
|
|
4
4
|
export type StatusSurfacePhase = "entering" | "left" | "episode-ended" | "failed" | "removed" | "full" | "elsewhere";
|
|
5
5
|
export interface StatusSurfaceProps {
|
|
@@ -7,6 +7,7 @@ export interface StatusSurfaceProps {
|
|
|
7
7
|
readonly callName?: string;
|
|
8
8
|
readonly reason?: string;
|
|
9
9
|
readonly errorCode?: ErrorCode;
|
|
10
|
+
readonly sync?: ClientFailure["sync"];
|
|
10
11
|
readonly theme?: ChalkTheme;
|
|
11
12
|
readonly onRejoin?: () => void;
|
|
12
13
|
readonly onHome?: () => void;
|
|
@@ -16,4 +17,4 @@ export interface StatusSurfaceProps {
|
|
|
16
17
|
readonly retryPending?: boolean;
|
|
17
18
|
}
|
|
18
19
|
/** All terminal call states share the same quiet page and host-owned navigation. */
|
|
19
|
-
export declare function StatusSurface({ phase, callName, reason, errorCode, theme, onRejoin, onHome, onRetry, onReportProblem, rejoinDisabled, retryPending }: StatusSurfaceProps): React.JSX.Element;
|
|
20
|
+
export declare function StatusSurface({ phase, callName, reason, errorCode, sync, theme, onRejoin, onHome, onRetry, onReportProblem, rejoinDisabled, retryPending }: StatusSurfaceProps): React.JSX.Element;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
export interface ChalkSheetProps {
|
|
3
|
-
readonly variant?: "default" | "details" | "panel" | "ready";
|
|
3
|
+
readonly variant?: "default" | "details" | "panel" | "ready" | "stage";
|
|
4
4
|
readonly open: boolean;
|
|
5
5
|
readonly onOpenChange: (open: boolean) => void;
|
|
6
6
|
readonly title: string;
|
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { type ReactNode } from "react";
|
|
2
2
|
export interface ChalkToastProps {
|
|
3
3
|
readonly message: ReactNode;
|
|
4
|
+
/** Stable semantic identity for rich message content. Change it when the message changes. */
|
|
5
|
+
readonly announcementKey?: string;
|
|
6
|
+
/** Plain speech text for custom message components. Literal text and markup are read without mounting them. */
|
|
7
|
+
readonly announcementText?: string;
|
|
4
8
|
readonly icon?: ReactNode;
|
|
5
9
|
readonly type?: "info" | "success" | "warning" | "error";
|
|
6
10
|
readonly duration?: number;
|
|
@@ -8,6 +12,8 @@ export interface ChalkToastProps {
|
|
|
8
12
|
readonly label: string;
|
|
9
13
|
readonly onClick: () => void;
|
|
10
14
|
};
|
|
15
|
+
/** Enables live announcements. Pass false for an already-announced notice; a replacement message can enable the same mounted toast later. */
|
|
16
|
+
readonly announce?: boolean;
|
|
11
17
|
readonly onDismiss: () => void;
|
|
12
18
|
}
|
|
13
|
-
export declare function ChalkToast({ message, type, duration, action, icon, onDismiss }: ChalkToastProps): import("react").JSX.Element;
|
|
19
|
+
export declare function ChalkToast({ message, announcementKey, announcementText, type, duration, action, icon, announce, onDismiss }: ChalkToastProps): import("react").JSX.Element;
|
|
@@ -1,2 +1,106 @@
|
|
|
1
|
-
import { type ClientFailure } from "@q9labsai/chalk-client";
|
|
1
|
+
import { type ClientFailure, type MediaSource } from "@q9labsai/chalk-client";
|
|
2
|
+
export type MediaCommandSource = MediaSource | "speaker";
|
|
3
|
+
export type MediaCommandAction = "start" | "stop" | "select" | "test" | "request";
|
|
4
|
+
type Operation = MediaCommandAction | "change";
|
|
5
|
+
type Context = {
|
|
6
|
+
readonly source?: MediaCommandSource;
|
|
7
|
+
readonly recoverable: boolean;
|
|
8
|
+
};
|
|
9
|
+
type Copy = (context: Context) => string;
|
|
10
|
+
type Rule = {
|
|
11
|
+
readonly copy: Readonly<Record<Operation, Copy>>;
|
|
12
|
+
readonly retry: boolean;
|
|
13
|
+
};
|
|
2
14
|
export declare function commandFailure(cause: unknown): ClientFailure;
|
|
15
|
+
/** Every client code has a media-context policy. Non-media commands are preserved at the boundary below. */
|
|
16
|
+
export declare const MEDIA_FAILURE_POLICY: {
|
|
17
|
+
"access.invalid": Rule;
|
|
18
|
+
"access.unavailable": Rule;
|
|
19
|
+
"chat.cursor_reset_required": Rule;
|
|
20
|
+
"chat.payload_invalid": Rule;
|
|
21
|
+
"client.internal_error": Rule;
|
|
22
|
+
"collaboration.unavailable": Rule;
|
|
23
|
+
"command.rate_limited": Rule;
|
|
24
|
+
"command.rejected": Rule;
|
|
25
|
+
"connection.invalid_state": {
|
|
26
|
+
copy: {
|
|
27
|
+
test: (context: Context) => string;
|
|
28
|
+
request: (context: Context) => string;
|
|
29
|
+
start: (context: Context) => string;
|
|
30
|
+
stop: (context: Context) => string;
|
|
31
|
+
select: (context: Context) => string;
|
|
32
|
+
change: (context: Context) => string;
|
|
33
|
+
};
|
|
34
|
+
retry: true;
|
|
35
|
+
};
|
|
36
|
+
"connection.join_cleanup_unconfirmed": Rule;
|
|
37
|
+
"connection.leave_unconfirmed": Rule;
|
|
38
|
+
"connection.media_recovery_exhausted": Rule;
|
|
39
|
+
"connection.media_start_failed": Rule;
|
|
40
|
+
"connection.state_digest_recovery_failed": Rule;
|
|
41
|
+
"connection.sync_recovery_exhausted": Rule;
|
|
42
|
+
"connection.sync_start_failed": Rule;
|
|
43
|
+
"connection.sync_hello_timeout": Rule;
|
|
44
|
+
"environment.unsupported": {
|
|
45
|
+
copy: {
|
|
46
|
+
stop: (context: Context) => string;
|
|
47
|
+
start: () => string;
|
|
48
|
+
request: () => string;
|
|
49
|
+
select: (context: Context) => "Media capture is unavailable in this environment. Use a supported browser over HTTPS or the native app." | "This browser can’t change your speaker. Use your system sound settings to choose an output device.";
|
|
50
|
+
test: () => string;
|
|
51
|
+
change: (context: Context) => "Media capture is unavailable in this environment. Use a supported browser over HTTPS or the native app." | "This browser can’t change your speaker. Use your system sound settings to choose an output device.";
|
|
52
|
+
};
|
|
53
|
+
retry: false;
|
|
54
|
+
};
|
|
55
|
+
"episode.ended": {
|
|
56
|
+
copy: {
|
|
57
|
+
start: () => string;
|
|
58
|
+
stop: () => string;
|
|
59
|
+
select: () => string;
|
|
60
|
+
test: () => string;
|
|
61
|
+
request: () => string;
|
|
62
|
+
change: () => string;
|
|
63
|
+
};
|
|
64
|
+
retry: false;
|
|
65
|
+
};
|
|
66
|
+
"media.permission_denied": Rule;
|
|
67
|
+
"media.device_not_found": Rule;
|
|
68
|
+
"media.device_busy": {
|
|
69
|
+
copy: Readonly<Record<Operation, Copy>>;
|
|
70
|
+
retry: true;
|
|
71
|
+
};
|
|
72
|
+
"media.device_constraint_invalid": Rule;
|
|
73
|
+
"media.capture_failed": {
|
|
74
|
+
copy: {
|
|
75
|
+
stop: (context: Context) => string;
|
|
76
|
+
select: (context: Context) => string;
|
|
77
|
+
test: (context: Context) => string;
|
|
78
|
+
start: Copy;
|
|
79
|
+
request: Copy;
|
|
80
|
+
change: (context: Context) => string;
|
|
81
|
+
};
|
|
82
|
+
retry: true;
|
|
83
|
+
};
|
|
84
|
+
"media.request_invalid": {
|
|
85
|
+
copy: {
|
|
86
|
+
test: () => string;
|
|
87
|
+
request: () => string;
|
|
88
|
+
select: (context: Context) => string;
|
|
89
|
+
start: () => string;
|
|
90
|
+
stop: () => string;
|
|
91
|
+
change: () => string;
|
|
92
|
+
};
|
|
93
|
+
retry: false;
|
|
94
|
+
};
|
|
95
|
+
"media.timeout": Rule;
|
|
96
|
+
"offline.discarded": Rule;
|
|
97
|
+
"participant.invalid": Rule;
|
|
98
|
+
"reaction.invalid": Rule;
|
|
99
|
+
"whiteboard.unavailable": Rule;
|
|
100
|
+
};
|
|
101
|
+
export declare function commandFailurePresentation(failure: ClientFailure, source?: MediaCommandSource, operation?: Operation, hasExactRetry?: boolean): {
|
|
102
|
+
readonly message: string;
|
|
103
|
+
readonly retry: boolean;
|
|
104
|
+
};
|
|
105
|
+
export declare function commandFailureMessage(failure: ClientFailure, source?: MediaCommandSource, operation?: MediaCommandAction): string;
|
|
106
|
+
export {};
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type React from "react";
|
|
2
|
+
import { type MediaCommandSource, type MediaCommandAction } from "./command-failure";
|
|
2
3
|
import type { CameraPreview, ClientFailure, MediaSource, ScreenShareOptions } from "@q9labsai/chalk-client";
|
|
3
4
|
import { type ParticipantGradientPreference } from "../../utils/colorGenerator";
|
|
4
5
|
export interface MediaDevice {
|
|
@@ -93,7 +94,8 @@ export interface ControlBarProps {
|
|
|
93
94
|
/** Set for hosts: the leave button opens a menu with Leave call and End call for everyone. */
|
|
94
95
|
readonly onEndForEveryone?: () => void;
|
|
95
96
|
readonly onLeft?: () => void;
|
|
96
|
-
|
|
97
|
+
/** The second argument retains the published MediaSource contract; the third also identifies speaker commands. */
|
|
98
|
+
readonly onCommandError?: (failure: ClientFailure | null, source?: MediaSource, commandSource?: MediaCommandSource, action?: MediaCommandAction, retryCommand?: () => Promise<unknown>) => void;
|
|
97
99
|
readonly participantColorSeed?: string;
|
|
98
100
|
readonly participantGradientPreference?: ParticipantGradientPreference;
|
|
99
101
|
readonly className?: string;
|
|
@@ -124,6 +126,8 @@ export declare function useControlBarSurfaceBehavior({ density, buttons, audioIn
|
|
|
124
126
|
interface ConnectedControlBarBehavior {
|
|
125
127
|
readonly surfaceProps: ControlBarSurfaceProps;
|
|
126
128
|
readonly commandError: ClientFailure | null;
|
|
129
|
+
readonly commandSource?: MediaCommandSource;
|
|
130
|
+
readonly commandAction?: MediaCommandAction;
|
|
127
131
|
}
|
|
128
132
|
export declare function useConnectedControlBarBehavior(props: ControlBarProps): ConnectedControlBarBehavior;
|
|
129
133
|
export {};
|
package/dist/components/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import {
|
|
|
5
5
|
RecordingHistoryPanel,
|
|
6
6
|
RecordingSpaceView,
|
|
7
7
|
RecordingWhiteboardView
|
|
8
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-CYC2G42K.js";
|
|
9
9
|
import {
|
|
10
10
|
loadRecordingWhiteboardFiles,
|
|
11
11
|
parseRecordingWhiteboardStateV1,
|
|
@@ -45,10 +45,10 @@ import {
|
|
|
45
45
|
ReactionsOverlay,
|
|
46
46
|
ScreenShareView,
|
|
47
47
|
Stage
|
|
48
|
-
} from "../chunk-
|
|
48
|
+
} from "../chunk-G62BKRNX.js";
|
|
49
49
|
import {
|
|
50
50
|
buildStageItems
|
|
51
|
-
} from "../chunk-
|
|
51
|
+
} from "../chunk-HWQLW4JR.js";
|
|
52
52
|
import "../chunk-4U7QZC72.js";
|
|
53
53
|
export {
|
|
54
54
|
AdmissionPanel,
|
|
@@ -3,6 +3,7 @@ import React from "react";
|
|
|
3
3
|
export declare const RECONNECTING_GRACE_MS = 2000;
|
|
4
4
|
export declare function offlineNoticeText(pendingCount: number, online: boolean): string;
|
|
5
5
|
/** Keep queued actions automatic while preserving the reconnecting notice. */
|
|
6
|
-
export declare function OfflineActions({ reconnecting: requestedReconnecting }?: {
|
|
6
|
+
export declare function OfflineActions({ reconnecting: requestedReconnecting, hasNotice }?: {
|
|
7
7
|
readonly reconnecting?: boolean;
|
|
8
|
+
readonly hasNotice?: boolean;
|
|
8
9
|
}): React.JSX.Element | null;
|
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
import { type RefObject } from "react";
|
|
2
2
|
import type { SpaceHeaderProps } from "./space-header-contract";
|
|
3
3
|
export declare const CALL_SHORTCUTS: readonly [readonly ["Mute or unmute", "⌘ D"], readonly ["Turn camera on or off", "⌘ E"], readonly ["Raise or lower hand", "⌃ ⌘ H"], readonly ["Open or close chat", "⌃ ⌘ C"], readonly ["Open or close people", "⌃ ⌘ P"], readonly ["Switch layout", "⌃ ⌘ L"], readonly ["Talk while muted", "Hold Space"], readonly ["Show shortcuts", "?"], readonly ["Close a menu or panel", "Esc"]];
|
|
4
|
-
export declare function CallDialog({ kind, open, onClose, onReportProblem, diagnostics, returnFocus, }: {
|
|
4
|
+
export declare function CallDialog({ kind, open, onClose, onReportProblem, diagnostics, returnFocus, focusScope, unavailableReason, }: {
|
|
5
5
|
readonly kind: "shortcuts" | "report";
|
|
6
6
|
readonly open: boolean;
|
|
7
7
|
readonly onClose: () => void;
|
|
8
8
|
readonly onReportProblem?: SpaceHeaderProps["onReportProblem"];
|
|
9
9
|
readonly diagnostics: string;
|
|
10
|
-
readonly returnFocus
|
|
10
|
+
readonly returnFocus?: RefObject<HTMLButtonElement | null>;
|
|
11
|
+
readonly focusScope: RefObject<HTMLElement | null>;
|
|
12
|
+
readonly unavailableReason?: string;
|
|
11
13
|
}): import("react").JSX.Element;
|
|
12
14
|
export declare function CallDetails({ shareUrl, spaceName, onClose, diagnostics, colorScheme, onColorSchemeChange, sounds, onSoundsChange, onDialog, onReportProblem, }: Pick<SpaceHeaderProps, "spaceName" | "shareUrl" | "diagnostics" | "colorScheme" | "onColorSchemeChange" | "onReportProblem"> & {
|
|
13
15
|
readonly onClose: () => void;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
1
2
|
export interface SpaceHeaderProps {
|
|
2
3
|
spaceName: string;
|
|
3
4
|
people?: readonly {
|
|
@@ -18,6 +19,8 @@ export interface SpaceHeaderProps {
|
|
|
18
19
|
readonly description: string;
|
|
19
20
|
readonly diagnostics?: string;
|
|
20
21
|
}) => void | Promise<void>;
|
|
22
|
+
reportUnavailableReason?: string;
|
|
23
|
+
onOpenReportProblem?: () => void;
|
|
21
24
|
shortcuts?: {
|
|
22
25
|
readonly isMuted?: boolean;
|
|
23
26
|
readonly onToggleMute?: () => void;
|
|
@@ -36,6 +39,8 @@ export interface SpaceHeaderProps {
|
|
|
36
39
|
isTranscribing?: boolean;
|
|
37
40
|
layout?: "grid" | "focus";
|
|
38
41
|
onLayoutChange?: (layout: "grid" | "focus") => void;
|
|
42
|
+
/** A control for notices that have no room on the stage, placed at the header's end, before the people button. */
|
|
43
|
+
notices?: ReactNode;
|
|
39
44
|
className?: string;
|
|
40
45
|
}
|
|
41
46
|
export declare function formatDuration(seconds: number): string;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type MediaCommandSource, type MediaCommandAction } from "../control-bar/command-failure";
|
|
2
|
+
import { type ChalkWhiteboardView, type ClientFailure } from "@q9labsai/chalk-client";
|
|
2
3
|
import React from "react";
|
|
3
4
|
import type { ChatUploadFile } from "@q9labsai/chalk-client";
|
|
4
5
|
import { type ReconnectingOverlayProps } from "../reconnecting-overlay/ReconnectingOverlay";
|
|
@@ -30,6 +31,8 @@ export interface SpaceViewProps {
|
|
|
30
31
|
readonly callLinkOpen?: boolean;
|
|
31
32
|
readonly onCallLinkToggle?: () => void;
|
|
32
33
|
readonly onReportProblem?: import("../space-header/SpaceHeader").SpaceHeaderProps["onReportProblem"];
|
|
34
|
+
readonly reportUnavailableReason?: string;
|
|
35
|
+
readonly onOpenReportProblem?: () => void;
|
|
33
36
|
readonly inviteLink?: string;
|
|
34
37
|
readonly pickChatFiles?: () => Promise<readonly ChatUploadFile[]>;
|
|
35
38
|
readonly stageBackground?: boolean;
|
|
@@ -39,7 +42,9 @@ export interface SpaceViewProps {
|
|
|
39
42
|
readonly initialPanel?: SpacePanel | null;
|
|
40
43
|
readonly features?: SpaceViewFeatures;
|
|
41
44
|
readonly generatedAvatars?: boolean;
|
|
42
|
-
readonly commandError?: string;
|
|
45
|
+
readonly commandError?: string | ClientFailure;
|
|
46
|
+
readonly commandSource?: MediaCommandSource;
|
|
47
|
+
readonly commandAction?: MediaCommandAction;
|
|
43
48
|
readonly onDismissCommandError?: () => void;
|
|
44
49
|
readonly onToggleWhiteboard?: () => void;
|
|
45
50
|
readonly whiteboard?: SpaceViewWhiteboard;
|
|
@@ -51,4 +56,4 @@ export interface SpaceViewProps {
|
|
|
51
56
|
readonly onEndEpisode?: () => void | Promise<void>;
|
|
52
57
|
readonly className?: string;
|
|
53
58
|
}
|
|
54
|
-
export declare function SpaceView({ colorScheme, onColorSchemeChange, spaceName, shareUrl, callLinkOpen, onCallLinkToggle, onReportProblem, inviteLink, pickChatFiles, stageBackground, layout: controlledLayout, onLayoutChange, initialPanel, features, generatedAvatars, commandError: externalCommandError, onDismissCommandError, onToggleWhiteboard, whiteboard, reconnecting, overlay, onLeft, onEndEpisode, className, }: SpaceViewProps): React.JSX.Element;
|
|
59
|
+
export declare function SpaceView({ colorScheme, onColorSchemeChange, spaceName, shareUrl, callLinkOpen, onCallLinkToggle, onReportProblem, reportUnavailableReason, onOpenReportProblem, inviteLink, pickChatFiles, stageBackground, layout: controlledLayout, onLayoutChange, initialPanel, features, generatedAvatars, commandError: externalCommandError, commandSource: externalCommandSource, commandAction: externalCommandAction, onDismissCommandError, onToggleWhiteboard, whiteboard, reconnecting, overlay, onLeft, onEndEpisode, className, }: SpaceViewProps): React.JSX.Element;
|
|
@@ -24,7 +24,7 @@ export interface StageProps {
|
|
|
24
24
|
readonly followingId?: string | null;
|
|
25
25
|
readonly onFollowChange?: (id: string) => void;
|
|
26
26
|
readonly clickAction?: "pin" | "follow";
|
|
27
|
-
/** Upper bound of
|
|
27
|
+
/** Upper bound of grid tiles, or secondary tiles in spotlight; overflow includes shared content. */
|
|
28
28
|
readonly maxPerPage?: number;
|
|
29
29
|
/** Tiles narrower than this fold the rest into overflow. */
|
|
30
30
|
readonly minTileWidth?: number;
|
|
@@ -28,5 +28,9 @@ export interface PrimaryContext {
|
|
|
28
28
|
}
|
|
29
29
|
/** A pin wins; otherwise the first live share, board, then active speaker leads Spotlight. */
|
|
30
30
|
export declare function choosePrimary(items: readonly StageItem[], context: PrimaryContext): StageItem | null;
|
|
31
|
+
/** Who is speaking now: the active speaker before anyone else speaking. */
|
|
32
|
+
export declare function speakingParticipantId(items: readonly StageItem[], accept?: (participant: Participant) => boolean): string | undefined;
|
|
33
|
+
/** The remote person whose face leads the floating column: whoever is speaking now, else the last remote speaker. */
|
|
34
|
+
export declare function floatingSpeakerId(items: readonly StageItem[], lastRemoteSpeakerId: string | null): string | null;
|
|
31
35
|
/** Grid order: pinned item first, everything else in stable order. */
|
|
32
36
|
export declare function gridOrder(items: readonly StageItem[], pinnedId: string | null): StageItem[];
|
|
@@ -3,7 +3,7 @@ export interface StageBox {
|
|
|
3
3
|
readonly width: number;
|
|
4
4
|
readonly height: number;
|
|
5
5
|
}
|
|
6
|
-
export type StageRole = "primary" | "strip" | "grid";
|
|
6
|
+
export type StageRole = "primary" | "strip" | "grid" | "float";
|
|
7
7
|
export interface StageFrame {
|
|
8
8
|
readonly id: string;
|
|
9
9
|
readonly role: StageRole;
|
|
@@ -37,8 +37,12 @@ export interface SpotlightFitOptions {
|
|
|
37
37
|
readonly content?: boolean;
|
|
38
38
|
readonly wideIds?: readonly string[];
|
|
39
39
|
readonly maxPerPage?: number;
|
|
40
|
-
/**
|
|
40
|
+
/** @deprecated Prefer floatingPhonePeople for boards. Preserves the 52px phone strip. */
|
|
41
41
|
readonly slimPhoneStrip?: boolean;
|
|
42
|
+
/** On a phone the board keeps the whole stage, and people float in a short column at its right edge. */
|
|
43
|
+
readonly floatingPhonePeople?: boolean;
|
|
44
|
+
readonly speakerId?: string | null;
|
|
45
|
+
readonly localId?: string | null;
|
|
42
46
|
}
|
|
43
47
|
export declare const WIDESCREEN: number;
|
|
44
48
|
export declare const MORE_TILE_ID = "stage:more";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
export declare function CallEventIcon({ kind, error }: {
|
|
2
|
-
readonly kind: "microphone" | "microphone-on" | "camera" | "screen" | "recording" | "hand" | "host";
|
|
2
|
+
readonly kind: "speaker" | "microphone" | "microphone-on" | "camera" | "screen" | "recording" | "hand" | "host";
|
|
3
3
|
readonly error?: boolean;
|
|
4
4
|
}): import("react").JSX.Element;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import type { Toast } from "./ToastStack";
|
|
2
|
+
export interface NoticeButtonProps {
|
|
3
|
+
readonly toasts: readonly Toast[];
|
|
4
|
+
readonly onDismiss: (id: string) => void;
|
|
5
|
+
}
|
|
6
|
+
/** The header's way into notices that have no room on the stage, such as on a short screen with the board's tools open. */
|
|
7
|
+
export declare function NoticeButton({ toasts, onDismiss }: NoticeButtonProps): import("react").JSX.Element | null;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ChalkToastProps } from "../chalk-ui/ChalkToast";
|
|
2
|
-
export interface Toast extends Omit<ChalkToastProps, "onDismiss"> {
|
|
2
|
+
export interface Toast extends Omit<ChalkToastProps, "onDismiss" | "announce"> {
|
|
3
3
|
readonly id: string;
|
|
4
4
|
}
|
|
5
5
|
export interface ToastStackProps {
|
|
@@ -7,5 +7,16 @@ export interface ToastStackProps {
|
|
|
7
7
|
readonly onDismiss: (id: string) => void;
|
|
8
8
|
readonly maxVisible?: number;
|
|
9
9
|
readonly className?: string;
|
|
10
|
+
/**
|
|
11
|
+
* Called with the ids of the notices the stage has no room for, so the host can offer them elsewhere, such as from the header.
|
|
12
|
+
* With it, a call shorter than `FOLD_BELOW_PX` folds every notice. Taller calls fold notices past `maxVisible` or outside the available band, announcing each folded message once.
|
|
13
|
+
*/
|
|
14
|
+
readonly onFold?: (folded: readonly string[]) => void;
|
|
10
15
|
}
|
|
11
|
-
|
|
16
|
+
/**
|
|
17
|
+
* Calls shorter than this fold every notice. It is the height the mockup's three-notice stack needs at its tallest, on a phone with
|
|
18
|
+
* the board open: 108px for the header and the board's menu, an 8px gap, three camera errors wrapped to three lines (79px each, with
|
|
19
|
+
* 8px gaps), and the 242px the open drawing tools lift notices by, 611px in all.
|
|
20
|
+
*/
|
|
21
|
+
export declare const FOLD_BELOW_PX = 620;
|
|
22
|
+
export declare const ToastStack: import("react").MemoExoticComponent<({ toasts, onDismiss, maxVisible, className, onFold }: ToastStackProps) => import("react").JSX.Element>;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
+
import { type MediaCommandSource, type MediaCommandAction } from "../control-bar/command-failure";
|
|
1
2
|
import type { ClientFailure, SpaceSnapshot } from "@q9labsai/chalk-client";
|
|
2
3
|
import type { Toast } from "./ToastStack";
|
|
3
|
-
export declare function mediaFailureToast(failure: ClientFailure | null, source?:
|
|
4
|
+
export declare function mediaFailureToast(failure: ClientFailure | null, source?: MediaCommandSource, action?: MediaCommandAction, onRetry?: () => void): Omit<Toast, "id"> | null;
|
|
4
5
|
/** Snapshot changes, not initial history, are events for the person already in the call. */
|
|
5
6
|
export declare function callEventToasts(previous: SpaceSnapshot, next: SpaceSnapshot): Omit<Toast, "id">[];
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { ChalkToastProps } from "../chalk-ui/ChalkToast";
|
|
2
|
+
export declare function noticeAnnouncement(toast: Pick<ChalkToastProps, "message" | "announcementText">): string;
|
|
3
|
+
/** Semantic speech/lifetime revision, independent of JSX and layout chrome. */
|
|
4
|
+
export declare function noticeIdentity(toast: Pick<ChalkToastProps, "message" | "announcementText" | "announcementKey" | "type">): string;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** A layout-only DOM snapshot: never instantiate React callers, custom elements, or media resources. */
|
|
2
|
+
export declare function captureToastLayout(toast: HTMLElement): HTMLDivElement;
|
|
3
|
+
/** Reflow cached markup at the current band's width without mounting the folded caller tree. */
|
|
4
|
+
export declare function measureToastLayout(stack: HTMLElement, snapshot: HTMLDivElement): number;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { type RefObject } from "react";
|
|
2
|
+
import type { Toast } from "./ToastStack";
|
|
3
|
+
/** Remember measured sizes when notices fold: resizing must not remount them to measure again. */
|
|
4
|
+
export declare function useToastCapacity(stack: RefObject<HTMLDivElement | null>, candidates: readonly Toast[], enabled: boolean, ownedIds: readonly string[]): {
|
|
5
|
+
readonly count: number;
|
|
6
|
+
readonly ready: boolean;
|
|
7
|
+
};
|