@scrawl-board/board 0.1.0-beta.0 → 0.1.0-beta.10
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/NOTICE +8 -0
- package/README.md +11 -5
- package/dist/Inter-OFL.txt +92 -0
- package/dist/browser.d.ts +1314 -29
- package/dist/browser.js +11407 -36607
- package/dist/core.d.ts +1455 -61
- package/dist/core.js +3003 -85
- package/dist/index.d.ts +2109 -78
- package/dist/index.js +14291 -37063
- package/dist/local.d.ts +518 -0
- package/dist/local.js +342 -0
- package/dist/react.d.ts +1444 -34
- package/dist/react.js +13757 -36879
- package/dist/styles.css +619 -58
- package/package.json +17 -3
package/dist/index.d.ts
CHANGED
|
@@ -1,19 +1,14 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, ComponentType, CSSProperties } from 'react';
|
|
3
3
|
|
|
4
|
-
declare const documentIdBrand: unique symbol;
|
|
5
|
-
declare const strokeIdBrand: unique symbol;
|
|
6
|
-
type DocumentId = string & {
|
|
7
|
-
readonly [documentIdBrand]: "DocumentId";
|
|
8
|
-
};
|
|
9
|
-
type StrokeId = string & {
|
|
10
|
-
readonly [strokeIdBrand]: "StrokeId";
|
|
11
|
-
};
|
|
12
|
-
declare function documentId(value: string): DocumentId;
|
|
13
|
-
declare function strokeId(value: string): StrokeId;
|
|
14
|
-
|
|
15
4
|
/** Wire grammar: `asset:<namespace>:<opaque-id>`. Interpreted only by the Host. */
|
|
16
5
|
type AssetRef = string;
|
|
6
|
+
declare const ASSET_REF_PATTERN: RegExp;
|
|
7
|
+
/** Hard ceiling on a reference's own wire length — independent of any resource limit below. */
|
|
8
|
+
declare const ASSET_REF_MAX_BYTES = 512;
|
|
9
|
+
declare function isAssetRef(value: unknown): value is AssetRef;
|
|
10
|
+
/** Throws on malformed input; use `isAssetRef` where a boolean is wanted instead. */
|
|
11
|
+
declare function assetRef(value: string): AssetRef;
|
|
17
12
|
type AssetKind = "image";
|
|
18
13
|
type AssetPurpose = "render" | "thumbnail" | "export";
|
|
19
14
|
interface AssetResolveRequest {
|
|
@@ -55,6 +50,12 @@ interface AssetIngestor {
|
|
|
55
50
|
ingest(request: AssetIngestRequest): Promise<AssetIngestResult>;
|
|
56
51
|
}
|
|
57
52
|
type AssetResolutionErrorCode = "resolver-unavailable" | "not-found" | "forbidden" | "offline" | "unsupported-type" | "too-large" | "invalid-content" | "decode-failed" | "budget-exceeded" | "aborted" | "unknown";
|
|
53
|
+
declare class AssetResolutionError extends Error {
|
|
54
|
+
readonly code: AssetResolutionErrorCode;
|
|
55
|
+
readonly retryable: boolean;
|
|
56
|
+
readonly ref?: AssetRef | undefined;
|
|
57
|
+
constructor(code: AssetResolutionErrorCode, retryable: boolean, message: string, ref?: AssetRef | undefined);
|
|
58
|
+
}
|
|
58
59
|
/** Runtime event for a resolution/ingestion failure — never carries credentials or a fetchable location. */
|
|
59
60
|
interface AssetDiagnostic {
|
|
60
61
|
code: AssetResolutionErrorCode;
|
|
@@ -63,6 +64,18 @@ interface AssetDiagnostic {
|
|
|
63
64
|
objectKind: "image" | "custom";
|
|
64
65
|
retryable: boolean;
|
|
65
66
|
}
|
|
67
|
+
declare const ASSET_MAX_ENCODED_BYTES: number;
|
|
68
|
+
declare const ASSET_MAX_DIMENSION_PX = 8192;
|
|
69
|
+
declare const ASSET_MAX_DECODED_MEGAPIXELS = 40;
|
|
70
|
+
declare const ASSET_MAX_CONCURRENT_RESOLUTIONS = 6;
|
|
71
|
+
declare const ASSET_CACHE_BYTES_DEFAULT: number;
|
|
72
|
+
declare const ASSET_CACHE_BYTES_MIN: number;
|
|
73
|
+
declare const ASSET_CACHE_BYTES_MAX: number;
|
|
74
|
+
declare const ASSET_EXPORT_MAX_ENCODED_BYTES: number;
|
|
75
|
+
declare const ASSET_EXPORT_MAX_DECODED_MEGAPIXELS = 100;
|
|
76
|
+
declare const SUPPORTED_ASSET_MEDIA_TYPES: readonly ["image/png", "image/jpeg", "image/webp"];
|
|
77
|
+
type SupportedAssetMediaType = (typeof SUPPORTED_ASSET_MEDIA_TYPES)[number];
|
|
78
|
+
declare function clampAssetCacheBytes(value: number | undefined): number;
|
|
66
79
|
|
|
67
80
|
type Mat2x3 = [number, number, number, number, number, number];
|
|
68
81
|
declare const IDENTITY: Mat2x3;
|
|
@@ -121,6 +134,7 @@ interface CustomBoardObject {
|
|
|
121
134
|
};
|
|
122
135
|
props: JsonValue;
|
|
123
136
|
}
|
|
137
|
+
declare function cloneCustomObject(object: CustomBoardObject): CustomBoardObject;
|
|
124
138
|
/**
|
|
125
139
|
* The read-only view handed to `describe`. Deep-readonly by construction
|
|
126
140
|
* (not derived via a shallow `Readonly<>`) because `describe` must treat its
|
|
@@ -158,7 +172,22 @@ interface CustomObjectDefinition<Props extends JsonValue = JsonValue> {
|
|
|
158
172
|
parse(input: unknown, schemaVersion: number): Props;
|
|
159
173
|
/** One pure, synchronous step per consecutive schema version. */
|
|
160
174
|
migrate?: Readonly<Record<number, (oldProps: JsonValue) => JsonValue>>;
|
|
161
|
-
describe(object: ReadonlyCustomObject<Props>, context: ObjectDescribeContext): BoardScene
|
|
175
|
+
describe(object: ReadonlyCustomObject<Props>, context: ObjectDescribeContext): BoardScene;
|
|
176
|
+
/**
|
|
177
|
+
* Optional point-level hit-test precision (Phase 8). Every custom object
|
|
178
|
+
* hit-tests against its bounding box (`fallback.bounds`) by default — this
|
|
179
|
+
* lets a non-rectangular shape (e.g. a circular card, an L-shaped region)
|
|
180
|
+
* reject a point that's inside that box but outside its actual visible
|
|
181
|
+
* silhouette, tightening a click/marquee/raycast hit to the shape's real
|
|
182
|
+
* outline. `point` is in this object's own local space — the same
|
|
183
|
+
* untransformed space `describe`'s returned geometry already lives in
|
|
184
|
+
* (the caller inverse-transforms the pointer's board point through
|
|
185
|
+
* `object.transform` before calling this). Absent means every point
|
|
186
|
+
* inside the bounding box hits, matching pre-Phase-8 behavior exactly.
|
|
187
|
+
* Rejecting a point here does not fall through to whatever's underneath —
|
|
188
|
+
* the gesture simply misses this object, same as clicking empty space.
|
|
189
|
+
*/
|
|
190
|
+
hitTest?(object: ReadonlyCustomObject<Props>, point: BoardPoint): boolean;
|
|
162
191
|
}
|
|
163
192
|
interface SceneNodeBase {
|
|
164
193
|
key: string;
|
|
@@ -190,9 +219,18 @@ interface SceneText extends SceneNodeBase {
|
|
|
190
219
|
}
|
|
191
220
|
interface SceneGroup extends SceneNodeBase {
|
|
192
221
|
kind: "group";
|
|
193
|
-
children: readonly BoardScene
|
|
222
|
+
children: readonly BoardScene[];
|
|
194
223
|
}
|
|
195
|
-
|
|
224
|
+
/**
|
|
225
|
+
* **No renderer or SVG-export interpreter exists for this node kind yet**
|
|
226
|
+
* (tracked as deferred work — see `renderer/shapes/customObjects.ts`'s
|
|
227
|
+
* `"path"` case). Returning a `ScenePath` from `describe()` renders nothing,
|
|
228
|
+
* exports nothing, and contributes no hit-test bounds — it neither errors
|
|
229
|
+
* nor emits a diagnostic. Until an interpreter ships, build custom shapes
|
|
230
|
+
* from `SceneRect`/`SceneEllipse`/`SceneGroup`/`SceneText`/`SceneImage`
|
|
231
|
+
* instead.
|
|
232
|
+
*/
|
|
233
|
+
interface ScenePath extends SceneNodeBase {
|
|
196
234
|
kind: "path";
|
|
197
235
|
/** SVG-style path data, board-local coordinates. */
|
|
198
236
|
d: string;
|
|
@@ -225,7 +263,7 @@ interface SceneEllipse extends SceneNodeBase {
|
|
|
225
263
|
stroke?: string;
|
|
226
264
|
strokeWidth?: number;
|
|
227
265
|
}
|
|
228
|
-
type BoardScene
|
|
266
|
+
type BoardScene = SceneGroup | ScenePath | SceneText | SceneImage | SceneRect | SceneEllipse;
|
|
229
267
|
type ToolCursor = "default" | "crosshair" | "pointer" | "grab" | "grabbing" | "text";
|
|
230
268
|
type ToolCancelReason = "escape-key" | "tool-switched" | "pointer-lost" | "error";
|
|
231
269
|
interface InputModifiers {
|
|
@@ -321,7 +359,7 @@ interface ToolCapabilities {
|
|
|
321
359
|
};
|
|
322
360
|
/** Session-only geometry — never enters Document/history/persistence/collaboration. */
|
|
323
361
|
preview: {
|
|
324
|
-
set(scene: BoardScene
|
|
362
|
+
set(scene: BoardScene): void;
|
|
325
363
|
clear(): void;
|
|
326
364
|
};
|
|
327
365
|
/** Constructs, validates, and commits one atomic command; controller derives Ops. */
|
|
@@ -364,8 +402,23 @@ declare function canUnlockItem(item: Lockable | undefined, userId: string | unde
|
|
|
364
402
|
/** Wire fields for a locked item; omitted entirely when unlocked. */
|
|
365
403
|
declare function serializeLock(item: Lockable): Lockable;
|
|
366
404
|
|
|
405
|
+
/**
|
|
406
|
+
* Per-object visibility (Phase 8) — mirrors `itemLock.ts`'s `Lockable`
|
|
407
|
+
* pattern exactly, but simpler: unlike a lock, hidden state carries no
|
|
408
|
+
* holder/ownership concept, so there's no analogue to `LockHolder`/
|
|
409
|
+
* `canUnlockItem`. A hidden object stays fully present in the Document
|
|
410
|
+
* (still serializes, persists, syncs, undoes/redoes) — it just skips
|
|
411
|
+
* rendering and hit-testing/selection candidacy. `hidden` absent or
|
|
412
|
+
* `false` means visible; this keeps every pre-Phase-8 document (which has
|
|
413
|
+
* no `hidden` field on any object at all) implicitly fully visible with
|
|
414
|
+
* zero migration needed.
|
|
415
|
+
*/
|
|
416
|
+
interface Hideable {
|
|
417
|
+
hidden?: boolean;
|
|
418
|
+
}
|
|
419
|
+
|
|
367
420
|
/** A kitchen timer sitting on the board. Remaining time is derived, not ticked. */
|
|
368
|
-
interface KitchenTimer extends Lockable {
|
|
421
|
+
interface KitchenTimer extends Lockable, Hideable {
|
|
369
422
|
id: string;
|
|
370
423
|
x: number;
|
|
371
424
|
y: number;
|
|
@@ -390,6 +443,173 @@ declare function toggleTimer(timer: KitchenTimer, now: number): KitchenTimer;
|
|
|
390
443
|
declare function setTimerDuration(timer: KitchenTimer, durationMs: number): KitchenTimer;
|
|
391
444
|
declare function formatTimer(ms: number): string;
|
|
392
445
|
|
|
446
|
+
declare const SHAPE_MIN_SIZE = 0.5;
|
|
447
|
+
/**
|
|
448
|
+
* Centralized shape style defaults (Phase 8) — one object a Host can read
|
|
449
|
+
* to know (or, by not relying on the standalone constants below, override
|
|
450
|
+
* via its own UI state) what a newly drawn Rectangle/Ellipse/Line/Arrow/
|
|
451
|
+
* Polygon/Star/Heart starts with when the user hasn't picked a stroke/width
|
|
452
|
+
* yet. `shapeTool.ts` (commit-time), `renderer/shapes/lines.ts` (render-time
|
|
453
|
+
* fallback for an object missing these fields), and
|
|
454
|
+
* `persistence/serialization/svg.ts` (export-time fallback) all read from
|
|
455
|
+
* here — the two standalone constants below are kept for source
|
|
456
|
+
* compatibility and simply mirror this object's values, not a second
|
|
457
|
+
* source of truth.
|
|
458
|
+
*/
|
|
459
|
+
declare const SHAPE_STYLE_DEFAULTS: {
|
|
460
|
+
readonly stroke: "#1C1C1E";
|
|
461
|
+
readonly strokeWidth: 0.3;
|
|
462
|
+
};
|
|
463
|
+
declare const SHAPE_DEFAULT_STROKE: "#1C1C1E";
|
|
464
|
+
declare const SHAPE_DEFAULT_STROKE_WIDTH: 0.3;
|
|
465
|
+
interface RectangleObject extends Lockable, Hideable {
|
|
466
|
+
id: string;
|
|
467
|
+
x: number;
|
|
468
|
+
y: number;
|
|
469
|
+
width: number;
|
|
470
|
+
height: number;
|
|
471
|
+
fill?: string;
|
|
472
|
+
stroke?: string;
|
|
473
|
+
strokeWidth?: number;
|
|
474
|
+
/** Corner radius in board units; clamped to at most half the shorter side at render time. */
|
|
475
|
+
cornerRadius?: number;
|
|
476
|
+
/** `[0, 1]`; undefined means fully opaque (Phase 4). */
|
|
477
|
+
opacity?: number;
|
|
478
|
+
/**
|
|
479
|
+
* Radians, about the shape's own center `(x + width/2, y - height/2)`.
|
|
480
|
+
* Undefined means 0 (Phase 3). `x`/`y`/`width`/`height` stay in the
|
|
481
|
+
* shape's own unrotated local frame — rotation is a separate, applied-last
|
|
482
|
+
* transform, not baked into them, matching how Stroke/CustomBoardObject
|
|
483
|
+
* keep geometry and placement independent via their own `matrix`.
|
|
484
|
+
*/
|
|
485
|
+
rotation?: number;
|
|
486
|
+
}
|
|
487
|
+
interface EllipseObject extends Lockable, Hideable {
|
|
488
|
+
id: string;
|
|
489
|
+
x: number;
|
|
490
|
+
y: number;
|
|
491
|
+
width: number;
|
|
492
|
+
height: number;
|
|
493
|
+
fill?: string;
|
|
494
|
+
stroke?: string;
|
|
495
|
+
strokeWidth?: number;
|
|
496
|
+
/** `[0, 1]`; undefined means fully opaque (Phase 4). */
|
|
497
|
+
opacity?: number;
|
|
498
|
+
/** Radians, about the shape's own center — see RectangleObject's `rotation` doc. */
|
|
499
|
+
rotation?: number;
|
|
500
|
+
}
|
|
501
|
+
declare function cloneRectangle(rect: RectangleObject): RectangleObject;
|
|
502
|
+
declare function cloneEllipse(ellipse: EllipseObject): EllipseObject;
|
|
503
|
+
/** `"none"` is a plain line with no arrowhead; today's only real head shape is `"triangle"`. New head shapes extend this union without touching `ArrowObject`'s own fields. */
|
|
504
|
+
type ArrowHeadStyle = "triangle" | "none";
|
|
505
|
+
interface LineObject extends Lockable, Hideable {
|
|
506
|
+
id: string;
|
|
507
|
+
start: BoardPoint;
|
|
508
|
+
end: BoardPoint;
|
|
509
|
+
stroke?: string;
|
|
510
|
+
strokeWidth?: number;
|
|
511
|
+
opacity?: number;
|
|
512
|
+
}
|
|
513
|
+
/** An endpoint anchored to a normalized position on another object's bounds. */
|
|
514
|
+
interface ConnectorBinding {
|
|
515
|
+
objectId: string;
|
|
516
|
+
/** Horizontal and vertical fractions of the target's axis-aligned bounds. */
|
|
517
|
+
x: number;
|
|
518
|
+
y: number;
|
|
519
|
+
}
|
|
520
|
+
interface ArrowObject extends Lockable, Hideable {
|
|
521
|
+
/** Omitted for legacy arrows. Connectors share Arrow's persistence/history contract. */
|
|
522
|
+
routing?: "straight" | "curved" | "polyline";
|
|
523
|
+
/** Intermediate board-space vertices for a multi-point connector. */
|
|
524
|
+
waypoints?: BoardPoint[];
|
|
525
|
+
startBinding?: ConnectorBinding;
|
|
526
|
+
endBinding?: ConnectorBinding;
|
|
527
|
+
id: string;
|
|
528
|
+
start: BoardPoint;
|
|
529
|
+
end: BoardPoint;
|
|
530
|
+
head?: ArrowHeadStyle;
|
|
531
|
+
stroke?: string;
|
|
532
|
+
strokeWidth?: number;
|
|
533
|
+
opacity?: number;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Triangle(3)/Diamond(4)/Pentagon(5)/Hexagon(6)/Octagon(8) as one shared
|
|
537
|
+
* type instead of five near-duplicate interfaces — a regular N-gon
|
|
538
|
+
* inscribed in the same `x`/`y`/`width`/`height`/`rotation` bounding box
|
|
539
|
+
* Rectangle already uses, parameterized by `sides`. Diamond is exactly a
|
|
540
|
+
* 4-sided regular polygon with vertex 0 pointing right (not up, like
|
|
541
|
+
* Triangle/Pentagon/Hexagon) — see `polygonGeometry.ts`'s
|
|
542
|
+
* `polygonStartAngle`, which encodes each side count's own vertex
|
|
543
|
+
* orientation so the outline always matches the legacy drag-preview shape.
|
|
544
|
+
*/
|
|
545
|
+
interface PolygonObject extends Lockable, Hideable {
|
|
546
|
+
id: string;
|
|
547
|
+
x: number;
|
|
548
|
+
y: number;
|
|
549
|
+
width: number;
|
|
550
|
+
height: number;
|
|
551
|
+
sides: 3 | 4 | 5 | 6 | 8;
|
|
552
|
+
fill?: string;
|
|
553
|
+
stroke?: string;
|
|
554
|
+
strokeWidth?: number;
|
|
555
|
+
opacity?: number;
|
|
556
|
+
/** Radians, about the shape's own center — see RectangleObject's `rotation` doc. */
|
|
557
|
+
rotation?: number;
|
|
558
|
+
}
|
|
559
|
+
declare function clonePolygon(polygon: PolygonObject): PolygonObject;
|
|
560
|
+
/** Same bounding-box/rotation convention as Rectangle; a 5-pointed star with a tuned inner-radius ratio, matching the legacy tool's own default (see `polygonGeometry.ts`'s `starPoints`). */
|
|
561
|
+
interface StarObject extends Lockable, Hideable {
|
|
562
|
+
id: string;
|
|
563
|
+
x: number;
|
|
564
|
+
y: number;
|
|
565
|
+
width: number;
|
|
566
|
+
height: number;
|
|
567
|
+
/** Vertex count; today's only shipped preset is 5, matching the legacy tool. */
|
|
568
|
+
points: number;
|
|
569
|
+
/** `(0, 1)` — inner vertex radius as a fraction of the outer radius. */
|
|
570
|
+
innerRadiusRatio: number;
|
|
571
|
+
fill?: string;
|
|
572
|
+
stroke?: string;
|
|
573
|
+
strokeWidth?: number;
|
|
574
|
+
opacity?: number;
|
|
575
|
+
rotation?: number;
|
|
576
|
+
}
|
|
577
|
+
declare function cloneStar(star: StarObject): StarObject;
|
|
578
|
+
/** Same bounding-box/rotation convention as Rectangle; the standard parametric heart curve (see `polygonGeometry.ts`'s `heartPoints`), no extra parameters beyond the shared shape fields. */
|
|
579
|
+
interface HeartObject extends Lockable, Hideable {
|
|
580
|
+
id: string;
|
|
581
|
+
x: number;
|
|
582
|
+
y: number;
|
|
583
|
+
width: number;
|
|
584
|
+
height: number;
|
|
585
|
+
fill?: string;
|
|
586
|
+
stroke?: string;
|
|
587
|
+
strokeWidth?: number;
|
|
588
|
+
opacity?: number;
|
|
589
|
+
rotation?: number;
|
|
590
|
+
}
|
|
591
|
+
declare function cloneHeart(heart: HeartObject): HeartObject;
|
|
592
|
+
declare function cloneLine(line: LineObject): LineObject;
|
|
593
|
+
declare function cloneArrow(arrow: ArrowObject): ArrowObject;
|
|
594
|
+
/**
|
|
595
|
+
* A logical grouping of other board objects (Phase 3 — Selection,
|
|
596
|
+
* Transformation & Grouping). Deliberately has no `x`/`y`/`transform` of its
|
|
597
|
+
* own — a group's bounds are always derived on demand from its (recursively
|
|
598
|
+
* resolved) children, and "moving/rotating/scaling the group" is exactly a
|
|
599
|
+
* multi-object transform applied to those children, nothing more. A group
|
|
600
|
+
* has no renderer/mesh of its own; its only visual presence is the
|
|
601
|
+
* selection gizmo's bounding box while it's the current selection.
|
|
602
|
+
*
|
|
603
|
+
* `children` may itself contain other group ids (nested groups) — expanding
|
|
604
|
+
* a group into its leaf members is always done by the caller (recursively,
|
|
605
|
+
* with cycle protection), never assumed here.
|
|
606
|
+
*/
|
|
607
|
+
interface GroupObject extends Lockable, Hideable {
|
|
608
|
+
id: string;
|
|
609
|
+
children: string[];
|
|
610
|
+
}
|
|
611
|
+
declare function cloneGroup(group: GroupObject): GroupObject;
|
|
612
|
+
|
|
393
613
|
interface BoardPoint {
|
|
394
614
|
x: number;
|
|
395
615
|
y: number;
|
|
@@ -403,9 +623,14 @@ interface StrokePoint extends BoardPoint {
|
|
|
403
623
|
*/
|
|
404
624
|
erase?: number;
|
|
405
625
|
}
|
|
406
|
-
/**
|
|
407
|
-
|
|
408
|
-
|
|
626
|
+
/**
|
|
627
|
+
* Which drawing tool made a stroke; undefined means marker (back-compat).
|
|
628
|
+
* `"shape"` (rect/ellipse/line/arrow/polygon/star/heart) renders at its
|
|
629
|
+
* exact configured width with no pressure variance or end taper — a
|
|
630
|
+
* geometric outline, not an expressive ink mark.
|
|
631
|
+
*/
|
|
632
|
+
type StrokeTool = "marker" | "highlighter" | "shape";
|
|
633
|
+
interface Stroke extends Lockable, Hideable {
|
|
409
634
|
id: string;
|
|
410
635
|
color: string;
|
|
411
636
|
baseWidth: number;
|
|
@@ -424,7 +649,7 @@ interface Stroke extends Lockable {
|
|
|
424
649
|
declare const ERASE_THRESHOLD = 0.95;
|
|
425
650
|
declare function cloneStroke(stroke: Stroke): Stroke;
|
|
426
651
|
type SerializedPoint = [number, number, number, number];
|
|
427
|
-
interface SerializedStroke extends Lockable {
|
|
652
|
+
interface SerializedStroke extends Lockable, Hideable {
|
|
428
653
|
id: string;
|
|
429
654
|
color: string;
|
|
430
655
|
baseWidth: number;
|
|
@@ -434,7 +659,36 @@ interface SerializedStroke extends Lockable {
|
|
|
434
659
|
matrix?: [number, number, number, number, number, number];
|
|
435
660
|
clusterId?: string;
|
|
436
661
|
}
|
|
662
|
+
/** A named notebook containing sections. */
|
|
663
|
+
interface BoardNotebook {
|
|
664
|
+
readonly id: string;
|
|
665
|
+
readonly name: string;
|
|
666
|
+
readonly color: string;
|
|
667
|
+
}
|
|
668
|
+
/** A named section containing pages. */
|
|
669
|
+
interface BoardSection {
|
|
670
|
+
readonly id: string;
|
|
671
|
+
readonly notebookId: string;
|
|
672
|
+
readonly name: string;
|
|
673
|
+
readonly color: string;
|
|
674
|
+
}
|
|
675
|
+
/** An additional page. The first page remains in the document's top-level collections. */
|
|
676
|
+
interface SerializedBoardPage {
|
|
677
|
+
id: string;
|
|
678
|
+
sectionId?: string;
|
|
679
|
+
name: string;
|
|
680
|
+
content: SerializedDocument;
|
|
681
|
+
}
|
|
437
682
|
interface SerializedDocument {
|
|
683
|
+
/** Notebook and section metadata. Older boards use an implicit default of each. */
|
|
684
|
+
notebooks?: BoardNotebook[];
|
|
685
|
+
sections?: BoardSection[];
|
|
686
|
+
/** Section of the original first page; defaults to `default`. */
|
|
687
|
+
pageSectionId?: string;
|
|
688
|
+
/** Display name of the first page (id `default`). */
|
|
689
|
+
pageName?: string;
|
|
690
|
+
/** Additional pages, in sidebar order. Nested pages are not allowed. */
|
|
691
|
+
pages?: SerializedBoardPage[];
|
|
438
692
|
/** Absent in every historical document; current saves always write 1. */
|
|
439
693
|
schemaVersion?: 1;
|
|
440
694
|
strokes: SerializedStroke[];
|
|
@@ -450,6 +704,31 @@ interface SerializedDocument {
|
|
|
450
704
|
timers?: KitchenTimer[];
|
|
451
705
|
/** Absent in documents saved before Custom board objects existed (ticket #22). */
|
|
452
706
|
customObjects?: CustomBoardObject[];
|
|
707
|
+
/** Absent in documents saved before semantic Rectangle objects existed (Phase 2). */
|
|
708
|
+
rectangles?: RectangleObject[];
|
|
709
|
+
/** Absent in documents saved before semantic Ellipse objects existed (Phase 2). */
|
|
710
|
+
ellipses?: EllipseObject[];
|
|
711
|
+
/** Absent in documents saved before Groups existed (Phase 3). */
|
|
712
|
+
groups?: GroupObject[];
|
|
713
|
+
/** Absent in documents saved before semantic Line objects existed (Phase 4). */
|
|
714
|
+
lines?: LineObject[];
|
|
715
|
+
/** Absent in documents saved before semantic Arrow objects existed (Phase 4). */
|
|
716
|
+
arrows?: ArrowObject[];
|
|
717
|
+
/** Absent in documents saved before semantic Polygon objects existed (Phase 4). */
|
|
718
|
+
polygons?: PolygonObject[];
|
|
719
|
+
/** Absent in documents saved before semantic Star objects existed (Phase 4). */
|
|
720
|
+
stars?: StarObject[];
|
|
721
|
+
/** Absent in documents saved before semantic Heart objects existed (Phase 4). */
|
|
722
|
+
hearts?: HeartObject[];
|
|
723
|
+
/**
|
|
724
|
+
* Every content-object id (every type above except comments, which are
|
|
725
|
+
* host-synced and never enter this schema) in paint order, back to front.
|
|
726
|
+
* Absent in documents saved before per-object z-order existed (Phase 3) —
|
|
727
|
+
* migration synthesizes a default order preserving the old fixed-Z-band
|
|
728
|
+
* visual stacking exactly, so an existing document never visibly changes
|
|
729
|
+
* on load; only an explicit reorder action touches this from then on.
|
|
730
|
+
*/
|
|
731
|
+
objectOrder?: string[];
|
|
453
732
|
}
|
|
454
733
|
declare const INK_COLORS: {
|
|
455
734
|
readonly black: "#1C1C1E";
|
|
@@ -458,16 +737,28 @@ declare const INK_COLORS: {
|
|
|
458
737
|
readonly green: "#16A34A";
|
|
459
738
|
};
|
|
460
739
|
declare const HIGHLIGHT_COLORS: {
|
|
740
|
+
readonly yellow: "#FACC15";
|
|
741
|
+
readonly green: "#4ADE80";
|
|
742
|
+
readonly pink: "#F472B6";
|
|
743
|
+
readonly blue: "#60A5FA";
|
|
744
|
+
};
|
|
745
|
+
declare const NOTE_COLORS: {
|
|
461
746
|
readonly yellow: "#FDE047";
|
|
462
|
-
readonly
|
|
747
|
+
readonly amber: "#FACC15";
|
|
748
|
+
readonly orange: "#FB923C";
|
|
749
|
+
readonly peach: "#FDBA74";
|
|
750
|
+
readonly red: "#FB7185";
|
|
751
|
+
readonly rose: "#FDA4AF";
|
|
463
752
|
readonly pink: "#F9A8D4";
|
|
753
|
+
readonly magenta: "#F472B6";
|
|
754
|
+
readonly purple: "#C084FC";
|
|
755
|
+
readonly lavender: "#C4B5FD";
|
|
464
756
|
readonly blue: "#93C5FD";
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
readonly
|
|
468
|
-
readonly
|
|
469
|
-
readonly
|
|
470
|
-
readonly green: "#BBF7D0";
|
|
757
|
+
readonly cornflower: "#60A5FA";
|
|
758
|
+
readonly sky: "#38BDF8";
|
|
759
|
+
readonly cyan: "#67E8F9";
|
|
760
|
+
readonly green: "#86EFAC";
|
|
761
|
+
readonly mint: "#4ADE80";
|
|
471
762
|
};
|
|
472
763
|
/**
|
|
473
764
|
* One collaborator's vote on a note. One per person; toggling removes it.
|
|
@@ -481,7 +772,7 @@ interface NoteVote {
|
|
|
481
772
|
* A sticky note: content floating above the board at a z-offset (pillar 3 —
|
|
482
773
|
* depth as an organizational axis). Center position in board space.
|
|
483
774
|
*/
|
|
484
|
-
interface StickyNote extends Lockable {
|
|
775
|
+
interface StickyNote extends Lockable, Hideable {
|
|
485
776
|
id: string;
|
|
486
777
|
x: number;
|
|
487
778
|
y: number;
|
|
@@ -499,7 +790,7 @@ interface StickyNote extends Lockable {
|
|
|
499
790
|
* top-left corner; lines flow downward (-y). Text joins the clustering
|
|
500
791
|
* system like handwriting (build prompt §6.4).
|
|
501
792
|
*/
|
|
502
|
-
interface TextBlock extends Lockable {
|
|
793
|
+
interface TextBlock extends Lockable, Hideable {
|
|
503
794
|
id: string;
|
|
504
795
|
x: number;
|
|
505
796
|
y: number;
|
|
@@ -529,7 +820,7 @@ declare function cloneNote(note: StickyNote): StickyNote;
|
|
|
529
820
|
* Interactive structured table on the board. Position (x, y) is top-left in board units.
|
|
530
821
|
* Cells are indexed as `${row},${col}` keys mapping to cell text content.
|
|
531
822
|
*/
|
|
532
|
-
interface TableBlock extends Lockable {
|
|
823
|
+
interface TableBlock extends Lockable, Hideable {
|
|
533
824
|
id: string;
|
|
534
825
|
x: number;
|
|
535
826
|
y: number;
|
|
@@ -550,13 +841,12 @@ declare function measureTable(table: TableBlock): {
|
|
|
550
841
|
width: number;
|
|
551
842
|
height: number;
|
|
552
843
|
};
|
|
553
|
-
declare const BOARD_COLOR = "#FFFFFF";
|
|
554
844
|
declare const FOG_COLOR = "#FFFFFF";
|
|
555
845
|
/**
|
|
556
846
|
* An imported image block on the board plane.
|
|
557
847
|
* Coordinates (x, y) represent the center of the image in board space.
|
|
558
848
|
*/
|
|
559
|
-
interface ImageBlock extends Lockable {
|
|
849
|
+
interface ImageBlock extends Lockable, Hideable {
|
|
560
850
|
id: string;
|
|
561
851
|
/**
|
|
562
852
|
* A legacy, read-only data URL (or, historically, an arbitrary string) —
|
|
@@ -580,13 +870,29 @@ interface ImageBlock extends Lockable {
|
|
|
580
870
|
}
|
|
581
871
|
declare function cloneImage(img: ImageBlock): ImageBlock;
|
|
582
872
|
|
|
873
|
+
declare const documentIdBrand: unique symbol;
|
|
874
|
+
declare const strokeIdBrand: unique symbol;
|
|
875
|
+
type DocumentId = string & {
|
|
876
|
+
readonly [documentIdBrand]: "DocumentId";
|
|
877
|
+
};
|
|
878
|
+
type StrokeId = string & {
|
|
879
|
+
readonly [strokeIdBrand]: "StrokeId";
|
|
880
|
+
};
|
|
881
|
+
declare function documentId(value: string): DocumentId;
|
|
882
|
+
declare function strokeId(value: string): StrokeId;
|
|
883
|
+
|
|
583
884
|
declare const CURRENT_DOCUMENT_SCHEMA_VERSION: 1;
|
|
584
885
|
type CurrentSerializedStroke = Omit<SerializedStroke, "id"> & {
|
|
585
886
|
id: StrokeId;
|
|
586
887
|
};
|
|
587
|
-
interface CurrentSerializedDocument extends Required<SerializedDocument
|
|
888
|
+
interface CurrentSerializedDocument extends Required<Omit<SerializedDocument, "pages" | "pageName" | "notebooks" | "sections" | "pageSectionId">> {
|
|
588
889
|
schemaVersion: typeof CURRENT_DOCUMENT_SCHEMA_VERSION;
|
|
589
890
|
strokes: CurrentSerializedStroke[];
|
|
891
|
+
pageName?: string;
|
|
892
|
+
notebooks?: BoardNotebook[];
|
|
893
|
+
sections?: BoardSection[];
|
|
894
|
+
pageSectionId?: string;
|
|
895
|
+
pages?: SerializedBoardPage[];
|
|
590
896
|
}
|
|
591
897
|
type DocumentRecoveryCode = "DOCUMENT_JSON_INVALID" | "DOCUMENT_VALIDATION_FAILED" | "DOCUMENT_VERSION_UNSUPPORTED";
|
|
592
898
|
declare class DocumentRecoveryError extends Error {
|
|
@@ -606,6 +912,15 @@ type DocumentLoadResult = {
|
|
|
606
912
|
declare function loadDocumentBytes(originalBytes: string): DocumentLoadResult;
|
|
607
913
|
declare function migrateDocument(raw: unknown): DocumentLoadResult;
|
|
608
914
|
declare function serializeDocument(document: unknown): string;
|
|
915
|
+
/**
|
|
916
|
+
* A Document's serialized size in bytes (Phase 5) — UTF-8, not UTF-16
|
|
917
|
+
* `string.length`, since a Document with non-ASCII note/text content (most
|
|
918
|
+
* of them, eventually) would otherwise under-report. Useful for a Host
|
|
919
|
+
* deciding when to warn about an unusually large board, or for logging/
|
|
920
|
+
* telemetry around save size — not consulted by anything inside this
|
|
921
|
+
* package itself, which has no size limit of its own.
|
|
922
|
+
*/
|
|
923
|
+
declare function documentSize(document: CurrentSerializedDocument): number;
|
|
609
924
|
|
|
610
925
|
interface SearchableComment {
|
|
611
926
|
id: string;
|
|
@@ -640,6 +955,18 @@ interface SearchableBoard {
|
|
|
640
955
|
*/
|
|
641
956
|
declare function searchBoard(query: string, board: SearchableBoard): SearchHit[];
|
|
642
957
|
|
|
958
|
+
interface ConnectorBox {
|
|
959
|
+
minX: number;
|
|
960
|
+
minY: number;
|
|
961
|
+
maxX: number;
|
|
962
|
+
maxY: number;
|
|
963
|
+
}
|
|
964
|
+
/** A bindable object's footprint: its bounds and whether its outline is the box or the ellipse inside it. */
|
|
965
|
+
interface ConnectorTarget {
|
|
966
|
+
box: ConnectorBox;
|
|
967
|
+
outline: "box" | "ellipse";
|
|
968
|
+
}
|
|
969
|
+
|
|
643
970
|
/**
|
|
644
971
|
* The one place a live Stroke becomes its wire representation — in
|
|
645
972
|
* particular, points collapse from {x,y,pressure,erase?} objects into
|
|
@@ -690,6 +1017,60 @@ interface DocumentChange {
|
|
|
690
1017
|
/** Ids of removed custom objects. */
|
|
691
1018
|
customObjectsRemoved: string[];
|
|
692
1019
|
customObjectsUpdated: CustomBoardObject[];
|
|
1020
|
+
/** Semantic Rectangle objects (Phase 2). */
|
|
1021
|
+
rectanglesAdded: RectangleObject[];
|
|
1022
|
+
/** Ids of removed rectangles. */
|
|
1023
|
+
rectanglesRemoved: string[];
|
|
1024
|
+
rectanglesUpdated: RectangleObject[];
|
|
1025
|
+
/** Semantic Ellipse objects (Phase 2). */
|
|
1026
|
+
ellipsesAdded: EllipseObject[];
|
|
1027
|
+
/** Ids of removed ellipses. */
|
|
1028
|
+
ellipsesRemoved: string[];
|
|
1029
|
+
ellipsesUpdated: EllipseObject[];
|
|
1030
|
+
/** Groups (Phase 3). */
|
|
1031
|
+
groupsAdded: GroupObject[];
|
|
1032
|
+
/** Ids of removed groups (ungrouping, or deleting a group). */
|
|
1033
|
+
groupsRemoved: string[];
|
|
1034
|
+
groupsUpdated: GroupObject[];
|
|
1035
|
+
/** Semantic Line objects (Phase 4). */
|
|
1036
|
+
linesAdded: LineObject[];
|
|
1037
|
+
/** Ids of removed lines. */
|
|
1038
|
+
linesRemoved: string[];
|
|
1039
|
+
linesUpdated: LineObject[];
|
|
1040
|
+
/** Semantic Arrow objects (Phase 4). */
|
|
1041
|
+
arrowsAdded: ArrowObject[];
|
|
1042
|
+
/** Ids of removed arrows. */
|
|
1043
|
+
arrowsRemoved: string[];
|
|
1044
|
+
arrowsUpdated: ArrowObject[];
|
|
1045
|
+
/** Semantic Polygon objects (Phase 4) — Triangle/Diamond/Pentagon/Hexagon/Octagon. */
|
|
1046
|
+
polygonsAdded: PolygonObject[];
|
|
1047
|
+
/** Ids of removed polygons. */
|
|
1048
|
+
polygonsRemoved: string[];
|
|
1049
|
+
polygonsUpdated: PolygonObject[];
|
|
1050
|
+
/** Semantic Star objects (Phase 4). */
|
|
1051
|
+
starsAdded: StarObject[];
|
|
1052
|
+
/** Ids of removed stars. */
|
|
1053
|
+
starsRemoved: string[];
|
|
1054
|
+
starsUpdated: StarObject[];
|
|
1055
|
+
/** Semantic Heart objects (Phase 4). */
|
|
1056
|
+
heartsAdded: HeartObject[];
|
|
1057
|
+
/** Ids of removed hearts. */
|
|
1058
|
+
heartsRemoved: string[];
|
|
1059
|
+
heartsUpdated: HeartObject[];
|
|
1060
|
+
/**
|
|
1061
|
+
* The full current paint order (back to front) of every flat content
|
|
1062
|
+
* object — strokes, texts, tables, images, rectangles, ellipses, lines,
|
|
1063
|
+
* arrows, custom objects, and groups. Populated whenever `objectOrder`
|
|
1064
|
+
* actually changed:
|
|
1065
|
+
* an explicit reorder (`bringForward` etc.), or any add/remove that
|
|
1066
|
+
* touches it — a removal shifts every id after it down one rank, not
|
|
1067
|
+
* just the removed one, so renderers need this to resync everyone, not
|
|
1068
|
+
* only the ids the same change's own `*Added`/`*Removed`/`*Updated`
|
|
1069
|
+
* fields name. Notes (their own `zOffset` peel depth) and Kitchen Timers
|
|
1070
|
+
* (genuine 3D objects, not a flat layer) are intentionally not part of
|
|
1071
|
+
* this order at all.
|
|
1072
|
+
*/
|
|
1073
|
+
orderChanged: readonly string[];
|
|
693
1074
|
}
|
|
694
1075
|
type Listener = (change: DocumentChange) => void;
|
|
695
1076
|
declare class BoardDocument {
|
|
@@ -704,12 +1085,78 @@ declare class BoardDocument {
|
|
|
704
1085
|
private readonly timers;
|
|
705
1086
|
/** All Custom board object types share one map, keyed by id — the envelope is already uniform. */
|
|
706
1087
|
private readonly customObjects;
|
|
1088
|
+
private readonly rectangles;
|
|
1089
|
+
private readonly ellipses;
|
|
1090
|
+
private readonly groups;
|
|
1091
|
+
private readonly lines;
|
|
1092
|
+
private readonly arrows;
|
|
1093
|
+
private readonly polygons;
|
|
1094
|
+
private readonly stars;
|
|
1095
|
+
private readonly hearts;
|
|
707
1096
|
private readonly bboxes;
|
|
708
1097
|
private readonly listeners;
|
|
1098
|
+
/** Paint order (back to front) of every flat content object — see `DocumentChange.orderChanged`'s doc comment. */
|
|
1099
|
+
private objectOrder;
|
|
1100
|
+
private orderIndex;
|
|
709
1101
|
constructor(id: DocumentId);
|
|
1102
|
+
private reindexOrder;
|
|
1103
|
+
/** The full current paint order, back to front. */
|
|
1104
|
+
order(): readonly string[];
|
|
1105
|
+
/** This object's rank in the paint order, or -1 if it doesn't participate (unknown id, a note, or a timer). */
|
|
1106
|
+
orderRank(id: string): number;
|
|
1107
|
+
private bringForward;
|
|
1108
|
+
private sendBackward;
|
|
1109
|
+
private bringToFront;
|
|
1110
|
+
private sendToBack;
|
|
1111
|
+
/** Reorders `id` relative to its current neighbors. A no-op for an id that doesn't participate in paint order (see `orderRank`). */
|
|
1112
|
+
reorder(id: string, direction: "forward" | "backward" | "front" | "back"): void;
|
|
1113
|
+
/**
|
|
1114
|
+
* Overwrites the paint order directly — used only when loading a document
|
|
1115
|
+
* that already carries a persisted `objectOrder`; every other order
|
|
1116
|
+
* mutation goes through `reorder`/the automatic append-on-add tracking in
|
|
1117
|
+
* `emit`. Ids not present in the document are dropped; ids present in the
|
|
1118
|
+
* document but missing from `order` are appended at the back, so a
|
|
1119
|
+
* partially-stale order (e.g. from a schema migration) never silently
|
|
1120
|
+
* drops an object from paint order entirely.
|
|
1121
|
+
*/
|
|
1122
|
+
private setOrder;
|
|
710
1123
|
get(id: string): Stroke | undefined;
|
|
711
1124
|
all(): IterableIterator<Stroke>;
|
|
712
|
-
|
|
1125
|
+
/**
|
|
1126
|
+
* World-space bounds for any content object, of any type. Strokes hit
|
|
1127
|
+
* their cached-on-mutation fast path (`bboxes`, populated by
|
|
1128
|
+
* `addStrokes`/`transformStrokes` — many points, worth caching); every
|
|
1129
|
+
* other type computes on demand via `objectBounds.ts` (cheap arithmetic,
|
|
1130
|
+
* no caching needed). A group's bounds are the union of its (recursively
|
|
1131
|
+
* resolved) children — `seen` guards against a cycle in nested groups.
|
|
1132
|
+
*/
|
|
1133
|
+
/**
|
|
1134
|
+
* What a connector can attach to at `id`: its bounds and outline shape.
|
|
1135
|
+
* Connectors, lines and groups aren't targets (that would allow cycles).
|
|
1136
|
+
*/
|
|
1137
|
+
connectorTarget(id: string): ConnectorTarget | undefined;
|
|
1138
|
+
bbox(id: string, seen?: Set<string>): BBox | undefined;
|
|
1139
|
+
/**
|
|
1140
|
+
* True if `id` exists and is locked, for any type — the same per-type
|
|
1141
|
+
* probe pattern as `bbox`, for interactive gestures (drag/transform) that
|
|
1142
|
+
* need to gate on lock state regardless of what's selected. Custom
|
|
1143
|
+
* objects are deliberately excluded: their `lock` field is a different
|
|
1144
|
+
* shape (`{holderId, acquiredAt}`, no display name) with no interactive
|
|
1145
|
+
* lock UI yet, matching the existing, deliberate "always unlockable,
|
|
1146
|
+
* never gates a drag" treatment already established elsewhere (e.g.
|
|
1147
|
+
* `getSelectedItemInfo`'s custom branch hardcodes `isLocked: false`).
|
|
1148
|
+
*/
|
|
1149
|
+
isLocked(id: string): boolean;
|
|
1150
|
+
/**
|
|
1151
|
+
* True if `id` exists and is hidden, for any type — the same per-type
|
|
1152
|
+
* probe pattern as {@link isLocked} (Phase 8). Custom objects are
|
|
1153
|
+
* excluded for the same reason `isLocked` excludes them: they have no
|
|
1154
|
+
* `Hideable` field at all, so "hidden" isn't a concept that applies to
|
|
1155
|
+
* them yet. Used by marquee selection (`selectTool.ts`) to keep a hidden
|
|
1156
|
+
* object out of a rubber-band selection even for object types whose own
|
|
1157
|
+
* renderer doesn't yet suppress click-based hit-testing.
|
|
1158
|
+
*/
|
|
1159
|
+
isHidden(id: string): boolean;
|
|
713
1160
|
subscribe(listener: Listener): () => void;
|
|
714
1161
|
addStrokes(strokes: Stroke[]): void;
|
|
715
1162
|
removeStrokes(ids: string[]): void;
|
|
@@ -752,6 +1199,54 @@ declare class BoardDocument {
|
|
|
752
1199
|
removeCustomObjects(ids: string[]): void;
|
|
753
1200
|
/** Replace a custom object's contents under the same id. */
|
|
754
1201
|
setCustomObject(object: CustomBoardObject): void;
|
|
1202
|
+
getRectangle(id: string): RectangleObject | undefined;
|
|
1203
|
+
allRectangles(): IterableIterator<RectangleObject>;
|
|
1204
|
+
addRectangles(rectangles: RectangleObject[]): void;
|
|
1205
|
+
removeRectangles(ids: string[]): void;
|
|
1206
|
+
/** Replace a rectangle's contents (move, resize, restyle) under the same id. */
|
|
1207
|
+
setRectangle(rect: RectangleObject): void;
|
|
1208
|
+
getEllipse(id: string): EllipseObject | undefined;
|
|
1209
|
+
allEllipses(): IterableIterator<EllipseObject>;
|
|
1210
|
+
addEllipses(ellipses: EllipseObject[]): void;
|
|
1211
|
+
removeEllipses(ids: string[]): void;
|
|
1212
|
+
/** Replace an ellipse's contents (move, resize, restyle) under the same id. */
|
|
1213
|
+
setEllipse(ellipse: EllipseObject): void;
|
|
1214
|
+
getGroup(id: string): GroupObject | undefined;
|
|
1215
|
+
allGroups(): IterableIterator<GroupObject>;
|
|
1216
|
+
addGroups(groups: GroupObject[]): void;
|
|
1217
|
+
removeGroups(ids: string[]): void;
|
|
1218
|
+
/** Replace a group's contents (its children list) under the same id. */
|
|
1219
|
+
setGroup(group: GroupObject): void;
|
|
1220
|
+
getLine(id: string): LineObject | undefined;
|
|
1221
|
+
allLines(): IterableIterator<LineObject>;
|
|
1222
|
+
addLines(lines: LineObject[]): void;
|
|
1223
|
+
removeLines(ids: string[]): void;
|
|
1224
|
+
/** Replace a line's contents (move, restyle) under the same id. */
|
|
1225
|
+
setLine(line: LineObject): void;
|
|
1226
|
+
getArrow(id: string): ArrowObject | undefined;
|
|
1227
|
+
allArrows(): IterableIterator<ArrowObject>;
|
|
1228
|
+
addArrows(arrows: ArrowObject[]): void;
|
|
1229
|
+
removeArrows(ids: string[]): void;
|
|
1230
|
+
/** Replace an arrow's contents (move, restyle, change head) under the same id. */
|
|
1231
|
+
setArrow(arrow: ArrowObject): void;
|
|
1232
|
+
getPolygon(id: string): PolygonObject | undefined;
|
|
1233
|
+
allPolygons(): IterableIterator<PolygonObject>;
|
|
1234
|
+
addPolygons(polygons: PolygonObject[]): void;
|
|
1235
|
+
removePolygons(ids: string[]): void;
|
|
1236
|
+
/** Replace a polygon's contents (move, resize, rotate, restyle) under the same id. */
|
|
1237
|
+
setPolygon(polygon: PolygonObject): void;
|
|
1238
|
+
getStar(id: string): StarObject | undefined;
|
|
1239
|
+
allStars(): IterableIterator<StarObject>;
|
|
1240
|
+
addStars(stars: StarObject[]): void;
|
|
1241
|
+
removeStars(ids: string[]): void;
|
|
1242
|
+
/** Replace a star's contents (move, resize, rotate, restyle) under the same id. */
|
|
1243
|
+
setStar(star: StarObject): void;
|
|
1244
|
+
getHeart(id: string): HeartObject | undefined;
|
|
1245
|
+
allHearts(): IterableIterator<HeartObject>;
|
|
1246
|
+
addHearts(hearts: HeartObject[]): void;
|
|
1247
|
+
removeHearts(ids: string[]): void;
|
|
1248
|
+
/** Replace a heart's contents (move, resize, rotate, restyle) under the same id. */
|
|
1249
|
+
setHeart(heart: HeartObject): void;
|
|
755
1250
|
setStrokeLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
756
1251
|
setStrokesLocked(ids: string[], locked: boolean, by?: LockHolder | null): void;
|
|
757
1252
|
setNoteLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
@@ -759,18 +1254,69 @@ declare class BoardDocument {
|
|
|
759
1254
|
setTableLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
760
1255
|
setImageLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
761
1256
|
setTimerLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
1257
|
+
setRectangleLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
1258
|
+
setEllipseLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
1259
|
+
setGroupLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
1260
|
+
setLineLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
1261
|
+
setArrowLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
1262
|
+
setPolygonLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
1263
|
+
setStarLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
1264
|
+
setHeartLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
762
1265
|
/** Replace all content (initial load). Does not touch `version`. */
|
|
763
|
-
replaceAll(strokes: Stroke[], notes: StickyNote[], texts: TextBlock[], tables?: TableBlock[], images?: ImageBlock[], timers?: KitchenTimer[], customObjects?: CustomBoardObject[]
|
|
1266
|
+
replaceAll(strokes: Stroke[], notes: StickyNote[], texts: TextBlock[], tables?: TableBlock[], images?: ImageBlock[], timers?: KitchenTimer[], customObjects?: CustomBoardObject[], rectangles?: RectangleObject[], ellipses?: EllipseObject[], groups?: GroupObject[], lines?: LineObject[], arrows?: ArrowObject[], polygons?: PolygonObject[], stars?: StarObject[], hearts?: HeartObject[],
|
|
1267
|
+
/** Persisted paint order; absent for a document saved before Phase 3, in which case one is synthesized (see `ORDERED_ADDED_FIELDS`'s doc comment). */
|
|
1268
|
+
objectOrder?: readonly string[]): void;
|
|
764
1269
|
/** Apply incremental real-time change received from a remote collaborator over WebSocket. */
|
|
765
1270
|
applyRemoteChange(change: Partial<DocumentChange>): void;
|
|
766
1271
|
toJSON(): SerializedDocument;
|
|
1272
|
+
static deserializeGroups(data: SerializedDocument): GroupObject[];
|
|
1273
|
+
static deserializeLines(data: SerializedDocument): LineObject[];
|
|
1274
|
+
static deserializeArrows(data: SerializedDocument): ArrowObject[];
|
|
1275
|
+
static deserializePolygons(data: SerializedDocument): PolygonObject[];
|
|
1276
|
+
static deserializeStars(data: SerializedDocument): StarObject[];
|
|
1277
|
+
static deserializeHearts(data: SerializedDocument): HeartObject[];
|
|
767
1278
|
static deserializeCustomObjects(data: SerializedDocument): CustomBoardObject[];
|
|
1279
|
+
static deserializeRectangles(data: SerializedDocument): RectangleObject[];
|
|
1280
|
+
static deserializeEllipses(data: SerializedDocument): EllipseObject[];
|
|
768
1281
|
static deserializeImages(data: SerializedDocument): ImageBlock[];
|
|
769
1282
|
static deserializeTimers(data: SerializedDocument): KitchenTimer[];
|
|
770
1283
|
static deserializeNotes(data: SerializedDocument): StickyNote[];
|
|
771
1284
|
static deserializeTexts(data: SerializedDocument): TextBlock[];
|
|
772
1285
|
static deserializeTables(data: SerializedDocument): TableBlock[];
|
|
773
|
-
|
|
1286
|
+
/**
|
|
1287
|
+
* `onSkip` (Phase 9) replaces an unconditional `console.error` — `core`
|
|
1288
|
+
* must never do raw console I/O (no dev-gate, no way for a Host to
|
|
1289
|
+
* suppress or redirect it), so a skipped stroke is now reported only if
|
|
1290
|
+
* the caller asks for it, via whatever diagnostic channel it already
|
|
1291
|
+
* has (e.g. `controller-internal.ts` routes this into the same typed
|
|
1292
|
+
* `"error"` event every other diagnostic already uses). Silent by
|
|
1293
|
+
* default, matching how every other `deserialize*` method here already
|
|
1294
|
+
* behaves (no diagnostics at all).
|
|
1295
|
+
*/
|
|
1296
|
+
static deserializeStrokes(data: SerializedDocument, onSkip?: (id: string, cause: unknown) => void): Stroke[];
|
|
1297
|
+
/**
|
|
1298
|
+
* Every mutation funnels through here, so paint-order tracking lives in
|
|
1299
|
+
* exactly one place rather than at every individual add/remove call site
|
|
1300
|
+
* (18 of them, times `replaceAll`/`applyRemoteChange`) — new ids are
|
|
1301
|
+
* appended to the back (front-most) of `objectOrder`, removed ids are
|
|
1302
|
+
* spliced out. An explicit reorder (`reorder`/`setOrder`) updates
|
|
1303
|
+
* `objectOrder` itself before calling this, so this step is a no-op for
|
|
1304
|
+
* ids already tracked (idempotent by construction: `orderIndex.has` gates
|
|
1305
|
+
* every append).
|
|
1306
|
+
*
|
|
1307
|
+
* `orderChanged` (Phase 9) reports exactly the ids whose rank actually
|
|
1308
|
+
* changed, using `orderIndex` throughout instead of `indexOf` — a pure
|
|
1309
|
+
* append never shifts any existing id's rank (new ids land at the tail,
|
|
1310
|
+
* already covered by this same change's own `Added` field, so
|
|
1311
|
+
* `orderChanged` stays unset), while a removal shifts every id at-or-
|
|
1312
|
+
* after the lowest removed rank down by one, computed in a single O(n)
|
|
1313
|
+
* filter pass (not one `indexOf`+`splice` per removed id) regardless of
|
|
1314
|
+
* how many ids this one change removes. Every renderer's `onChange` now
|
|
1315
|
+
* looks up only the ids actually in `orderChanged` instead of walking
|
|
1316
|
+
* its entire mesh map on any order-touching change — a broad, unfiltered
|
|
1317
|
+
* `orderChanged` here would silently defeat that fix, not just waste
|
|
1318
|
+
* cycles here.
|
|
1319
|
+
*/
|
|
774
1320
|
private emit;
|
|
775
1321
|
}
|
|
776
1322
|
|
|
@@ -806,12 +1352,33 @@ interface Command {
|
|
|
806
1352
|
apply(doc: BoardDocument): void;
|
|
807
1353
|
revert(doc: BoardDocument): void;
|
|
808
1354
|
}
|
|
1355
|
+
/**
|
|
1356
|
+
* Several commands applied/reverted together as one undo step (Phase 3
|
|
1357
|
+
* consolidation — this exact class used to be hand-duplicated as a private
|
|
1358
|
+
* `CommandBatch` in `controller-internal.ts` and an exported
|
|
1359
|
+
* `ExtensionCommandBatch` in `interaction/tools/customTool.ts`; both now
|
|
1360
|
+
* import this one instead). Revert runs in reverse order, so a batch that
|
|
1361
|
+
* depends on ordering (e.g. add-then-reference) undoes cleanly.
|
|
1362
|
+
*/
|
|
1363
|
+
declare class CommandBatch implements Command {
|
|
1364
|
+
readonly label: string;
|
|
1365
|
+
private readonly commands;
|
|
1366
|
+
constructor(label: string, commands: readonly Command[]);
|
|
1367
|
+
apply(doc: BoardDocument): void;
|
|
1368
|
+
revert(doc: BoardDocument): void;
|
|
1369
|
+
}
|
|
809
1370
|
/** How a command reached the document — undo/redo are audited distinctly. */
|
|
810
1371
|
type CommandKind = "do" | "undo" | "redo";
|
|
811
1372
|
declare class History {
|
|
812
1373
|
private readonly doc;
|
|
813
1374
|
private readonly undoStack;
|
|
814
1375
|
private readonly redoStack;
|
|
1376
|
+
private scope;
|
|
1377
|
+
private readonly scopes;
|
|
1378
|
+
/** Switch independent page histories while retaining the same document instance. */
|
|
1379
|
+
setScope(id: string): void;
|
|
1380
|
+
/** Discard all page histories after replacing a saved document. */
|
|
1381
|
+
clearScopes(): void;
|
|
815
1382
|
/**
|
|
816
1383
|
* Observer for every mutation, in one place: all board edits funnel
|
|
817
1384
|
* through record/undo/redo. The audit trail listens here.
|
|
@@ -870,6 +1437,40 @@ declare class TransformCommand implements Command {
|
|
|
870
1437
|
revert(doc: BoardDocument): void;
|
|
871
1438
|
private compose;
|
|
872
1439
|
}
|
|
1440
|
+
/**
|
|
1441
|
+
* One move gesture over a mixed-type selection (Phase 3) — the general
|
|
1442
|
+
* successor to `TransformCommand` for translation. `TransformCommand`
|
|
1443
|
+
* itself stays as-is (still used for scale/rotate, which remain stroke-only
|
|
1444
|
+
* this phase — see `interaction/tools/selectTool.ts`'s `TransformState`):
|
|
1445
|
+
* its own per-id `doc.get(id)` check already no-ops safely for any id that
|
|
1446
|
+
* isn't a stroke, so it doesn't need touching for that narrower case.
|
|
1447
|
+
*
|
|
1448
|
+
* `delta` here is always a pure translation (never scale/rotate), so
|
|
1449
|
+
* `apply(delta, point)` is a safe, uniform way to move every position-only
|
|
1450
|
+
* type's `x`/`y` — translating commutes trivially regardless of a shape's
|
|
1451
|
+
* own rotation. Matrix-carrying types (stroke, Custom) instead compose
|
|
1452
|
+
* `delta` onto their existing matrix/transform, matching `TransformCommand`.
|
|
1453
|
+
*
|
|
1454
|
+
* A group id in `ids` is expanded into its (recursively resolved, cycle-
|
|
1455
|
+
* safe) children every time `compose` runs — deterministic, since group
|
|
1456
|
+
* membership never changes mid-command — so moving a selected group means
|
|
1457
|
+
* moving every one of its members by the same delta. This expansion is
|
|
1458
|
+
* `TransformObjectsCommand`'s own responsibility precisely so a caller that
|
|
1459
|
+
* doesn't itself expand groups (e.g. `ScrawlEngine.nudgeSelection`) still
|
|
1460
|
+
* gets correct behavior; `interaction/tools/selectTool.ts`'s `TransformState`
|
|
1461
|
+
* separately expands for its own reason (live per-child drag preview), which
|
|
1462
|
+
* makes this a no-op re-expansion for that caller, not a conflict.
|
|
1463
|
+
*/
|
|
1464
|
+
declare class TransformObjectsCommand implements Command {
|
|
1465
|
+
private readonly ids;
|
|
1466
|
+
private readonly delta;
|
|
1467
|
+
readonly label = "move selection";
|
|
1468
|
+
private readonly inverse;
|
|
1469
|
+
constructor(ids: readonly string[], delta: Mat2x3);
|
|
1470
|
+
apply(doc: BoardDocument): void;
|
|
1471
|
+
revert(doc: BoardDocument): void;
|
|
1472
|
+
private compose;
|
|
1473
|
+
}
|
|
873
1474
|
declare class AddNoteCommand implements Command {
|
|
874
1475
|
readonly label = "add note";
|
|
875
1476
|
private readonly note;
|
|
@@ -991,8 +1592,213 @@ declare class DeleteTimerCommand implements Command {
|
|
|
991
1592
|
apply(doc: BoardDocument): void;
|
|
992
1593
|
revert(doc: BoardDocument): void;
|
|
993
1594
|
}
|
|
1595
|
+
/** Add/update/delete for semantic Rectangle objects (Phase 2). */
|
|
1596
|
+
declare class AddRectangleCommand implements Command {
|
|
1597
|
+
readonly label = "add rectangle";
|
|
1598
|
+
private readonly rect;
|
|
1599
|
+
constructor(rect: RectangleObject);
|
|
1600
|
+
apply(doc: BoardDocument): void;
|
|
1601
|
+
revert(doc: BoardDocument): void;
|
|
1602
|
+
}
|
|
1603
|
+
declare class UpdateRectangleCommand implements Command {
|
|
1604
|
+
readonly label = "update rectangle";
|
|
1605
|
+
private readonly before;
|
|
1606
|
+
private readonly after;
|
|
1607
|
+
constructor(before: RectangleObject, after: RectangleObject);
|
|
1608
|
+
apply(doc: BoardDocument): void;
|
|
1609
|
+
revert(doc: BoardDocument): void;
|
|
1610
|
+
}
|
|
1611
|
+
declare class DeleteRectangleCommand implements Command {
|
|
1612
|
+
readonly label = "delete rectangle";
|
|
1613
|
+
private readonly rect;
|
|
1614
|
+
constructor(rect: RectangleObject);
|
|
1615
|
+
apply(doc: BoardDocument): void;
|
|
1616
|
+
revert(doc: BoardDocument): void;
|
|
1617
|
+
}
|
|
1618
|
+
/** Add/update/delete for semantic Ellipse objects (Phase 2). */
|
|
1619
|
+
declare class AddEllipseCommand implements Command {
|
|
1620
|
+
readonly label = "add ellipse";
|
|
1621
|
+
private readonly ellipse;
|
|
1622
|
+
constructor(ellipse: EllipseObject);
|
|
1623
|
+
apply(doc: BoardDocument): void;
|
|
1624
|
+
revert(doc: BoardDocument): void;
|
|
1625
|
+
}
|
|
1626
|
+
declare class UpdateEllipseCommand implements Command {
|
|
1627
|
+
readonly label = "update ellipse";
|
|
1628
|
+
private readonly before;
|
|
1629
|
+
private readonly after;
|
|
1630
|
+
constructor(before: EllipseObject, after: EllipseObject);
|
|
1631
|
+
apply(doc: BoardDocument): void;
|
|
1632
|
+
revert(doc: BoardDocument): void;
|
|
1633
|
+
}
|
|
1634
|
+
declare class DeleteEllipseCommand implements Command {
|
|
1635
|
+
readonly label = "delete ellipse";
|
|
1636
|
+
private readonly ellipse;
|
|
1637
|
+
constructor(ellipse: EllipseObject);
|
|
1638
|
+
apply(doc: BoardDocument): void;
|
|
1639
|
+
revert(doc: BoardDocument): void;
|
|
1640
|
+
}
|
|
1641
|
+
/** Add/update/delete for semantic Line objects (Phase 4). */
|
|
1642
|
+
declare class AddLineCommand implements Command {
|
|
1643
|
+
readonly label = "add line";
|
|
1644
|
+
private readonly line;
|
|
1645
|
+
constructor(line: LineObject);
|
|
1646
|
+
apply(doc: BoardDocument): void;
|
|
1647
|
+
revert(doc: BoardDocument): void;
|
|
1648
|
+
}
|
|
1649
|
+
declare class UpdateLineCommand implements Command {
|
|
1650
|
+
readonly label = "update line";
|
|
1651
|
+
private readonly before;
|
|
1652
|
+
private readonly after;
|
|
1653
|
+
constructor(before: LineObject, after: LineObject);
|
|
1654
|
+
apply(doc: BoardDocument): void;
|
|
1655
|
+
revert(doc: BoardDocument): void;
|
|
1656
|
+
}
|
|
1657
|
+
declare class DeleteLineCommand implements Command {
|
|
1658
|
+
readonly label = "delete line";
|
|
1659
|
+
private readonly line;
|
|
1660
|
+
constructor(line: LineObject);
|
|
1661
|
+
apply(doc: BoardDocument): void;
|
|
1662
|
+
revert(doc: BoardDocument): void;
|
|
1663
|
+
}
|
|
1664
|
+
/** Add/update/delete for semantic Arrow objects (Phase 4). */
|
|
1665
|
+
declare class AddArrowCommand implements Command {
|
|
1666
|
+
readonly label = "add arrow";
|
|
1667
|
+
private readonly arrow;
|
|
1668
|
+
constructor(arrow: ArrowObject);
|
|
1669
|
+
apply(doc: BoardDocument): void;
|
|
1670
|
+
revert(doc: BoardDocument): void;
|
|
1671
|
+
}
|
|
1672
|
+
declare class UpdateArrowCommand implements Command {
|
|
1673
|
+
readonly label = "update arrow";
|
|
1674
|
+
private readonly before;
|
|
1675
|
+
private readonly after;
|
|
1676
|
+
constructor(before: ArrowObject, after: ArrowObject);
|
|
1677
|
+
apply(doc: BoardDocument): void;
|
|
1678
|
+
revert(doc: BoardDocument): void;
|
|
1679
|
+
}
|
|
1680
|
+
declare class DeleteArrowCommand implements Command {
|
|
1681
|
+
readonly label = "delete arrow";
|
|
1682
|
+
private readonly arrow;
|
|
1683
|
+
constructor(arrow: ArrowObject);
|
|
1684
|
+
apply(doc: BoardDocument): void;
|
|
1685
|
+
revert(doc: BoardDocument): void;
|
|
1686
|
+
}
|
|
1687
|
+
/** Add/update/delete for semantic Polygon objects (Phase 4) — Triangle/Diamond/Pentagon/Hexagon/Octagon. */
|
|
1688
|
+
declare class AddPolygonCommand implements Command {
|
|
1689
|
+
readonly label = "add polygon";
|
|
1690
|
+
private readonly polygon;
|
|
1691
|
+
constructor(polygon: PolygonObject);
|
|
1692
|
+
apply(doc: BoardDocument): void;
|
|
1693
|
+
revert(doc: BoardDocument): void;
|
|
1694
|
+
}
|
|
1695
|
+
declare class UpdatePolygonCommand implements Command {
|
|
1696
|
+
readonly label = "update polygon";
|
|
1697
|
+
private readonly before;
|
|
1698
|
+
private readonly after;
|
|
1699
|
+
constructor(before: PolygonObject, after: PolygonObject);
|
|
1700
|
+
apply(doc: BoardDocument): void;
|
|
1701
|
+
revert(doc: BoardDocument): void;
|
|
1702
|
+
}
|
|
1703
|
+
declare class DeletePolygonCommand implements Command {
|
|
1704
|
+
readonly label = "delete polygon";
|
|
1705
|
+
private readonly polygon;
|
|
1706
|
+
constructor(polygon: PolygonObject);
|
|
1707
|
+
apply(doc: BoardDocument): void;
|
|
1708
|
+
revert(doc: BoardDocument): void;
|
|
1709
|
+
}
|
|
1710
|
+
/** Add/update/delete for semantic Star objects (Phase 4). */
|
|
1711
|
+
declare class AddStarCommand implements Command {
|
|
1712
|
+
readonly label = "add star";
|
|
1713
|
+
private readonly star;
|
|
1714
|
+
constructor(star: StarObject);
|
|
1715
|
+
apply(doc: BoardDocument): void;
|
|
1716
|
+
revert(doc: BoardDocument): void;
|
|
1717
|
+
}
|
|
1718
|
+
declare class UpdateStarCommand implements Command {
|
|
1719
|
+
readonly label = "update star";
|
|
1720
|
+
private readonly before;
|
|
1721
|
+
private readonly after;
|
|
1722
|
+
constructor(before: StarObject, after: StarObject);
|
|
1723
|
+
apply(doc: BoardDocument): void;
|
|
1724
|
+
revert(doc: BoardDocument): void;
|
|
1725
|
+
}
|
|
1726
|
+
declare class DeleteStarCommand implements Command {
|
|
1727
|
+
readonly label = "delete star";
|
|
1728
|
+
private readonly star;
|
|
1729
|
+
constructor(star: StarObject);
|
|
1730
|
+
apply(doc: BoardDocument): void;
|
|
1731
|
+
revert(doc: BoardDocument): void;
|
|
1732
|
+
}
|
|
1733
|
+
/** Add/update/delete for semantic Heart objects (Phase 4). */
|
|
1734
|
+
declare class AddHeartCommand implements Command {
|
|
1735
|
+
readonly label = "add heart";
|
|
1736
|
+
private readonly heart;
|
|
1737
|
+
constructor(heart: HeartObject);
|
|
1738
|
+
apply(doc: BoardDocument): void;
|
|
1739
|
+
revert(doc: BoardDocument): void;
|
|
1740
|
+
}
|
|
1741
|
+
declare class UpdateHeartCommand implements Command {
|
|
1742
|
+
readonly label = "update heart";
|
|
1743
|
+
private readonly before;
|
|
1744
|
+
private readonly after;
|
|
1745
|
+
constructor(before: HeartObject, after: HeartObject);
|
|
1746
|
+
apply(doc: BoardDocument): void;
|
|
1747
|
+
revert(doc: BoardDocument): void;
|
|
1748
|
+
}
|
|
1749
|
+
declare class DeleteHeartCommand implements Command {
|
|
1750
|
+
readonly label = "delete heart";
|
|
1751
|
+
private readonly heart;
|
|
1752
|
+
constructor(heart: HeartObject);
|
|
1753
|
+
apply(doc: BoardDocument): void;
|
|
1754
|
+
revert(doc: BoardDocument): void;
|
|
1755
|
+
}
|
|
1756
|
+
/** Add/update/delete for Groups (Phase 3). */
|
|
1757
|
+
declare class AddGroupCommand implements Command {
|
|
1758
|
+
readonly label = "group";
|
|
1759
|
+
private readonly group;
|
|
1760
|
+
constructor(group: GroupObject);
|
|
1761
|
+
apply(doc: BoardDocument): void;
|
|
1762
|
+
revert(doc: BoardDocument): void;
|
|
1763
|
+
}
|
|
1764
|
+
declare class UpdateGroupCommand implements Command {
|
|
1765
|
+
readonly label = "update group";
|
|
1766
|
+
private readonly before;
|
|
1767
|
+
private readonly after;
|
|
1768
|
+
constructor(before: GroupObject, after: GroupObject);
|
|
1769
|
+
apply(doc: BoardDocument): void;
|
|
1770
|
+
revert(doc: BoardDocument): void;
|
|
1771
|
+
}
|
|
1772
|
+
declare class DeleteGroupCommand implements Command {
|
|
1773
|
+
readonly label = "ungroup";
|
|
1774
|
+
private readonly group;
|
|
1775
|
+
constructor(group: GroupObject);
|
|
1776
|
+
apply(doc: BoardDocument): void;
|
|
1777
|
+
revert(doc: BoardDocument): void;
|
|
1778
|
+
}
|
|
1779
|
+
type ReorderDirection = "forward" | "backward" | "front" | "back";
|
|
1780
|
+
/**
|
|
1781
|
+
* Bring-forward / send-backward / bring-to-front / send-to-back (Phase 3) —
|
|
1782
|
+
* one command family covering all four directions rather than four
|
|
1783
|
+
* near-identical classes, since the only difference between them is which
|
|
1784
|
+
* `BoardDocument.reorder` direction to replay. Captures the full paint order
|
|
1785
|
+
* on first `apply` rather than in the constructor — `BoardDocument.order()`
|
|
1786
|
+
* needs the doc, which a `Command` only ever receives via `apply`/`revert` —
|
|
1787
|
+
* so `revert` can restore it exactly; redo re-runs the same `reorder` call,
|
|
1788
|
+
* which is deterministic because `revert` always restores the identical
|
|
1789
|
+
* starting order first.
|
|
1790
|
+
*/
|
|
1791
|
+
declare class ReorderObjectCommand implements Command {
|
|
1792
|
+
private readonly id;
|
|
1793
|
+
private readonly direction;
|
|
1794
|
+
readonly label: string;
|
|
1795
|
+
private before;
|
|
1796
|
+
constructor(id: string, direction: ReorderDirection);
|
|
1797
|
+
apply(doc: BoardDocument): void;
|
|
1798
|
+
revert(doc: BoardDocument): void;
|
|
1799
|
+
}
|
|
994
1800
|
interface LockTarget {
|
|
995
|
-
type: "stroke" | "note" | "text" | "table" | "image" | "timer";
|
|
1801
|
+
type: "stroke" | "note" | "text" | "table" | "image" | "timer" | "rectangle" | "ellipse" | "group" | "line" | "arrow" | "polygon" | "star" | "heart";
|
|
996
1802
|
id: string;
|
|
997
1803
|
locked: boolean;
|
|
998
1804
|
lockedBy?: string;
|
|
@@ -1009,7 +1815,7 @@ declare class LockItemsCommand implements Command {
|
|
|
1009
1815
|
private applyLock;
|
|
1010
1816
|
}
|
|
1011
1817
|
|
|
1012
|
-
type OpCollection = "strokes" | "notes" | "textBlocks" | "tables" | "images" | "timers" | "customObjects";
|
|
1818
|
+
type OpCollection = "strokes" | "notes" | "textBlocks" | "tables" | "images" | "timers" | "rectangles" | "ellipses" | "groups" | "lines" | "arrows" | "polygons" | "stars" | "hearts" | "customObjects";
|
|
1013
1819
|
type Op = {
|
|
1014
1820
|
kind: "upsert";
|
|
1015
1821
|
collection: OpCollection;
|
|
@@ -1040,15 +1846,23 @@ interface RibbonEdgePoint {
|
|
|
1040
1846
|
/** 1 = intact ink, 0 = fully erased (from the erasure channel). */
|
|
1041
1847
|
alpha: number;
|
|
1042
1848
|
}
|
|
1043
|
-
|
|
1849
|
+
/**
|
|
1850
|
+
* @param handDrawn Default `true`: ink's organic feel — width follows
|
|
1851
|
+
* per-point pressure, and both ends taper. Pass `false` for a geometric
|
|
1852
|
+
* shape outline, which has no real pressure signal and isn't a pen stroke
|
|
1853
|
+
* with a lift-off: it renders at the exact `baseWidth`, uniformly, with no
|
|
1854
|
+
* taper at its start/end (which, for a closed shape, is the same point —
|
|
1855
|
+
* tapering it would visibly pinch just that one corner).
|
|
1856
|
+
*/
|
|
1857
|
+
declare function ribbonEdges(points: StrokePoint[], baseWidth: number, handDrawn?: boolean): RibbonEdgePoint[];
|
|
1044
1858
|
|
|
1045
1859
|
declare class SpatialIndex {
|
|
1046
1860
|
private readonly doc;
|
|
1047
1861
|
private readonly cells;
|
|
1048
|
-
private readonly
|
|
1862
|
+
private readonly objectCells;
|
|
1049
1863
|
private readonly unsubscribe;
|
|
1050
1864
|
constructor(doc: BoardDocument);
|
|
1051
|
-
/** Ids of
|
|
1865
|
+
/** Ids of content objects (any type except groups) whose bbox may overlap the query rect. */
|
|
1052
1866
|
query(minX: number, minY: number, maxX: number, maxY: number): Set<string>;
|
|
1053
1867
|
dispose(): void;
|
|
1054
1868
|
private insert;
|
|
@@ -1097,6 +1911,539 @@ declare function placePresenceBeacon(screen: {
|
|
|
1097
1911
|
height: number;
|
|
1098
1912
|
}, inset?: ViewportInset): PresencePlacement;
|
|
1099
1913
|
|
|
1914
|
+
interface IconDefinition {
|
|
1915
|
+
/** Stable, set-prefixed id (`lucide:rocket`), also what an ImageBlock stores in its `stamp` field. */
|
|
1916
|
+
readonly name: string;
|
|
1917
|
+
readonly label: string;
|
|
1918
|
+
readonly category: string;
|
|
1919
|
+
/** Inner SVG markup for a 24x24 viewBox; strokes use `currentColor`. */
|
|
1920
|
+
readonly body: string;
|
|
1921
|
+
}
|
|
1922
|
+
declare const ICON_LIBRARY: readonly [{
|
|
1923
|
+
readonly name: "lucide:check";
|
|
1924
|
+
readonly label: "Check";
|
|
1925
|
+
readonly category: "Status";
|
|
1926
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M20 6L9 17l-5-5\"/>";
|
|
1927
|
+
}, {
|
|
1928
|
+
readonly name: "lucide:x";
|
|
1929
|
+
readonly label: "Cross";
|
|
1930
|
+
readonly category: "Status";
|
|
1931
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M18 6L6 18M6 6l12 12\"/>";
|
|
1932
|
+
}, {
|
|
1933
|
+
readonly name: "lucide:star";
|
|
1934
|
+
readonly label: "Star";
|
|
1935
|
+
readonly category: "Status";
|
|
1936
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.12 2.12 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.12 2.12 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.12 2.12 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.12 2.12 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.12 2.12 0 0 0 1.597-1.16z\"/>";
|
|
1937
|
+
}, {
|
|
1938
|
+
readonly name: "lucide:heart";
|
|
1939
|
+
readonly label: "Heart";
|
|
1940
|
+
readonly category: "Status";
|
|
1941
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M2 9.5a5.5 5.5 0 0 1 9.591-3.676a.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5\"/>";
|
|
1942
|
+
}, {
|
|
1943
|
+
readonly name: "lucide:flag";
|
|
1944
|
+
readonly label: "Flag";
|
|
1945
|
+
readonly category: "Status";
|
|
1946
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 22V4a1 1 0 0 1 .4-.8A6 6 0 0 1 8 2c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10a1 1 0 0 1-.4.8A6 6 0 0 1 16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528\"/>";
|
|
1947
|
+
}, {
|
|
1948
|
+
readonly name: "lucide:bookmark";
|
|
1949
|
+
readonly label: "Bookmark";
|
|
1950
|
+
readonly category: "Status";
|
|
1951
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M17 3a2 2 0 0 1 2 2v15a1 1 0 0 1-1.496.868l-4.512-2.578a2 2 0 0 0-1.984 0l-4.512 2.578A1 1 0 0 1 5 20V5a2 2 0 0 1 2-2z\"/>";
|
|
1952
|
+
}, {
|
|
1953
|
+
readonly name: "lucide:bell";
|
|
1954
|
+
readonly label: "Bell";
|
|
1955
|
+
readonly category: "Status";
|
|
1956
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10.268 21a2 2 0 0 0 3.464 0m-10.47-5.674A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326\"/>";
|
|
1957
|
+
}, {
|
|
1958
|
+
readonly name: "lucide:info";
|
|
1959
|
+
readonly label: "Info";
|
|
1960
|
+
readonly category: "Status";
|
|
1961
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4m0-4h.01\"/></g>";
|
|
1962
|
+
}, {
|
|
1963
|
+
readonly name: "lucide:circle-alert";
|
|
1964
|
+
readonly label: "Alert";
|
|
1965
|
+
readonly category: "Status";
|
|
1966
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 8v4m0 4h.01\"/></g>";
|
|
1967
|
+
}, {
|
|
1968
|
+
readonly name: "lucide:circle-question-mark";
|
|
1969
|
+
readonly label: "Question";
|
|
1970
|
+
readonly category: "Status";
|
|
1971
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3m.08 4h.01\"/></g>";
|
|
1972
|
+
}, {
|
|
1973
|
+
readonly name: "lucide:thumbs-up";
|
|
1974
|
+
readonly label: "Thumbs up";
|
|
1975
|
+
readonly category: "Status";
|
|
1976
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 5.88L14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88M7 10v12\"/>";
|
|
1977
|
+
}, {
|
|
1978
|
+
readonly name: "lucide:thumbs-down";
|
|
1979
|
+
readonly label: "Thumbs down";
|
|
1980
|
+
readonly category: "Status";
|
|
1981
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 18.12L10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88M17 14V2\"/>";
|
|
1982
|
+
}, {
|
|
1983
|
+
readonly name: "lucide:arrow-up";
|
|
1984
|
+
readonly label: "Arrow up";
|
|
1985
|
+
readonly category: "Arrows";
|
|
1986
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m5 12l7-7l7 7m-7 7V5\"/>";
|
|
1987
|
+
}, {
|
|
1988
|
+
readonly name: "lucide:arrow-down";
|
|
1989
|
+
readonly label: "Arrow down";
|
|
1990
|
+
readonly category: "Arrows";
|
|
1991
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 5v14m7-7l-7 7l-7-7\"/>";
|
|
1992
|
+
}, {
|
|
1993
|
+
readonly name: "lucide:arrow-left";
|
|
1994
|
+
readonly label: "Arrow left";
|
|
1995
|
+
readonly category: "Arrows";
|
|
1996
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m12 19l-7-7l7-7m7 7H5\"/>";
|
|
1997
|
+
}, {
|
|
1998
|
+
readonly name: "lucide:arrow-right";
|
|
1999
|
+
readonly label: "Arrow right";
|
|
2000
|
+
readonly category: "Arrows";
|
|
2001
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 12h14m-7-7l7 7l-7 7\"/>";
|
|
2002
|
+
}, {
|
|
2003
|
+
readonly name: "lucide:arrow-up-right";
|
|
2004
|
+
readonly label: "Arrow up-right";
|
|
2005
|
+
readonly category: "Arrows";
|
|
2006
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M7 7h10v10M7 17L17 7\"/>";
|
|
2007
|
+
}, {
|
|
2008
|
+
readonly name: "lucide:refresh-cw";
|
|
2009
|
+
readonly label: "Refresh";
|
|
2010
|
+
readonly category: "Arrows";
|
|
2011
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M3 12a9 9 0 0 1 9-9a9.75 9.75 0 0 1 6.74 2.74L21 8\"/><path d=\"M21 3v5h-5m5 4a9 9 0 0 1-9 9a9.75 9.75 0 0 1-6.74-2.74L3 16\"/><path d=\"M8 16H3v5\"/></g>";
|
|
2012
|
+
}, {
|
|
2013
|
+
readonly name: "lucide:move";
|
|
2014
|
+
readonly label: "Move";
|
|
2015
|
+
readonly category: "Arrows";
|
|
2016
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 2v20m3-3l-3 3l-3-3M19 9l3 3l-3 3M2 12h20M5 9l-3 3l3 3M9 5l3-3l3 3\"/>";
|
|
2017
|
+
}, {
|
|
2018
|
+
readonly name: "lucide:shuffle";
|
|
2019
|
+
readonly label: "Shuffle";
|
|
2020
|
+
readonly category: "Arrows";
|
|
2021
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"m18 14l4 4l-4 4m0-20l4 4l-4 4\"/><path d=\"M2 18h1.973a4 4 0 0 0 3.3-1.7l5.454-8.6a4 4 0 0 1 3.3-1.7H22M2 6h1.972a4 4 0 0 1 3.6 2.2M22 18h-6.041a4 4 0 0 1-3.3-1.8l-.359-.45\"/></g>";
|
|
2022
|
+
}, {
|
|
2023
|
+
readonly name: "lucide:user";
|
|
2024
|
+
readonly label: "User";
|
|
2025
|
+
readonly category: "People";
|
|
2026
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/></g>";
|
|
2027
|
+
}, {
|
|
2028
|
+
readonly name: "lucide:users";
|
|
2029
|
+
readonly label: "Users";
|
|
2030
|
+
readonly category: "People";
|
|
2031
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2M16 3.128a4 4 0 0 1 0 7.744M22 21v-2a4 4 0 0 0-3-3.87\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/></g>";
|
|
2032
|
+
}, {
|
|
2033
|
+
readonly name: "lucide:briefcase";
|
|
2034
|
+
readonly label: "Briefcase";
|
|
2035
|
+
readonly category: "People";
|
|
2036
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M16 20V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16\"/><rect width=\"20\" height=\"14\" x=\"2\" y=\"6\" rx=\"2\"/></g>";
|
|
2037
|
+
}, {
|
|
2038
|
+
readonly name: "lucide:building";
|
|
2039
|
+
readonly label: "Building";
|
|
2040
|
+
readonly category: "People";
|
|
2041
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M12 10h.01M12 14h.01M12 6h.01M16 10h.01M16 14h.01M16 6h.01M8 10h.01M8 14h.01M8 6h.01M9 22v-3a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3\"/><rect width=\"16\" height=\"20\" x=\"4\" y=\"2\" rx=\"2\"/></g>";
|
|
2042
|
+
}, {
|
|
2043
|
+
readonly name: "lucide:calendar";
|
|
2044
|
+
readonly label: "Calendar";
|
|
2045
|
+
readonly category: "People";
|
|
2046
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M8 2v3m8-3v3\"/><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/></g>";
|
|
2047
|
+
}, {
|
|
2048
|
+
readonly name: "lucide:clock";
|
|
2049
|
+
readonly label: "Clock";
|
|
2050
|
+
readonly category: "People";
|
|
2051
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 6v6l4 2\"/></g>";
|
|
2052
|
+
}, {
|
|
2053
|
+
readonly name: "lucide:mail";
|
|
2054
|
+
readonly label: "Mail";
|
|
2055
|
+
readonly category: "People";
|
|
2056
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"m22 7l-8.991 5.727a2 2 0 0 1-2.009 0L2 7\"/><rect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\"/></g>";
|
|
2057
|
+
}, {
|
|
2058
|
+
readonly name: "lucide:message-circle";
|
|
2059
|
+
readonly label: "Message";
|
|
2060
|
+
readonly category: "People";
|
|
2061
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092a10 10 0 1 0-4.777-4.719\"/>";
|
|
2062
|
+
}, {
|
|
2063
|
+
readonly name: "lucide:file";
|
|
2064
|
+
readonly label: "File";
|
|
2065
|
+
readonly category: "Objects";
|
|
2066
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z\"/><path d=\"M14 2v5a1 1 0 0 0 1 1h5\"/></g>";
|
|
2067
|
+
}, {
|
|
2068
|
+
readonly name: "lucide:folder";
|
|
2069
|
+
readonly label: "Folder";
|
|
2070
|
+
readonly category: "Objects";
|
|
2071
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\"/>";
|
|
2072
|
+
}, {
|
|
2073
|
+
readonly name: "lucide:image";
|
|
2074
|
+
readonly label: "Image";
|
|
2075
|
+
readonly category: "Objects";
|
|
2076
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15l-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"/></g>";
|
|
2077
|
+
}, {
|
|
2078
|
+
readonly name: "lucide:link";
|
|
2079
|
+
readonly label: "Link";
|
|
2080
|
+
readonly category: "Objects";
|
|
2081
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/></g>";
|
|
2082
|
+
}, {
|
|
2083
|
+
readonly name: "lucide:paperclip";
|
|
2084
|
+
readonly label: "Paperclip";
|
|
2085
|
+
readonly category: "Objects";
|
|
2086
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m16 6l-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551\"/>";
|
|
2087
|
+
}, {
|
|
2088
|
+
readonly name: "lucide:pin";
|
|
2089
|
+
readonly label: "Pin";
|
|
2090
|
+
readonly category: "Objects";
|
|
2091
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 17v5M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4a1 1 0 0 1 1 1z\"/>";
|
|
2092
|
+
}, {
|
|
2093
|
+
readonly name: "lucide:map-pin";
|
|
2094
|
+
readonly label: "Map pin";
|
|
2095
|
+
readonly category: "Objects";
|
|
2096
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0\"/><circle cx=\"12\" cy=\"10\" r=\"3\"/></g>";
|
|
2097
|
+
}, {
|
|
2098
|
+
readonly name: "lucide:search";
|
|
2099
|
+
readonly label: "Search";
|
|
2100
|
+
readonly category: "Objects";
|
|
2101
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"m21 21l-4.34-4.34\"/><circle cx=\"11\" cy=\"11\" r=\"8\"/></g>";
|
|
2102
|
+
}, {
|
|
2103
|
+
readonly name: "lucide:settings";
|
|
2104
|
+
readonly label: "Settings";
|
|
2105
|
+
readonly category: "Objects";
|
|
2106
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M9.671 4.136a2.34 2.34 0 0 1 4.659 0a2.34 2.34 0 0 0 3.319 1.915a2.34 2.34 0 0 1 2.33 4.033a2.34 2.34 0 0 0 0 3.831a2.34 2.34 0 0 1-2.33 4.033a2.34 2.34 0 0 0-3.319 1.915a2.34 2.34 0 0 1-4.659 0a2.34 2.34 0 0 0-3.32-1.915a2.34 2.34 0 0 1-2.33-4.033a2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></g>";
|
|
2107
|
+
}, {
|
|
2108
|
+
readonly name: "lucide:trash-2";
|
|
2109
|
+
readonly label: "Trash";
|
|
2110
|
+
readonly category: "Objects";
|
|
2111
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 11v6m4-6v6m5-11v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>";
|
|
2112
|
+
}, {
|
|
2113
|
+
readonly name: "lucide:lock";
|
|
2114
|
+
readonly label: "Lock";
|
|
2115
|
+
readonly category: "Objects";
|
|
2116
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><rect width=\"18\" height=\"11\" x=\"3\" y=\"11\" rx=\"2\" ry=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"/></g>";
|
|
2117
|
+
}, {
|
|
2118
|
+
readonly name: "lucide:key";
|
|
2119
|
+
readonly label: "Key";
|
|
2120
|
+
readonly category: "Objects";
|
|
2121
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"m15.5 7.5l2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4m2-2l-9.6 9.6\"/><circle cx=\"7.5\" cy=\"15.5\" r=\"5.5\"/></g>";
|
|
2122
|
+
}, {
|
|
2123
|
+
readonly name: "lucide:code";
|
|
2124
|
+
readonly label: "Code";
|
|
2125
|
+
readonly category: "Tech";
|
|
2126
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m16 18l6-6l-6-6M8 6l-6 6l6 6\"/>";
|
|
2127
|
+
}, {
|
|
2128
|
+
readonly name: "lucide:database";
|
|
2129
|
+
readonly label: "Database";
|
|
2130
|
+
readonly category: "Tech";
|
|
2131
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><ellipse cx=\"12\" cy=\"5\" rx=\"9\" ry=\"3\"/><path d=\"M3 5v14a9 3 0 0 0 18 0V5\"/><path d=\"M3 12a9 3 0 0 0 18 0\"/></g>";
|
|
2132
|
+
}, {
|
|
2133
|
+
readonly name: "lucide:server";
|
|
2134
|
+
readonly label: "Server";
|
|
2135
|
+
readonly category: "Tech";
|
|
2136
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><rect width=\"20\" height=\"8\" x=\"2\" y=\"2\" rx=\"2\" ry=\"2\"/><rect width=\"20\" height=\"8\" x=\"2\" y=\"14\" rx=\"2\" ry=\"2\"/><path d=\"M6 6h.01M6 18h.01\"/></g>";
|
|
2137
|
+
}, {
|
|
2138
|
+
readonly name: "lucide:cloud";
|
|
2139
|
+
readonly label: "Cloud";
|
|
2140
|
+
readonly category: "Tech";
|
|
2141
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9\"/>";
|
|
2142
|
+
}, {
|
|
2143
|
+
readonly name: "lucide:git-branch";
|
|
2144
|
+
readonly label: "Branch";
|
|
2145
|
+
readonly category: "Tech";
|
|
2146
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M15 6a9 9 0 0 0-9 9V3\"/><circle cx=\"18\" cy=\"6\" r=\"3\"/><circle cx=\"6\" cy=\"18\" r=\"3\"/></g>";
|
|
2147
|
+
}, {
|
|
2148
|
+
readonly name: "lucide:terminal";
|
|
2149
|
+
readonly label: "Terminal";
|
|
2150
|
+
readonly category: "Tech";
|
|
2151
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 19h8M4 17l6-6l-6-6\"/>";
|
|
2152
|
+
}, {
|
|
2153
|
+
readonly name: "lucide:cpu";
|
|
2154
|
+
readonly label: "CPU";
|
|
2155
|
+
readonly category: "Tech";
|
|
2156
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M12 20v2m0-20v2m5 16v2m0-20v2M2 12h2m-2 5h2M2 7h2m16 5h2m-2 5h2M20 7h2M7 20v2M7 2v2\"/><rect width=\"16\" height=\"16\" x=\"4\" y=\"4\" rx=\"2\"/><rect width=\"8\" height=\"8\" x=\"8\" y=\"8\" rx=\"1\"/></g>";
|
|
2157
|
+
}, {
|
|
2158
|
+
readonly name: "lucide:bug";
|
|
2159
|
+
readonly label: "Bug";
|
|
2160
|
+
readonly category: "Tech";
|
|
2161
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M12 20v-9m2-4a4 4 0 0 1 4 4v3a6 6 0 0 1-12 0v-3a4 4 0 0 1 4-4zm.12-3.12L16 2\"/><path d=\"M21 21a4 4 0 0 0-3.81-4M21 5a4 4 0 0 1-3.55 3.97M22 13h-4M3 21a4 4 0 0 1 3.81-4M3 5a4 4 0 0 0 3.55 3.97M6 13H2M8 2l1.88 1.88M9 7.13V6a3 3 0 1 1 6 0v1.13\"/></g>";
|
|
2162
|
+
}, {
|
|
2163
|
+
readonly name: "lucide:lightbulb";
|
|
2164
|
+
readonly label: "Idea";
|
|
2165
|
+
readonly category: "Ideas";
|
|
2166
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 14c.2-1 .7-1.7 1.5-2.5c1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5c.7.7 1.3 1.5 1.5 2.5m0 4h6m-5 4h4\"/>";
|
|
2167
|
+
}, {
|
|
2168
|
+
readonly name: "lucide:zap";
|
|
2169
|
+
readonly label: "Zap";
|
|
2170
|
+
readonly category: "Ideas";
|
|
2171
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15.914 4a1.5 1.5 0 0 0-2.474-1.561l-9 9A1.5 1.5 0 0 0 5.5 14h4.002a.5.5 0 0 1 .471.666L8.086 20a1.5 1.5 0 0 0 2.475 1.56l9-9A1.5 1.5 0 0 0 18.5 10h-3.997a.5.5 0 0 1-.472-.667z\"/>";
|
|
2172
|
+
}, {
|
|
2173
|
+
readonly name: "lucide:target";
|
|
2174
|
+
readonly label: "Target";
|
|
2175
|
+
readonly category: "Ideas";
|
|
2176
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></g>";
|
|
2177
|
+
}, {
|
|
2178
|
+
readonly name: "lucide:rocket";
|
|
2179
|
+
readonly label: "Rocket";
|
|
2180
|
+
readonly category: "Ideas";
|
|
2181
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09\"/><path d=\"M9 12a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.4 22.4 0 0 1-4 2z\"/><path d=\"M9 12H4s.55-3.03 2-4c1.62-1.08 5 .05 5 .05\"/></g>";
|
|
2182
|
+
}, {
|
|
2183
|
+
readonly name: "lucide:trophy";
|
|
2184
|
+
readonly label: "Trophy";
|
|
2185
|
+
readonly category: "Ideas";
|
|
2186
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M10 14.66V17a1 1 0 0 1-1 1a2 2 0 0 0-2 2v2m7-7.34V17a1 1 0 0 0 1 1a2 2 0 0 1 2 2v2m.916-12H19.5A2.5 2.5 0 0 0 22 7.5V5a1 1 0 0 0-1-1h-3M4 22h16\"/><path d=\"M6 9a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1z\"/><path d=\"M6.084 10H4.5A2.5 2.5 0 0 1 2 7.5V5a1 1 0 0 1 1-1h3\"/></g>";
|
|
2187
|
+
}, {
|
|
2188
|
+
readonly name: "lucide:gift";
|
|
2189
|
+
readonly label: "Gift";
|
|
2190
|
+
readonly category: "Ideas";
|
|
2191
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M12 7v14m8-10v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8m3.5-4a1 1 0 0 1 0-5A4.8 8 0 0 1 12 7a4.8 8 0 0 1 4.5-5a1 1 0 0 1 0 5\"/><rect width=\"18\" height=\"4\" x=\"3\" y=\"7\" rx=\"1\"/></g>";
|
|
2192
|
+
}, {
|
|
2193
|
+
readonly name: "lucide:coffee";
|
|
2194
|
+
readonly label: "Coffee";
|
|
2195
|
+
readonly category: "Ideas";
|
|
2196
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 2v2m4-2v2m2 4a1 1 0 0 1 1 1v8a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1h14a4 4 0 1 1 0 8h-1M6 2v2\"/>";
|
|
2197
|
+
}, {
|
|
2198
|
+
readonly name: "lucide:smile";
|
|
2199
|
+
readonly label: "Smile";
|
|
2200
|
+
readonly category: "Ideas";
|
|
2201
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M8 14s1.5 2 4 2s4-2 4-2M9 9h.01M15 9h.01\"/></g>";
|
|
2202
|
+
}];
|
|
2203
|
+
/** Every vendored icon's id, as a literal union. */
|
|
2204
|
+
type IconName = (typeof ICON_LIBRARY)[number]["name"];
|
|
2205
|
+
|
|
2206
|
+
/**
|
|
2207
|
+
* Emoji stickers. Unlike the icon library there's no artwork to vendor: each
|
|
2208
|
+
* is a Unicode character, drawn by the platform's own colour-emoji font when
|
|
2209
|
+
* its SVG is rasterised — so an emoji looks native to each viewer's OS
|
|
2210
|
+
* (Apple, Segoe, Noto), the same way it would in a chat app.
|
|
2211
|
+
*/
|
|
2212
|
+
interface EmojiDefinition {
|
|
2213
|
+
/** Stable, set-prefixed id (`emoji:fire`), also what an ImageBlock stores in its `stamp` field. */
|
|
2214
|
+
readonly name: string;
|
|
2215
|
+
readonly label: string;
|
|
2216
|
+
readonly category: string;
|
|
2217
|
+
readonly char: string;
|
|
2218
|
+
}
|
|
2219
|
+
declare const EMOJI_LIBRARY: readonly [{
|
|
2220
|
+
readonly name: "emoji:grinning";
|
|
2221
|
+
readonly label: "Grinning face";
|
|
2222
|
+
readonly category: "Smileys";
|
|
2223
|
+
readonly char: "😀";
|
|
2224
|
+
}, {
|
|
2225
|
+
readonly name: "emoji:joy";
|
|
2226
|
+
readonly label: "Tears of joy";
|
|
2227
|
+
readonly category: "Smileys";
|
|
2228
|
+
readonly char: "😂";
|
|
2229
|
+
}, {
|
|
2230
|
+
readonly name: "emoji:smiling";
|
|
2231
|
+
readonly label: "Smiling face";
|
|
2232
|
+
readonly category: "Smileys";
|
|
2233
|
+
readonly char: "😊";
|
|
2234
|
+
}, {
|
|
2235
|
+
readonly name: "emoji:heart-eyes";
|
|
2236
|
+
readonly label: "Heart eyes";
|
|
2237
|
+
readonly category: "Smileys";
|
|
2238
|
+
readonly char: "😍";
|
|
2239
|
+
}, {
|
|
2240
|
+
readonly name: "emoji:thinking";
|
|
2241
|
+
readonly label: "Thinking face";
|
|
2242
|
+
readonly category: "Smileys";
|
|
2243
|
+
readonly char: "🤔";
|
|
2244
|
+
}, {
|
|
2245
|
+
readonly name: "emoji:sunglasses";
|
|
2246
|
+
readonly label: "Sunglasses";
|
|
2247
|
+
readonly category: "Smileys";
|
|
2248
|
+
readonly char: "😎";
|
|
2249
|
+
}, {
|
|
2250
|
+
readonly name: "emoji:crying";
|
|
2251
|
+
readonly label: "Crying face";
|
|
2252
|
+
readonly category: "Smileys";
|
|
2253
|
+
readonly char: "😢";
|
|
2254
|
+
}, {
|
|
2255
|
+
readonly name: "emoji:angry";
|
|
2256
|
+
readonly label: "Angry face";
|
|
2257
|
+
readonly category: "Smileys";
|
|
2258
|
+
readonly char: "😡";
|
|
2259
|
+
}, {
|
|
2260
|
+
readonly name: "emoji:sleeping";
|
|
2261
|
+
readonly label: "Sleeping face";
|
|
2262
|
+
readonly category: "Smileys";
|
|
2263
|
+
readonly char: "😴";
|
|
2264
|
+
}, {
|
|
2265
|
+
readonly name: "emoji:mind-blown";
|
|
2266
|
+
readonly label: "Mind blown";
|
|
2267
|
+
readonly category: "Smileys";
|
|
2268
|
+
readonly char: "🤯";
|
|
2269
|
+
}, {
|
|
2270
|
+
readonly name: "emoji:grimacing";
|
|
2271
|
+
readonly label: "Grimacing face";
|
|
2272
|
+
readonly category: "Smileys";
|
|
2273
|
+
readonly char: "😬";
|
|
2274
|
+
}, {
|
|
2275
|
+
readonly name: "emoji:partying";
|
|
2276
|
+
readonly label: "Partying face";
|
|
2277
|
+
readonly category: "Smileys";
|
|
2278
|
+
readonly char: "🥳";
|
|
2279
|
+
}, {
|
|
2280
|
+
readonly name: "emoji:thumbs-up";
|
|
2281
|
+
readonly label: "Thumbs up emoji";
|
|
2282
|
+
readonly category: "Gestures";
|
|
2283
|
+
readonly char: "👍";
|
|
2284
|
+
}, {
|
|
2285
|
+
readonly name: "emoji:thumbs-down";
|
|
2286
|
+
readonly label: "Thumbs down emoji";
|
|
2287
|
+
readonly category: "Gestures";
|
|
2288
|
+
readonly char: "👎";
|
|
2289
|
+
}, {
|
|
2290
|
+
readonly name: "emoji:clap";
|
|
2291
|
+
readonly label: "Clapping hands";
|
|
2292
|
+
readonly category: "Gestures";
|
|
2293
|
+
readonly char: "👏";
|
|
2294
|
+
}, {
|
|
2295
|
+
readonly name: "emoji:raised-hands";
|
|
2296
|
+
readonly label: "Raised hands";
|
|
2297
|
+
readonly category: "Gestures";
|
|
2298
|
+
readonly char: "🙌";
|
|
2299
|
+
}, {
|
|
2300
|
+
readonly name: "emoji:folded-hands";
|
|
2301
|
+
readonly label: "Folded hands";
|
|
2302
|
+
readonly category: "Gestures";
|
|
2303
|
+
readonly char: "🙏";
|
|
2304
|
+
}, {
|
|
2305
|
+
readonly name: "emoji:eyes";
|
|
2306
|
+
readonly label: "Eyes";
|
|
2307
|
+
readonly category: "Gestures";
|
|
2308
|
+
readonly char: "👀";
|
|
2309
|
+
}, {
|
|
2310
|
+
readonly name: "emoji:muscle";
|
|
2311
|
+
readonly label: "Flexed biceps";
|
|
2312
|
+
readonly category: "Gestures";
|
|
2313
|
+
readonly char: "💪";
|
|
2314
|
+
}, {
|
|
2315
|
+
readonly name: "emoji:wave";
|
|
2316
|
+
readonly label: "Waving hand";
|
|
2317
|
+
readonly category: "Gestures";
|
|
2318
|
+
readonly char: "👋";
|
|
2319
|
+
}, {
|
|
2320
|
+
readonly name: "emoji:red-heart";
|
|
2321
|
+
readonly label: "Red heart";
|
|
2322
|
+
readonly category: "Symbols";
|
|
2323
|
+
readonly char: "❤️";
|
|
2324
|
+
}, {
|
|
2325
|
+
readonly name: "emoji:fire";
|
|
2326
|
+
readonly label: "Flame";
|
|
2327
|
+
readonly category: "Symbols";
|
|
2328
|
+
readonly char: "🔥";
|
|
2329
|
+
}, {
|
|
2330
|
+
readonly name: "emoji:star";
|
|
2331
|
+
readonly label: "Medium star";
|
|
2332
|
+
readonly category: "Symbols";
|
|
2333
|
+
readonly char: "⭐";
|
|
2334
|
+
}, {
|
|
2335
|
+
readonly name: "emoji:check";
|
|
2336
|
+
readonly label: "Check mark button";
|
|
2337
|
+
readonly category: "Symbols";
|
|
2338
|
+
readonly char: "✅";
|
|
2339
|
+
}, {
|
|
2340
|
+
readonly name: "emoji:cross";
|
|
2341
|
+
readonly label: "Cross mark";
|
|
2342
|
+
readonly category: "Symbols";
|
|
2343
|
+
readonly char: "❌";
|
|
2344
|
+
}, {
|
|
2345
|
+
readonly name: "emoji:warning";
|
|
2346
|
+
readonly label: "Warning sign";
|
|
2347
|
+
readonly category: "Symbols";
|
|
2348
|
+
readonly char: "⚠️";
|
|
2349
|
+
}, {
|
|
2350
|
+
readonly name: "emoji:question";
|
|
2351
|
+
readonly label: "Red question mark";
|
|
2352
|
+
readonly category: "Symbols";
|
|
2353
|
+
readonly char: "❓";
|
|
2354
|
+
}, {
|
|
2355
|
+
readonly name: "emoji:hundred";
|
|
2356
|
+
readonly label: "Hundred points";
|
|
2357
|
+
readonly category: "Symbols";
|
|
2358
|
+
readonly char: "💯";
|
|
2359
|
+
}, {
|
|
2360
|
+
readonly name: "emoji:light-bulb";
|
|
2361
|
+
readonly label: "Light bulb";
|
|
2362
|
+
readonly category: "Symbols";
|
|
2363
|
+
readonly char: "💡";
|
|
2364
|
+
}, {
|
|
2365
|
+
readonly name: "emoji:bullseye";
|
|
2366
|
+
readonly label: "Bullseye";
|
|
2367
|
+
readonly category: "Symbols";
|
|
2368
|
+
readonly char: "🎯";
|
|
2369
|
+
}, {
|
|
2370
|
+
readonly name: "emoji:party-popper";
|
|
2371
|
+
readonly label: "Party popper";
|
|
2372
|
+
readonly category: "Objects";
|
|
2373
|
+
readonly char: "🎉";
|
|
2374
|
+
}, {
|
|
2375
|
+
readonly name: "emoji:rocket";
|
|
2376
|
+
readonly label: "Rocket emoji";
|
|
2377
|
+
readonly category: "Objects";
|
|
2378
|
+
readonly char: "🚀";
|
|
2379
|
+
}, {
|
|
2380
|
+
readonly name: "emoji:trophy";
|
|
2381
|
+
readonly label: "Trophy emoji";
|
|
2382
|
+
readonly category: "Objects";
|
|
2383
|
+
readonly char: "🏆";
|
|
2384
|
+
}, {
|
|
2385
|
+
readonly name: "emoji:pushpin";
|
|
2386
|
+
readonly label: "Pushpin";
|
|
2387
|
+
readonly category: "Objects";
|
|
2388
|
+
readonly char: "📌";
|
|
2389
|
+
}, {
|
|
2390
|
+
readonly name: "emoji:memo";
|
|
2391
|
+
readonly label: "Memo";
|
|
2392
|
+
readonly category: "Objects";
|
|
2393
|
+
readonly char: "📝";
|
|
2394
|
+
}, {
|
|
2395
|
+
readonly name: "emoji:calendar";
|
|
2396
|
+
readonly label: "Tear-off calendar";
|
|
2397
|
+
readonly category: "Objects";
|
|
2398
|
+
readonly char: "📅";
|
|
2399
|
+
}, {
|
|
2400
|
+
readonly name: "emoji:alarm-clock";
|
|
2401
|
+
readonly label: "Alarm clock";
|
|
2402
|
+
readonly category: "Objects";
|
|
2403
|
+
readonly char: "⏰";
|
|
2404
|
+
}, {
|
|
2405
|
+
readonly name: "emoji:speech-balloon";
|
|
2406
|
+
readonly label: "Speech balloon";
|
|
2407
|
+
readonly category: "Objects";
|
|
2408
|
+
readonly char: "💬";
|
|
2409
|
+
}];
|
|
2410
|
+
type EmojiName = (typeof EMOJI_LIBRARY)[number]["name"];
|
|
2411
|
+
declare function isEmojiName(value: unknown): value is EmojiName;
|
|
2412
|
+
declare function emojiDefinition(name: string): EmojiDefinition | undefined;
|
|
2413
|
+
/** An emoji as a standalone SVG data URL, sized to rasterise sharply (see icons.ts). */
|
|
2414
|
+
declare function emojiDataUrl(name: string): string;
|
|
2415
|
+
|
|
2416
|
+
/**
|
|
2417
|
+
* What the stamp tool will drop next: one of the six built-in stamp-pad
|
|
2418
|
+
* stickers, an emoji, or any vendored icon. All land on the board the same way
|
|
2419
|
+
* — as an image whose `stamp` field records which one it was (see `placeStamp`).
|
|
2420
|
+
*/
|
|
2421
|
+
type StickerKind = StampKind | EmojiName | IconName;
|
|
2422
|
+
declare function isIconName(value: unknown): value is IconName;
|
|
2423
|
+
declare function iconDefinition(name: string): IconDefinition | undefined;
|
|
2424
|
+
/** The vendored icons grouped for a picker, in the order they were vendored. */
|
|
2425
|
+
declare function iconCategories(): {
|
|
2426
|
+
category: string;
|
|
2427
|
+
icons: readonly IconDefinition[];
|
|
2428
|
+
}[];
|
|
2429
|
+
/**
|
|
2430
|
+
* An icon as a standalone SVG data URL, drawn in `color`.
|
|
2431
|
+
*
|
|
2432
|
+
* The library's art strokes in `currentColor`, which has no meaning once the
|
|
2433
|
+
* markup is detached into a data URL — an `<img>`/texture has no inherited
|
|
2434
|
+
* colour to resolve it against — so the colour is substituted in rather than
|
|
2435
|
+
* inherited.
|
|
2436
|
+
*/
|
|
2437
|
+
declare function iconDataUrl(name: string, color: string): string;
|
|
2438
|
+
declare function isStickerKind(value: unknown): value is StickerKind;
|
|
2439
|
+
/** Human-readable name for any sticker kind — what a placed sticker is called and searched by. */
|
|
2440
|
+
declare function stickerLabel(kind: StickerKind): string;
|
|
2441
|
+
/**
|
|
2442
|
+
* The image `src` for whichever sticker kind is selected. Stamp-pad stamps
|
|
2443
|
+
* and emoji carry their own colours, so only icons take the Host's colour.
|
|
2444
|
+
*/
|
|
2445
|
+
declare function stickerDataUrl(kind: StickerKind, color: string): string;
|
|
2446
|
+
|
|
1100
2447
|
interface ExtensionRegistry {
|
|
1101
2448
|
readonly extensions: readonly ScrawlExtension[];
|
|
1102
2449
|
tool(id: ToolId): CustomToolDefinition | undefined;
|
|
@@ -1109,6 +2456,11 @@ interface ExtensionRegistry {
|
|
|
1109
2456
|
* isn't in it), Custom objects still export via the standard fallback
|
|
1110
2457
|
* placeholder (`fallback.bounds`/`label`), never silently dropped.
|
|
1111
2458
|
*
|
|
2459
|
+
* `backgroundColor` should be the Host's actual resolved `boardTheme.surface`
|
|
2460
|
+
* so an export matches what was on screen; defaults to the theme system's
|
|
2461
|
+
* own light-preset default when the caller doesn't have one on hand. The
|
|
2462
|
+
* board's reference grid, if any, is a screen-only aid and never exported.
|
|
2463
|
+
*
|
|
1112
2464
|
* `resolvedAssets` (ticket #23) maps an Asset reference to an already-
|
|
1113
2465
|
* resolved `data:` URI — see `assetExport.ts`'s `exportDocumentSVGWithAssets`,
|
|
1114
2466
|
* which is the only intended caller that ever passes one. Without it, every
|
|
@@ -1116,27 +2468,11 @@ interface ExtensionRegistry {
|
|
|
1116
2468
|
* placeholder instead of guessing at a URL; a legacy `src`-backed image is
|
|
1117
2469
|
* unaffected either way.
|
|
1118
2470
|
*/
|
|
1119
|
-
declare function documentToSVG(doc: SerializedDocument, now?: number, registry?: ExtensionRegistry, resolvedAssets?: ReadonlyMap<AssetRef, string>): string;
|
|
2471
|
+
declare function documentToSVG(doc: SerializedDocument, now?: number, registry?: ExtensionRegistry, backgroundColor?: string, resolvedAssets?: ReadonlyMap<AssetRef, string>): string;
|
|
1120
2472
|
|
|
1121
2473
|
declare const SDK_PACKAGE_NAME = "@scrawl-board/board";
|
|
1122
2474
|
declare const SDK_DEVELOPMENT_VERSION = "0.0.0-development";
|
|
1123
2475
|
|
|
1124
|
-
type BoardBounds = {
|
|
1125
|
-
minX: number;
|
|
1126
|
-
minY: number;
|
|
1127
|
-
maxX: number;
|
|
1128
|
-
maxY: number;
|
|
1129
|
-
};
|
|
1130
|
-
type ScenePath = {
|
|
1131
|
-
id: string;
|
|
1132
|
-
color: string;
|
|
1133
|
-
width: number;
|
|
1134
|
-
points: readonly BoardPoint[];
|
|
1135
|
-
};
|
|
1136
|
-
type BoardScene = {
|
|
1137
|
-
bounds: BoardBounds;
|
|
1138
|
-
paths: readonly ScenePath[];
|
|
1139
|
-
};
|
|
1140
2476
|
type BoardStroke = Stroke;
|
|
1141
2477
|
type SerializedBoardStroke = SerializedStroke;
|
|
1142
2478
|
type SerializedBoardDocument = CurrentSerializedDocument;
|
|
@@ -1179,16 +2515,30 @@ interface BoardStyle {
|
|
|
1179
2515
|
readonly noteColor: string;
|
|
1180
2516
|
readonly tableRows: number;
|
|
1181
2517
|
readonly tableCols: number;
|
|
1182
|
-
|
|
2518
|
+
/** A stamp-pad kind or any vendored icon name (see `ICON_LIBRARY`). */
|
|
2519
|
+
readonly stampKind: StickerKind;
|
|
1183
2520
|
readonly timerDurationMs: number;
|
|
1184
2521
|
}
|
|
2522
|
+
/** A page listed in the Board navigator. `default` is the original first page. */
|
|
2523
|
+
interface BoardPage {
|
|
2524
|
+
readonly id: string;
|
|
2525
|
+
readonly name: string;
|
|
2526
|
+
readonly sectionId: string;
|
|
2527
|
+
}
|
|
1185
2528
|
interface BoardSnapshot {
|
|
2529
|
+
readonly notebooks: readonly BoardNotebook[];
|
|
2530
|
+
readonly sections: readonly BoardSection[];
|
|
2531
|
+
readonly activeNotebookId: string;
|
|
2532
|
+
readonly activeSectionId: string;
|
|
2533
|
+
readonly pages: readonly BoardPage[];
|
|
2534
|
+
readonly activePageId: string;
|
|
1186
2535
|
readonly status: "loading" | "ready" | "disposed";
|
|
1187
2536
|
readonly documentId: string;
|
|
1188
2537
|
readonly tool: BuiltInTool | (string & {});
|
|
1189
2538
|
readonly zoom: number;
|
|
1190
2539
|
readonly readOnly: boolean;
|
|
1191
2540
|
readonly selection: readonly string[];
|
|
2541
|
+
readonly focusedItem: FocusedItem | null;
|
|
1192
2542
|
readonly strokeCount: number;
|
|
1193
2543
|
readonly objectCount: number;
|
|
1194
2544
|
readonly canUndo: boolean;
|
|
@@ -1199,6 +2549,27 @@ interface BoardSnapshot {
|
|
|
1199
2549
|
readonly collaboration: CollaborationSnapshot;
|
|
1200
2550
|
};
|
|
1201
2551
|
}
|
|
2552
|
+
/**
|
|
2553
|
+
* `selection` collapsed into the one thing a selected-object toolbar needs:
|
|
2554
|
+
* what's selected, its lock state, and where to anchor above it. `null`
|
|
2555
|
+
* when nothing is selected, or when the selection mixes types/objects that
|
|
2556
|
+
* don't resolve to a single focus (anything but one object, or several
|
|
2557
|
+
* strokes sharing a `clusterId` — a multi-stroke shape).
|
|
2558
|
+
*
|
|
2559
|
+
* Raw lock fields, not a derived `canUnlock` — this API has no notion of
|
|
2560
|
+
* "the local user" to judge that against (see `canUnlockItem` in `../core`,
|
|
2561
|
+
* which takes a `userId` the Host already owns). A Custom object's lock
|
|
2562
|
+
* shape doesn't carry a holder name, so it always reports `locked: false`
|
|
2563
|
+
* here, matching the engine's own internal selection-badge behavior.
|
|
2564
|
+
*/
|
|
2565
|
+
interface FocusedItem {
|
|
2566
|
+
readonly type: "stroke" | "note" | "text" | "table" | "image" | "timer" | "rectangle" | "ellipse" | "group" | "line" | "arrow" | "polygon" | "star" | "heart" | "custom";
|
|
2567
|
+
readonly id: string;
|
|
2568
|
+
readonly locked: boolean;
|
|
2569
|
+
readonly lockedBy?: string;
|
|
2570
|
+
readonly lockedByName?: string;
|
|
2571
|
+
readonly screenPosition?: ScreenPoint;
|
|
2572
|
+
}
|
|
1202
2573
|
interface BoardControllerError {
|
|
1203
2574
|
source: "controller" | "renderer" | "persistence" | "collaboration";
|
|
1204
2575
|
code: string;
|
|
@@ -1207,6 +2578,9 @@ interface BoardControllerError {
|
|
|
1207
2578
|
}
|
|
1208
2579
|
interface BoardEventMap {
|
|
1209
2580
|
change: BoardSnapshot;
|
|
2581
|
+
"page-changing": {
|
|
2582
|
+
pageId: string;
|
|
2583
|
+
};
|
|
1210
2584
|
"tool-change": {
|
|
1211
2585
|
tool: string;
|
|
1212
2586
|
};
|
|
@@ -1273,6 +2647,8 @@ interface BoardEventMap {
|
|
|
1273
2647
|
"asset-diagnostic": AssetDiagnostic;
|
|
1274
2648
|
/** A batch of Ops was reconciled (not applied as-sent) by the persistence adapter (ticket #24). */
|
|
1275
2649
|
"persistence-diagnostic": PersistenceDiagnostic;
|
|
2650
|
+
/** The collaboration server has confirmed receipt of these op ids (Phase 7) — observability only; the Document was already correct via optimistic local apply before this ever fires. */
|
|
2651
|
+
"collaboration-ops-acknowledged": CollaborationAckDiagnostic;
|
|
1276
2652
|
audit: unknown;
|
|
1277
2653
|
error: BoardControllerError;
|
|
1278
2654
|
disposed: undefined;
|
|
@@ -1292,6 +2668,27 @@ interface BoardView {
|
|
|
1292
2668
|
y: number;
|
|
1293
2669
|
zoom: number;
|
|
1294
2670
|
}
|
|
2671
|
+
/**
|
|
2672
|
+
* The rendered board surface's color, reference grid, and shape stroke
|
|
2673
|
+
* width — the subset of {@link ScrawlTheme} that reaches the rendering
|
|
2674
|
+
* engine directly (everything else is UI-chrome-only, applied as CSS). Any
|
|
2675
|
+
* field left unset keeps its current value.
|
|
2676
|
+
*/
|
|
2677
|
+
interface BoardThemeOptions {
|
|
2678
|
+
/** The board/canvas background color — distinct from UI chrome panels. */
|
|
2679
|
+
surface?: string;
|
|
2680
|
+
/** `"flat"` (default): a plain, uniform board surface. `"textured"`: a subtle "melamine" micro-noise + satin sheen, like a physical whiteboard. */
|
|
2681
|
+
surfaceTexture?: "flat" | "textured";
|
|
2682
|
+
/** `"none"` (default) keeps the board a plain surface; `"line"`/`"dot"` draw a zoom-adaptive reference grid. */
|
|
2683
|
+
gridMode?: "none" | "line" | "dot";
|
|
2684
|
+
gridColor?: string;
|
|
2685
|
+
/** Grid spacing in board units at 100% zoom. Ignored when `gridMode` is `"none"`. */
|
|
2686
|
+
gridSpacing?: number;
|
|
2687
|
+
/** On-screen grid line/dot width in CSS pixels — stays this width at any zoom, since the grid is a reference aid, not content. */
|
|
2688
|
+
gridLineWidth?: number;
|
|
2689
|
+
/** Shape (rectangle, ellipse, arrow, ...) border width, in board units — scales with zoom like ink, since it's part of the drawn content. */
|
|
2690
|
+
shapeStrokeWidth?: number;
|
|
2691
|
+
}
|
|
1295
2692
|
/**
|
|
1296
2693
|
* A Host-owned comment, summarized for Board-side search and marker
|
|
1297
2694
|
* rendering. Comments are not Document content — they carry no undo
|
|
@@ -1315,9 +2712,12 @@ interface PresenceView {
|
|
|
1315
2712
|
readonly height: number;
|
|
1316
2713
|
}
|
|
1317
2714
|
/**
|
|
1318
|
-
* A
|
|
1319
|
-
*
|
|
1320
|
-
*
|
|
2715
|
+
* A collaborator, synced in for cursor/roster rendering only. Presence is
|
|
2716
|
+
* ephemeral — it never touches the Document, Ops, undo/redo, or persistence
|
|
2717
|
+
* (ADR 0006/0007). Two ways a roster gets populated (`presence.sync`
|
|
2718
|
+
* directly, or a `CollaborationAdapter`'s optional presence channel —
|
|
2719
|
+
* Phase 6, ADR 0015) both feed the exact same read/query capability below;
|
|
2720
|
+
* a Host picks one, not both, for a given controller.
|
|
1321
2721
|
*/
|
|
1322
2722
|
interface PresenceUser {
|
|
1323
2723
|
readonly id: string;
|
|
@@ -1326,6 +2726,14 @@ interface PresenceUser {
|
|
|
1326
2726
|
readonly tool?: string;
|
|
1327
2727
|
readonly cursor?: PresenceCursor;
|
|
1328
2728
|
readonly view?: PresenceView;
|
|
2729
|
+
/** Host-supplied extras (avatar URL, role, etc.) — opaque to Scrawl, never interpreted. */
|
|
2730
|
+
readonly metadata?: Record<string, unknown>;
|
|
2731
|
+
}
|
|
2732
|
+
/** This client's own local presence, published via `presence.broadcast()` (Phase 6). */
|
|
2733
|
+
interface LocalPresence {
|
|
2734
|
+
readonly cursor?: PresenceCursor | null;
|
|
2735
|
+
readonly view?: PresenceView | null;
|
|
2736
|
+
readonly tool?: string;
|
|
1329
2737
|
}
|
|
1330
2738
|
/**
|
|
1331
2739
|
* The Custom arm wraps `CustomBoardObject` under the same `type` discriminant
|
|
@@ -1353,6 +2761,22 @@ type BoardObject = ({
|
|
|
1353
2761
|
} & ImageBlock) | ({
|
|
1354
2762
|
type: "timer";
|
|
1355
2763
|
} & KitchenTimer) | ({
|
|
2764
|
+
type: "rectangle";
|
|
2765
|
+
} & RectangleObject) | ({
|
|
2766
|
+
type: "ellipse";
|
|
2767
|
+
} & EllipseObject) | ({
|
|
2768
|
+
type: "group";
|
|
2769
|
+
} & GroupObject) | ({
|
|
2770
|
+
type: "line";
|
|
2771
|
+
} & LineObject) | ({
|
|
2772
|
+
type: "arrow";
|
|
2773
|
+
} & ArrowObject) | ({
|
|
2774
|
+
type: "polygon";
|
|
2775
|
+
} & PolygonObject) | ({
|
|
2776
|
+
type: "star";
|
|
2777
|
+
} & StarObject) | ({
|
|
2778
|
+
type: "heart";
|
|
2779
|
+
} & HeartObject) | ({
|
|
1356
2780
|
type: "custom";
|
|
1357
2781
|
customType: ObjectType;
|
|
1358
2782
|
} & Omit<CustomBoardObject, "type">);
|
|
@@ -1381,6 +2805,30 @@ type BoardObjectInput = {
|
|
|
1381
2805
|
type: "timer";
|
|
1382
2806
|
id?: string;
|
|
1383
2807
|
} & Omit<KitchenTimer, "id">) | ({
|
|
2808
|
+
type: "rectangle";
|
|
2809
|
+
id?: string;
|
|
2810
|
+
} & Omit<RectangleObject, "id">) | ({
|
|
2811
|
+
type: "ellipse";
|
|
2812
|
+
id?: string;
|
|
2813
|
+
} & Omit<EllipseObject, "id">) | ({
|
|
2814
|
+
type: "group";
|
|
2815
|
+
id?: string;
|
|
2816
|
+
} & Omit<GroupObject, "id">) | ({
|
|
2817
|
+
type: "line";
|
|
2818
|
+
id?: string;
|
|
2819
|
+
} & Omit<LineObject, "id">) | ({
|
|
2820
|
+
type: "arrow";
|
|
2821
|
+
id?: string;
|
|
2822
|
+
} & Omit<ArrowObject, "id">) | ({
|
|
2823
|
+
type: "polygon";
|
|
2824
|
+
id?: string;
|
|
2825
|
+
} & Omit<PolygonObject, "id">) | ({
|
|
2826
|
+
type: "star";
|
|
2827
|
+
id?: string;
|
|
2828
|
+
} & Omit<StarObject, "id">) | ({
|
|
2829
|
+
type: "heart";
|
|
2830
|
+
id?: string;
|
|
2831
|
+
} & Omit<HeartObject, "id">) | ({
|
|
1384
2832
|
type: "custom";
|
|
1385
2833
|
id?: string;
|
|
1386
2834
|
customType: ObjectType;
|
|
@@ -1411,10 +2859,34 @@ type LoadResult = {
|
|
|
1411
2859
|
} | {
|
|
1412
2860
|
state: "missing";
|
|
1413
2861
|
};
|
|
2862
|
+
/**
|
|
2863
|
+
* Result of a whole-document `PersistenceAdapter.replace()` call (ADR 0006:
|
|
2864
|
+
* "Whole-document writes survive only for create, clear-board and import,
|
|
2865
|
+
* where replacing everything is the actual intent"). Revision-gated, unlike
|
|
2866
|
+
* `applyOps` — `conflict` means `baseRevision` was stale (someone else's
|
|
2867
|
+
* write landed first); the caller must reload and never overwrites blind.
|
|
2868
|
+
*/
|
|
2869
|
+
type ReplaceResult = {
|
|
2870
|
+
state: "applied";
|
|
2871
|
+
revision: string;
|
|
2872
|
+
} | {
|
|
2873
|
+
state: "conflict";
|
|
2874
|
+
currentRevision: string;
|
|
2875
|
+
};
|
|
2876
|
+
/**
|
|
2877
|
+
* The one sanctioned seam for persisting a Board's Document to a Host's own
|
|
2878
|
+
* storage — implement this against a database, an HTTP API, IndexedDB
|
|
2879
|
+
* (see `@scrawl-board/board/local`'s `createIndexedDBPersistence`), or
|
|
2880
|
+
* anything else. `load()` fetches the current state on connect; `applyOps()`
|
|
2881
|
+
* streams incremental Ops as edits happen; `replace()` is only for
|
|
2882
|
+
* whole-document writes (create, clear-board, import — see ADR 0006) and is
|
|
2883
|
+
* revision-gated so a stale write never silently clobbers a newer one.
|
|
2884
|
+
* Passed via `createBoardController({ adapters: { persistence } })`.
|
|
2885
|
+
*/
|
|
1414
2886
|
interface PersistenceAdapter {
|
|
1415
2887
|
load(context: DocumentContext): Promise<LoadResult>;
|
|
1416
2888
|
applyOps(context: DocumentContext, ops: readonly ControllerOp[]): Promise<ApplyOpsResult>;
|
|
1417
|
-
replace(context: DocumentContext, document: CurrentSerializedDocument, baseRevision: string): Promise<
|
|
2889
|
+
replace(context: DocumentContext, document: CurrentSerializedDocument, baseRevision: string): Promise<ReplaceResult>;
|
|
1418
2890
|
}
|
|
1419
2891
|
/**
|
|
1420
2892
|
* `"reconcile"` (ticket #24) means the server authoritatively resolved the
|
|
@@ -1438,14 +2910,61 @@ interface PersistenceDiagnostic {
|
|
|
1438
2910
|
rejectedOpIds: readonly string[];
|
|
1439
2911
|
revision: string;
|
|
1440
2912
|
}
|
|
2913
|
+
/** Emitted as `"collaboration-ops-acknowledged"` (Phase 7) — the server has confirmed receipt of these op ids on the live pipe. */
|
|
2914
|
+
interface CollaborationAckDiagnostic {
|
|
2915
|
+
opIds: readonly string[];
|
|
2916
|
+
}
|
|
1441
2917
|
interface ControllerOp {
|
|
1442
2918
|
id: string;
|
|
1443
2919
|
schemaVersion: 1;
|
|
1444
2920
|
kind: "upsert" | "restore" | "remove";
|
|
1445
|
-
objectType: "stroke" | "note" | "text" | "table" | "image" | "timer" | "custom"
|
|
2921
|
+
objectType: "stroke" | "note" | "text" | "table" | "image" | "timer" | "rectangle" | "ellipse" | "group" | "line" | "arrow" | "polygon" | "star" | "heart" | "custom"
|
|
2922
|
+
/**
|
|
2923
|
+
* A whole-document paint-order sync (Phase 3), not a per-object type —
|
|
2924
|
+
* `objectId` is always the fixed sentinel `"order"` and `payload` is
|
|
2925
|
+
* `{ order: string[] }`. The only `objectType` with no matching
|
|
2926
|
+
* `BoardObject`/document collection; kept in this same union (rather
|
|
2927
|
+
* than a separate wire message) so it flows through the existing
|
|
2928
|
+
* `PersistenceAdapter`/`CollaborationAdapter` opaquely, unchanged.
|
|
2929
|
+
*/
|
|
2930
|
+
| "order" | "page" | "notebook" | "section";
|
|
2931
|
+
/** Absent for the original page; scopes content and order Ops to an additional page. */
|
|
2932
|
+
pageId?: string;
|
|
1446
2933
|
objectId: string;
|
|
1447
2934
|
payload?: unknown;
|
|
2935
|
+
/**
|
|
2936
|
+
* This op's position in its own originating client's local sequence
|
|
2937
|
+
* (Phase 7) — 1, 2, 3, ... per controller instance, distinct from `id`
|
|
2938
|
+
* (an opaque, globally-unique identifier used for dedup/ack, not
|
|
2939
|
+
* ordering) and from a server's own authoritative ordering (e.g.
|
|
2940
|
+
* `referenceCollaborationServer.ts`'s per-room `version` counter).
|
|
2941
|
+
* Present on every op this SDK originates locally; a remote peer's op
|
|
2942
|
+
* carries whatever its own origin set, unchanged — never renumbered in
|
|
2943
|
+
* transit. Absent on an op minted by decoding the legacy wire envelope
|
|
2944
|
+
* (`scrawlOpEnvelope.ts`), which predates this field and has no
|
|
2945
|
+
* per-client sequence concept of its own.
|
|
2946
|
+
*/
|
|
2947
|
+
clientSequence?: number;
|
|
2948
|
+
/**
|
|
2949
|
+
* The `CollaboratorIdentity.id` of this op's originating client (Phase
|
|
2950
|
+
* 7) — set for every op this SDK originates locally when `identity` is
|
|
2951
|
+
* configured, omitted entirely otherwise (never sent as `undefined`).
|
|
2952
|
+
* The explicit foundation for a future per-author undo filter (a local
|
|
2953
|
+
* user's own undo should only ever touch their own ops) — no undo-stack
|
|
2954
|
+
* behavior itself changes this phase.
|
|
2955
|
+
*/
|
|
2956
|
+
clientId?: string;
|
|
1448
2957
|
}
|
|
2958
|
+
/**
|
|
2959
|
+
* The one sanctioned seam for real-time multiplayer — implement this against
|
|
2960
|
+
* a Host's own collaboration backend (WebSocket relay, CRDT server, etc.).
|
|
2961
|
+
* `connect()` is called once per controller with the local user's
|
|
2962
|
+
* `identity` and a `receive` callback the adapter invokes with incoming
|
|
2963
|
+
* Ops, presence updates, acks, and connection status; it resolves with a
|
|
2964
|
+
* `CollaborationSession` the controller uses to send local Ops and presence
|
|
2965
|
+
* back out. Passed via `createBoardController({ adapters: { collaboration } })`;
|
|
2966
|
+
* omit it entirely to run single-player.
|
|
2967
|
+
*/
|
|
1449
2968
|
interface CollaborationAdapter {
|
|
1450
2969
|
connect(options: DocumentContext & {
|
|
1451
2970
|
identity: CollaboratorIdentity;
|
|
@@ -1456,14 +2975,72 @@ interface CollaboratorIdentity {
|
|
|
1456
2975
|
id: string;
|
|
1457
2976
|
name: string;
|
|
1458
2977
|
color?: string;
|
|
2978
|
+
/** Host-supplied extras (avatar URL, role, etc.) — opaque to Scrawl, forwarded into any resulting `PresenceUser` unread and never interpreted. */
|
|
2979
|
+
metadata?: Record<string, unknown>;
|
|
1459
2980
|
}
|
|
1460
2981
|
interface CollaborationReceiver {
|
|
1461
2982
|
ops(ops: readonly ControllerOp[]): void;
|
|
2983
|
+
/**
|
|
2984
|
+
* The current presence roster (Phase 6, ADR 0015) — always a full
|
|
2985
|
+
* replacement, never a delta, matching `presence.sync`'s existing
|
|
2986
|
+
* semantics exactly (an adapter that aggregates wire deltas into a full
|
|
2987
|
+
* roster before calling this is the adapter's own job, not the
|
|
2988
|
+
* controller's). Required on this interface (not optional) because a
|
|
2989
|
+
* Host only ever *consumes* `CollaborationReceiver` — never implements
|
|
2990
|
+
* it — so adding a required method here cannot break an existing custom
|
|
2991
|
+
* `CollaborationAdapter`. An adapter with no presence support simply
|
|
2992
|
+
* never calls it.
|
|
2993
|
+
*/
|
|
2994
|
+
presence(users: readonly PresenceUser[]): void;
|
|
2995
|
+
/**
|
|
2996
|
+
* The server has confirmed receipt of these op ids (Phase 7) —
|
|
2997
|
+
* distinguishes "sent" from "server accepted," which `sendOps` alone
|
|
2998
|
+
* (fire-and-forget) cannot. Required for the same reason `presence` is:
|
|
2999
|
+
* Hosts only ever consume this interface, never implement it, so this
|
|
3000
|
+
* cannot break an existing custom `CollaborationAdapter`. An adapter with
|
|
3001
|
+
* no ack support simply never calls it — the collaboration pipe still
|
|
3002
|
+
* works exactly as it did before this existed, just without the
|
|
3003
|
+
* bookkeeping/observability this enables.
|
|
3004
|
+
*/
|
|
3005
|
+
acknowledged(opIds: readonly string[]): void;
|
|
1462
3006
|
status(state: "online" | "reconnecting" | "offline"): void;
|
|
1463
3007
|
error(cause: unknown): void;
|
|
1464
3008
|
}
|
|
3009
|
+
/**
|
|
3010
|
+
* Result of `CollaborationSession.requestSync()` (Phase 7). `"ops"` means
|
|
3011
|
+
* the adapter's own live-pipe cache fully covered the gap since the
|
|
3012
|
+
* caller's last known revision — apply `ops` and the client is caught up,
|
|
3013
|
+
* no persistence reload needed. `"unavailable"` means it couldn't (gap too
|
|
3014
|
+
* large, server restarted, or the adapter has no retained history at all)
|
|
3015
|
+
* — the caller must fall back to a persistence-backed reload. This is a
|
|
3016
|
+
* best-effort *liveness* cache, deliberately never a durable source of
|
|
3017
|
+
* truth (ADR 0006's "collaboration is never a second source of document
|
|
3018
|
+
* truth" — see ADR 0015's own extension of that principle to presence,
|
|
3019
|
+
* now extended once more, the same way, to this).
|
|
3020
|
+
*/
|
|
3021
|
+
type CollaborationSyncResult = {
|
|
3022
|
+
state: "ops";
|
|
3023
|
+
ops: readonly ControllerOp[];
|
|
3024
|
+
serverRevision: string;
|
|
3025
|
+
} | {
|
|
3026
|
+
state: "unavailable";
|
|
3027
|
+
};
|
|
1465
3028
|
interface CollaborationSession {
|
|
1466
3029
|
sendOps(ops: readonly ControllerOp[]): void;
|
|
3030
|
+
/**
|
|
3031
|
+
* Publishes this client's own local presence (Phase 6, ADR 0015) —
|
|
3032
|
+
* best-effort, unordered, never persisted, never an Op. Optional: an
|
|
3033
|
+
* adapter that doesn't support presence simply omits this method, and
|
|
3034
|
+
* `presence.broadcast()` becomes a silent no-op.
|
|
3035
|
+
*/
|
|
3036
|
+
updatePresence?(presence: LocalPresence): void;
|
|
3037
|
+
/**
|
|
3038
|
+
* Requests an incremental catch-up after a reconnect (Phase 7) — optional;
|
|
3039
|
+
* an adapter that doesn't support this simply omits the method, and the
|
|
3040
|
+
* caller (`resyncAfterReconnect`) goes straight to its existing
|
|
3041
|
+
* persistence-backed full reload, unchanged from Phase 6.
|
|
3042
|
+
*/
|
|
3043
|
+
requestSync?(): Promise<CollaborationSyncResult>;
|
|
1467
3044
|
close(): Promise<void>;
|
|
1468
3045
|
}
|
|
1469
3046
|
interface CreateBoardControllerOptions {
|
|
@@ -1483,24 +3060,133 @@ interface CreateBoardControllerOptions {
|
|
|
1483
3060
|
* Trusted Custom tool/object registrations (ticket #22, design:
|
|
1484
3061
|
* docs/research/extension-contracts.md). Validated atomically at
|
|
1485
3062
|
* construction; registration failure throws before any controller is
|
|
1486
|
-
* returned.
|
|
1487
|
-
* internal-only until the reference Extension proves the seam.
|
|
3063
|
+
* returned.
|
|
1488
3064
|
*/
|
|
1489
3065
|
extensions?: readonly ScrawlExtension[];
|
|
1490
3066
|
/**
|
|
1491
3067
|
* Optional Host-managed Asset capabilities (ticket #23, design:
|
|
1492
3068
|
* docs/research/asset-resolution-resource-policy.md). Without a
|
|
1493
3069
|
* resolver, referenced Assets preserve their Document geometry and
|
|
1494
|
-
* render an accessible placeholder.
|
|
1495
|
-
* package entry point — internal-only until the reference resolver
|
|
1496
|
-
* proves the seam, matching how `extensions` is scoped.
|
|
3070
|
+
* render an accessible placeholder.
|
|
1497
3071
|
*/
|
|
1498
3072
|
assetResolver?: AssetResolver;
|
|
1499
3073
|
assetIngestor?: AssetIngestor;
|
|
1500
3074
|
/** Clamped to 64–512MiB; defaults to 256MiB. */
|
|
1501
3075
|
assetCacheBytes?: number;
|
|
3076
|
+
/**
|
|
3077
|
+
* The rendered board surface's color and reference grid. Defaults to the
|
|
3078
|
+
* light theme preset's values; `<Scrawl>` keeps this current across theme
|
|
3079
|
+
* changes via `boardTheme.set` below — a headless/browser-tier Host that
|
|
3080
|
+
* doesn't use the React theme system can set this directly instead.
|
|
3081
|
+
*/
|
|
3082
|
+
boardTheme?: BoardThemeOptions;
|
|
3083
|
+
/**
|
|
3084
|
+
* Debounced auto-flush of pending persistence Ops after document changes
|
|
3085
|
+
* settle (Phase 5). Enabled by default (1000ms debounce) whenever
|
|
3086
|
+
* `adapters.persistence` is configured — today, without this, a Host must
|
|
3087
|
+
* call `flush()` manually after every edit for anything to persist. Pass
|
|
3088
|
+
* `false` to opt out entirely and drive `flush()` yourself, preserving
|
|
3089
|
+
* prior behavior exactly. Never fires on a per-change basis — rapid edits
|
|
3090
|
+
* coalesce into one flush of their final state (ADR 0006).
|
|
3091
|
+
*/
|
|
3092
|
+
autosave?: boolean | {
|
|
3093
|
+
debounceMs?: number;
|
|
3094
|
+
};
|
|
3095
|
+
/**
|
|
3096
|
+
* Throttle for `presence.broadcast()` (Phase 6, ADR 0015) — the minimum
|
|
3097
|
+
* interval between outgoing presence updates sent via the configured
|
|
3098
|
+
* `CollaborationAdapter`. Defaults to 50ms. A trailing throttle: the
|
|
3099
|
+
* latest value passed to `broadcast()` always eventually sends, even if
|
|
3100
|
+
* calls arrive faster than this interval.
|
|
3101
|
+
*/
|
|
3102
|
+
presenceThrottleMs?: number;
|
|
3103
|
+
/**
|
|
3104
|
+
* Caps how many `ControllerOp`s can sit queued, unsent, for the
|
|
3105
|
+
* persistence pipe (`pendingOps`) or the collaboration pipe
|
|
3106
|
+
* (`pendingCollaborationOps`) at once (Phase 7) — each pipe is capped
|
|
3107
|
+
* independently. Prevents unbounded memory growth from a long-lived
|
|
3108
|
+
* offline session or a stuck adapter. Exceeding it never fails or drops
|
|
3109
|
+
* the local edit itself (the Document already applied it optimistically)
|
|
3110
|
+
* — only queueing for that one pipe is skipped, and a
|
|
3111
|
+
* `{code:"queue-overflow", retryable:false}` error is emitted so a Host
|
|
3112
|
+
* can react. Defaults to 1000 — the Phase 9 collaboration coalescing
|
|
3113
|
+
* above (`collaborationCoalesceMs`) already keeps a busy drag from
|
|
3114
|
+
* approaching this on its own, so hitting it in practice means a pipe
|
|
3115
|
+
* has been offline/stuck for a genuinely long editing session.
|
|
3116
|
+
*
|
|
3117
|
+
* **Recovery** (Phase 9): the dropped op itself is gone from that one
|
|
3118
|
+
* pipe's queue — there is no automatic backfill, and the live
|
|
3119
|
+
* controller keeps running with that pipe now silently missing one
|
|
3120
|
+
* edit. Two things stay true regardless: (1) the in-memory Document is
|
|
3121
|
+
* never affected — a queue-overflow can never corrupt or roll back a
|
|
3122
|
+
* local edit, only skip sending it; (2) staleness is per-object, not
|
|
3123
|
+
* permanent — any *later* edit to that same object produces a brand
|
|
3124
|
+
* new, undropped Op carrying its full current state, which naturally
|
|
3125
|
+
* supersedes the gap (the Op model is already last-write-wins/
|
|
3126
|
+
* idempotent, so a superseding Op doesn't need the earlier one to have
|
|
3127
|
+
* arrived). The real risk is an object that's dropped and never edited
|
|
3128
|
+
* again before the controller is disposed or the page reloads — a Host
|
|
3129
|
+
* that needs strict durability should treat `queue-overflow` as a
|
|
3130
|
+
* signal to check `persistence.state`/`pendingOps` pressure (via
|
|
3131
|
+
* `usePersistenceStatus`/`getSnapshot().connection.persistence`)
|
|
3132
|
+
* before disposing, not assume disposing and reconnecting alone
|
|
3133
|
+
* repairs the gap (a fresh `load()` only returns what the backend
|
|
3134
|
+
* already has, which is exactly what's missing the dropped edit).
|
|
3135
|
+
*/
|
|
3136
|
+
maxPendingOps?: number;
|
|
3137
|
+
/**
|
|
3138
|
+
* Coalescing window (ms) for outgoing collaboration Ops (Phase 9) — same
|
|
3139
|
+
* trailing-throttle shape as `presenceThrottleMs`: the first Op after an
|
|
3140
|
+
* idle period sends immediately, and subsequent Ops for the *same*
|
|
3141
|
+
* object within this window replace each other (latest value wins,
|
|
3142
|
+
* matching the already-idempotent Op model) rather than each triggering
|
|
3143
|
+
* its own send. A multi-second drag that previously sent one full Op per
|
|
3144
|
+
* pointer-move now sends at most one per window per touched object.
|
|
3145
|
+
* Persistence (`pendingOps`) is unaffected — it already debounces via
|
|
3146
|
+
* `autosave`, so this option only changes live collaboration traffic.
|
|
3147
|
+
* Defaults to 50ms.
|
|
3148
|
+
*/
|
|
3149
|
+
collaborationCoalesceMs?: number;
|
|
3150
|
+
/**
|
|
3151
|
+
* Caps how many resolved objects a single `content.copy`/`content.cut`
|
|
3152
|
+
* (or their Cmd/Ctrl+C/X keyboard equivalents) will hold in the
|
|
3153
|
+
* in-memory clipboard at once (Phase 9) — `expandSelection` recursively
|
|
3154
|
+
* expands groups, so an unbounded selection (a huge group, or thousands
|
|
3155
|
+
* of individually selected strokes) could otherwise clone and retain an
|
|
3156
|
+
* arbitrarily large snapshot indefinitely, until the next copy/cut
|
|
3157
|
+
* replaces it. Exceeding it rejects the whole copy/cut (nothing is
|
|
3158
|
+
* cloned, and — for cut — nothing is removed from the Document either,
|
|
3159
|
+
* never a partial copy of an arbitrary subset) and emits a
|
|
3160
|
+
* `{code:"clipboard-overflow", retryable:false}` error. Defaults to
|
|
3161
|
+
* 5000.
|
|
3162
|
+
*/
|
|
3163
|
+
maxClipboardItems?: number;
|
|
1502
3164
|
}
|
|
1503
3165
|
interface BoardController {
|
|
3166
|
+
readonly notebooks: {
|
|
3167
|
+
/** Create a notebook containing one section and an empty page; select it. */
|
|
3168
|
+
add(name?: string): string;
|
|
3169
|
+
select(id: string): void;
|
|
3170
|
+
rename(id: string, name: string): void;
|
|
3171
|
+
list(): readonly BoardNotebook[];
|
|
3172
|
+
current(): string;
|
|
3173
|
+
};
|
|
3174
|
+
readonly sections: {
|
|
3175
|
+
/** Create a section in the current or specified notebook, with one empty page. */
|
|
3176
|
+
add(name?: string, notebookId?: string): string;
|
|
3177
|
+
select(id: string): void;
|
|
3178
|
+
rename(id: string, name: string): void;
|
|
3179
|
+
list(notebookId?: string): readonly BoardSection[];
|
|
3180
|
+
current(): string;
|
|
3181
|
+
};
|
|
3182
|
+
readonly pages: {
|
|
3183
|
+
/** Add an empty page and switch to it. */
|
|
3184
|
+
add(name?: string, sectionId?: string): string;
|
|
3185
|
+
select(id: string): void;
|
|
3186
|
+
rename(id: string, name: string): void;
|
|
3187
|
+
list(sectionId?: string): readonly BoardPage[];
|
|
3188
|
+
current(): string;
|
|
3189
|
+
};
|
|
1504
3190
|
readonly document: ReadonlyBoardDocument;
|
|
1505
3191
|
readonly tools: {
|
|
1506
3192
|
select(tool: BuiltInTool | (string & {})): void;
|
|
@@ -1515,7 +3201,7 @@ interface BoardController {
|
|
|
1515
3201
|
noteColor: string;
|
|
1516
3202
|
tableRows: number;
|
|
1517
3203
|
tableCols: number;
|
|
1518
|
-
stampKind:
|
|
3204
|
+
stampKind: StickerKind;
|
|
1519
3205
|
timerDurationMs: number;
|
|
1520
3206
|
}>): void;
|
|
1521
3207
|
current(): BoardStyle;
|
|
@@ -1524,10 +3210,35 @@ interface BoardController {
|
|
|
1524
3210
|
undo(): void;
|
|
1525
3211
|
redo(): void;
|
|
1526
3212
|
};
|
|
3213
|
+
readonly boardTheme: {
|
|
3214
|
+
/** Live update of the board surface color/grid/shape-stroke-width — the controller's identity stays fixed across theme changes. */
|
|
3215
|
+
set(theme: BoardThemeOptions): void;
|
|
3216
|
+
};
|
|
1527
3217
|
readonly view: {
|
|
1528
3218
|
fit(): void;
|
|
3219
|
+
/**
|
|
3220
|
+
* Frame the current selection (Phase 8), the same way `fit()` frames the
|
|
3221
|
+
* whole board. A no-op with nothing selected — deliberately doesn't fall
|
|
3222
|
+
* back to `fit()`'s "frame everything," which would be a surprising
|
|
3223
|
+
* result for an empty selection. On a headless board this can only
|
|
3224
|
+
* re-center the view (no viewport to compute a real zoom-to-fit from),
|
|
3225
|
+
* matching `fit()`'s own headless limitation exactly.
|
|
3226
|
+
*/
|
|
3227
|
+
zoomToSelection(): void;
|
|
1529
3228
|
zoomTo(value: number): void;
|
|
1530
3229
|
centerOn(point: BoardPoint): void;
|
|
3230
|
+
/**
|
|
3231
|
+
* Tell the canvas an inline text editor is open over a text block (`{ id }`)
|
|
3232
|
+
* or a table cell (`{ id, row, col }`), so it stops drawing that text until
|
|
3233
|
+
* the editor closes (`null`) and the two copies don't overlap. The default
|
|
3234
|
+
* UI's inline editors call this; a Host with its own editors should too.
|
|
3235
|
+
* Presentation only — never touches the Document. A no-op when headless.
|
|
3236
|
+
*/
|
|
3237
|
+
setInlineEditing(target: {
|
|
3238
|
+
id: string;
|
|
3239
|
+
row?: number;
|
|
3240
|
+
col?: number;
|
|
3241
|
+
} | null): void;
|
|
1531
3242
|
get(): BoardView;
|
|
1532
3243
|
boardToScreen(point: BoardPoint): ScreenPoint;
|
|
1533
3244
|
screenToBoard(point: ScreenPoint): BoardPoint;
|
|
@@ -1536,6 +3247,75 @@ interface BoardController {
|
|
|
1536
3247
|
add(input: BoardObjectInput): string;
|
|
1537
3248
|
update(id: string, patch: BoardObjectPatch): void;
|
|
1538
3249
|
remove(ids: readonly string[]): void;
|
|
3250
|
+
/**
|
|
3251
|
+
* Clone each given object as a new, unlocked copy offset by a small
|
|
3252
|
+
* fixed cascade (matching the Duplicate affordance's established Host
|
|
3253
|
+
* convention), as one undoable step. Order-preserving: `result[i]` is
|
|
3254
|
+
* the clone of `ids[i]`. Strokes that share a `clusterId` among the
|
|
3255
|
+
* given ids get a single fresh shared `clusterId` in the result, so
|
|
3256
|
+
* duplicating a whole multi-stroke shape (e.g. an arrow's shaft + head)
|
|
3257
|
+
* keeps it one shape — pass every member's id together, not just one.
|
|
3258
|
+
* Unknown ids are silently skipped, matching `remove`'s convention.
|
|
3259
|
+
*/
|
|
3260
|
+
duplicate(ids: readonly string[]): readonly string[];
|
|
3261
|
+
/**
|
|
3262
|
+
* Creates a new Group referencing `ids` as its children and returns its
|
|
3263
|
+
* id, as one undoable step. Unknown ids are silently skipped, matching
|
|
3264
|
+
* `duplicate`/`remove`'s convention. A child id that's itself a group
|
|
3265
|
+
* makes a nested group — expanding nested groups into their leaf
|
|
3266
|
+
* members is always the caller's job, never assumed here (matches the
|
|
3267
|
+
* document-model `GroupObject` itself).
|
|
3268
|
+
*/
|
|
3269
|
+
group(ids: readonly string[]): string;
|
|
3270
|
+
/**
|
|
3271
|
+
* Dissolves one group, returning its immediate children's ids (a nested
|
|
3272
|
+
* subgroup among them stays intact, itself still a group) — the group
|
|
3273
|
+
* record itself is removed, the children are untouched. A no-op
|
|
3274
|
+
* (returns `[]`) if `groupId` isn't a group.
|
|
3275
|
+
*/
|
|
3276
|
+
ungroup(groupId: string): readonly string[];
|
|
3277
|
+
/**
|
|
3278
|
+
* Aligns every given object's matching edge/center to the corresponding
|
|
3279
|
+
* edge/center of their combined bounding box, as one undoable step.
|
|
3280
|
+
* `"top"`/`"bottom"` follow board space's Y-up convention (`"top"` is
|
|
3281
|
+
* the larger Y). Ids that don't resolve, or resolve to a Group (which
|
|
3282
|
+
* has no position of its own), are skipped. A no-op under 2 resolvable
|
|
3283
|
+
* ids — there's nothing to align relative to.
|
|
3284
|
+
*/
|
|
3285
|
+
align(ids: readonly string[], edge: "left" | "right" | "top" | "bottom" | "centerX" | "centerY"): void;
|
|
3286
|
+
/**
|
|
3287
|
+
* Spaces the middle objects' centers evenly between the first and last
|
|
3288
|
+
* (sorted along `axis`), as one undoable step — the two endpoints don't
|
|
3289
|
+
* move. Ids that don't resolve, or resolve to a Group, are skipped. A
|
|
3290
|
+
* no-op under 3 resolvable ids — there's no "middle" to distribute.
|
|
3291
|
+
*/
|
|
3292
|
+
distribute(ids: readonly string[], axis: "x" | "y"): void;
|
|
3293
|
+
/**
|
|
3294
|
+
* Snapshots `ids` (recursively expanded through any group, same as
|
|
3295
|
+
* `duplicate`) into an internal in-memory clipboard — never
|
|
3296
|
+
* `navigator.clipboard`, scoped to this one controller instance and
|
|
3297
|
+
* replaced wholesale by the next `copy`/`cut`. Read-only; works even
|
|
3298
|
+
* on a read-only board.
|
|
3299
|
+
*/
|
|
3300
|
+
copy(ids: readonly string[]): void;
|
|
3301
|
+
/** `copy`, then removes every resolved object (recursively through any group) as one undoable step. */
|
|
3302
|
+
cut(ids: readonly string[]): void;
|
|
3303
|
+
/**
|
|
3304
|
+
* Clones the current clipboard contents onto the board as one undoable
|
|
3305
|
+
* step, offset the same small cascade `duplicate` uses (no cursor
|
|
3306
|
+
* position to paste relative to yet). Returns the new top-level ids —
|
|
3307
|
+
* a pasted group's own id stands for its (also-pasted) children, which
|
|
3308
|
+
* aren't listed separately. `[]` when the clipboard is empty.
|
|
3309
|
+
*/
|
|
3310
|
+
paste(): readonly string[];
|
|
3311
|
+
/**
|
|
3312
|
+
* Select every top-level object (Phase 8) — a group's own id stands for
|
|
3313
|
+
* its children, which aren't selected separately, matching `paste`'s own
|
|
3314
|
+
* "what the user sees" id list. Hidden objects are excluded, consistent
|
|
3315
|
+
* with them already being excluded from marquee selection. Works with
|
|
3316
|
+
* no canvas/engine, same as {@link toggleSelectionVisibility}.
|
|
3317
|
+
*/
|
|
3318
|
+
selectAll(): void;
|
|
1539
3319
|
table: {
|
|
1540
3320
|
addRow(tableId: string): void;
|
|
1541
3321
|
addCol(tableId: string): void;
|
|
@@ -1546,6 +3326,29 @@ interface BoardController {
|
|
|
1546
3326
|
};
|
|
1547
3327
|
select(ids: readonly string[]): void;
|
|
1548
3328
|
import(document: SerializedBoardDocument): readonly string[];
|
|
3329
|
+
/**
|
|
3330
|
+
* Toggle lock state for the current selection (or focused note/text/
|
|
3331
|
+
* table/image/timer), matching whatever a single Host lock/unlock
|
|
3332
|
+
* control already does per object type. A no-op with nothing selected,
|
|
3333
|
+
* on a headless board, or when every actionable target is locked by
|
|
3334
|
+
* another collaborator who isn't the current lock holder.
|
|
3335
|
+
*/
|
|
3336
|
+
toggleSelectionLock(): void;
|
|
3337
|
+
/**
|
|
3338
|
+
* Toggle hidden state for the current selection, as one undo entry
|
|
3339
|
+
* (Phase 8). If any selected object is hidden, shows every selected
|
|
3340
|
+
* object; otherwise hides them all — same "any wins" semantics as
|
|
3341
|
+
* {@link toggleSelectionLock}. Hidden objects stay fully present in the
|
|
3342
|
+
* document (they still serialize, persist, sync, undo/redo) — they just
|
|
3343
|
+
* stop rendering and stop being hit-testable/selectable via pointer
|
|
3344
|
+
* interaction. Unlike `toggleSelectionLock`, this works on a headless
|
|
3345
|
+
* board too: it only touches `selection`/the document, no canvas or
|
|
3346
|
+
* engine involved. Custom objects have no visibility concept (no
|
|
3347
|
+
* `Hideable` field) and are silently skipped, matching how
|
|
3348
|
+
* `toggleSelectionLock` already excludes them. A no-op with nothing
|
|
3349
|
+
* selected or when the selection is only custom objects.
|
|
3350
|
+
*/
|
|
3351
|
+
toggleSelectionVisibility(): void;
|
|
1549
3352
|
};
|
|
1550
3353
|
readonly query: {
|
|
1551
3354
|
get(id: string): DeepReadonly<BoardObject> | undefined;
|
|
@@ -1574,6 +3377,15 @@ interface BoardController {
|
|
|
1574
3377
|
follow(view: PresenceView): void;
|
|
1575
3378
|
/** Ease the camera to a peer's view; returns false (no-op) while mid-stroke. */
|
|
1576
3379
|
gather(view: PresenceView): boolean;
|
|
3380
|
+
/**
|
|
3381
|
+
* Publishes this client's own cursor/tool/view for other collaborators
|
|
3382
|
+
* (Phase 6, ADR 0015), via the configured `CollaborationAdapter` —
|
|
3383
|
+
* throttled internally (`presenceThrottleMs` option, default 50ms) so
|
|
3384
|
+
* a raw pointermove stream never becomes a message-per-event flood. A
|
|
3385
|
+
* no-op if no collaboration adapter is configured, or if the
|
|
3386
|
+
* configured one doesn't implement `updatePresence`.
|
|
3387
|
+
*/
|
|
3388
|
+
broadcast(local: LocalPresence): void;
|
|
1577
3389
|
};
|
|
1578
3390
|
readonly export: {
|
|
1579
3391
|
svg(): string;
|
|
@@ -1599,7 +3411,19 @@ interface BoardController {
|
|
|
1599
3411
|
}>;
|
|
1600
3412
|
dispose(): Promise<void>;
|
|
1601
3413
|
}
|
|
3414
|
+
/**
|
|
3415
|
+
* Creates a {@link BoardController} — the SDK's canonical, capability-grouped
|
|
3416
|
+
* entry point (`content`, `tools`, `style`, `history`, `view`, `query`,
|
|
3417
|
+
* `comments`, `presence`, `export`, `assets`, plus top-level `getSnapshot`/
|
|
3418
|
+
* `subscribe`/`on`/`setReadOnly`/`flush`/`dispose`) for driving a Board
|
|
3419
|
+
* imperatively from any JS/TS runtime. Supply a `canvas` to render, or omit
|
|
3420
|
+
* it to run headless (SSR, tests, or a document/history-only integration).
|
|
3421
|
+
* Persistence and Collaboration are opt-in via `options.adapters` — without
|
|
3422
|
+
* them the controller runs entirely in memory. Call `dispose()` when done to
|
|
3423
|
+
* release the renderer, adapters, and any pending timers.
|
|
3424
|
+
*/
|
|
1602
3425
|
declare function createBoardController(options: CreateBoardControllerOptions): BoardController;
|
|
3426
|
+
/** @deprecated Use `BoardController.getSnapshot()`'s return type instead. */
|
|
1603
3427
|
type LocalBoardSnapshot = {
|
|
1604
3428
|
documentId: string;
|
|
1605
3429
|
selectedStrokeId: string | null;
|
|
@@ -1608,10 +3432,12 @@ type LocalBoardSnapshot = {
|
|
|
1608
3432
|
canRedo: boolean;
|
|
1609
3433
|
disposed: boolean;
|
|
1610
3434
|
};
|
|
3435
|
+
/** @deprecated Use `CreateBoardControllerOptions` with `createBoardController` instead. */
|
|
1611
3436
|
type LocalBoardOptions = {
|
|
1612
3437
|
documentId: string;
|
|
1613
3438
|
initialDocument?: SerializedBoardDocument;
|
|
1614
3439
|
};
|
|
3440
|
+
/** @deprecated Use `BoardController` (from `createBoardController`) instead — this stroke-only, single-tool surface predates the full capability-grouped controller. */
|
|
1615
3441
|
type LocalBoard = {
|
|
1616
3442
|
drawStroke(stroke: Stroke): void;
|
|
1617
3443
|
selectAt(point: BoardPoint): string | null;
|
|
@@ -1622,34 +3448,100 @@ type LocalBoard = {
|
|
|
1622
3448
|
subscribe(listener: () => void): () => void;
|
|
1623
3449
|
dispose(): Promise<void>;
|
|
1624
3450
|
};
|
|
3451
|
+
/** @deprecated Use `createBoardController` instead — this is a thin, stroke-only wrapper kept for the original Phase 1 tracer's compatibility. */
|
|
1625
3452
|
declare function createLocalBoard(options: LocalBoardOptions): LocalBoard;
|
|
1626
3453
|
|
|
3454
|
+
interface CreateMemoryPersistenceOptions {
|
|
3455
|
+
/** Pre-seeded documents, keyed by document id — as if a prior session had already saved them. Seeded documents start at revision "1". */
|
|
3456
|
+
seed?: Record<string, CurrentSerializedDocument>;
|
|
3457
|
+
}
|
|
3458
|
+
/**
|
|
3459
|
+
* Creates a real, in-memory `PersistenceAdapter`. One instance can back
|
|
3460
|
+
* multiple documents (keyed by `DocumentContext.documentId`, like every
|
|
3461
|
+
* other adapter in this package). State lives only in this instance —
|
|
3462
|
+
* discarded on garbage collection, never written to disk.
|
|
3463
|
+
*/
|
|
3464
|
+
declare function createMemoryPersistence(options?: CreateMemoryPersistenceOptions): PersistenceAdapter;
|
|
3465
|
+
|
|
1627
3466
|
type ScrawlThemePreset = "light" | "dark";
|
|
1628
3467
|
type ScrawlDensity = "comfortable" | "compact";
|
|
3468
|
+
type ScrawlGridMode = "none" | "line" | "dot";
|
|
3469
|
+
type ScrawlSurfaceTexture = "flat" | "textured";
|
|
3470
|
+
/**
|
|
3471
|
+
* Every field is optional — anything you don't set falls back to the
|
|
3472
|
+
* chosen `preset` ("light" or "dark", see {@link resolveScrawlTheme}).
|
|
3473
|
+
* Overrides are semantic, board-local runtime configuration: they never
|
|
3474
|
+
* get written into the Document or into exports, so switching themes is
|
|
3475
|
+
* always non-destructive.
|
|
3476
|
+
*/
|
|
1629
3477
|
interface ScrawlTheme {
|
|
3478
|
+
/** UI chrome background — toolbar/panel base surface. Distinct from `boardSurface` (the canvas itself). */
|
|
1630
3479
|
surface?: string;
|
|
3480
|
+
/** UI chrome background, one step up from `surface` — popovers, dropdowns, elevated panels. */
|
|
1631
3481
|
surfaceRaised?: string;
|
|
3482
|
+
/** UI chrome background, one step down from `surface` — subtle fills, hover states. */
|
|
1632
3483
|
surfaceMuted?: string;
|
|
3484
|
+
/** Primary UI text color. Checked for contrast against `surface`. */
|
|
1633
3485
|
text?: string;
|
|
3486
|
+
/** Secondary/de-emphasized UI text color. Checked for contrast against `surface`. */
|
|
1634
3487
|
textMuted?: string;
|
|
3488
|
+
/** Borders and dividers between UI chrome elements. */
|
|
1635
3489
|
edge?: string;
|
|
3490
|
+
/**
|
|
3491
|
+
* Brand accent color. Drives the active/selected state of toolbar
|
|
3492
|
+
* controls (e.g. the active tool button): its icon/text render in this
|
|
3493
|
+
* color, and its background is automatically derived as a light tint of
|
|
3494
|
+
* it (via `color-mix`) — set this one token and both follow.
|
|
3495
|
+
*/
|
|
3496
|
+
primary?: string;
|
|
3497
|
+
/** Focus ring color. Checked for contrast against `surface`. */
|
|
1636
3498
|
focus?: string;
|
|
3499
|
+
/** Selection highlight color (e.g. selected list items, not board object selection). */
|
|
1637
3500
|
selection?: string;
|
|
3501
|
+
/** Destructive/error state color (delete confirmations, error text). */
|
|
1638
3502
|
danger?: string;
|
|
3503
|
+
/** Warning state color. */
|
|
1639
3504
|
warning?: string;
|
|
3505
|
+
/** Success/confirmation state color. */
|
|
1640
3506
|
success?: string;
|
|
3507
|
+
/** Font stack for UI chrome (toolbar labels, menus, dialogs). */
|
|
1641
3508
|
uiFontFamily?: string;
|
|
3509
|
+
/** Font stack for board content and data (e.g. table cell text). */
|
|
1642
3510
|
dataFontFamily?: string;
|
|
3511
|
+
/** Base UI font size in px. Range: 12–24. */
|
|
1643
3512
|
baseFontSize?: number;
|
|
3513
|
+
/** Regular UI font weight. Range: 300–900. */
|
|
1644
3514
|
regularWeight?: number;
|
|
3515
|
+
/** Emphasized UI font weight (headings, active states). Range: 300–900. */
|
|
1645
3516
|
strongWeight?: number;
|
|
3517
|
+
/** Corner radius for small controls (buttons, inputs) in px. Range: 0–32. */
|
|
1646
3518
|
controlRadius?: number;
|
|
3519
|
+
/** Corner radius for panels/dialogs in px. Range: 0–32. */
|
|
1647
3520
|
panelRadius?: number;
|
|
3521
|
+
/** CSS `box-shadow` value for subtle elevation (e.g. toolbar). */
|
|
1648
3522
|
elevationLow?: string;
|
|
3523
|
+
/** CSS `box-shadow` value for prominent elevation (e.g. modals). */
|
|
1649
3524
|
elevationHigh?: string;
|
|
3525
|
+
/** UI transition duration in ms. Range: 0–500. */
|
|
1650
3526
|
motionDuration?: number;
|
|
3527
|
+
/** CSS easing function for UI transitions. */
|
|
1651
3528
|
motionEasing?: string;
|
|
3529
|
+
/** UI chrome spacing/sizing scale. */
|
|
1652
3530
|
density?: ScrawlDensity;
|
|
3531
|
+
/** The rendered board/canvas surface color — distinct from `surface` (UI chrome panels). */
|
|
3532
|
+
boardSurface?: string;
|
|
3533
|
+
/** `"flat"` (default): a plain, uniform board surface. `"textured"`: a subtle "melamine" micro-noise + satin sheen, like a physical whiteboard. */
|
|
3534
|
+
boardSurfaceTexture?: ScrawlSurfaceTexture;
|
|
3535
|
+
/** `"none"` (default) keeps the board a plain surface; `"line"`/`"dot"` draw a zoom-adaptive reference grid. */
|
|
3536
|
+
gridMode?: ScrawlGridMode;
|
|
3537
|
+
/** Grid line/dot color. Ignored when `gridMode` is `"none"`. */
|
|
3538
|
+
gridColor?: string;
|
|
3539
|
+
/** Grid spacing in board units at 100% zoom. Ignored when `gridMode` is `"none"`. */
|
|
3540
|
+
gridSpacing?: number;
|
|
3541
|
+
/** On-screen grid line/dot width in CSS pixels. Range: 0.5–8. A reference aid, so unlike shape/ink strokes it stays this width at any zoom. */
|
|
3542
|
+
gridLineWidth?: number;
|
|
3543
|
+
/** Shape (rectangle, ellipse, arrow, ...) border width, in board units. Range: 0.01–5. Scales with zoom like ink, since it's part of the drawn content. */
|
|
3544
|
+
shapeStrokeWidth?: number;
|
|
1653
3545
|
}
|
|
1654
3546
|
type ResolvedScrawlTheme = Required<ScrawlTheme>;
|
|
1655
3547
|
interface ScrawlThemeDiagnostic {
|
|
@@ -1666,6 +3558,11 @@ declare const scrawlThemePresets: Readonly<Record<ScrawlThemePreset, Readonly<Re
|
|
|
1666
3558
|
declare function validateScrawlTheme(theme: ScrawlTheme | Record<string, unknown>): ScrawlThemeDiagnostic[];
|
|
1667
3559
|
declare function resolveScrawlTheme(preset?: ScrawlThemePreset, theme?: ScrawlTheme | Record<string, unknown>): ScrawlResolvedTheme;
|
|
1668
3560
|
|
|
3561
|
+
interface CommentAuthor {
|
|
3562
|
+
name: string;
|
|
3563
|
+
color?: string;
|
|
3564
|
+
}
|
|
3565
|
+
|
|
1669
3566
|
interface DefaultBoardChromeProps {
|
|
1670
3567
|
controller: BoardController;
|
|
1671
3568
|
snapshot: BoardSnapshot;
|
|
@@ -1674,8 +3571,22 @@ interface DefaultBoardChromeProps {
|
|
|
1674
3571
|
style?: React.CSSProperties;
|
|
1675
3572
|
regions?: Partial<Record<DefaultUIRegion, boolean>>;
|
|
1676
3573
|
slots?: DefaultUISlots;
|
|
3574
|
+
/**
|
|
3575
|
+
* Override any built-in tool icon. Tools without an override keep the
|
|
3576
|
+
* SDK's outline icon. Accessible labels and shortcut tooltips are preserved.
|
|
3577
|
+
*/
|
|
3578
|
+
icons?: Partial<Record<BuiltInTool, ReactNode>>;
|
|
3579
|
+
/** Name (and pin colour) shown on comments left through the default comment UI. Defaults to "You". */
|
|
3580
|
+
commentAuthor?: CommentAuthor;
|
|
1677
3581
|
}
|
|
1678
|
-
|
|
3582
|
+
|
|
3583
|
+
/**
|
|
3584
|
+
* `comments` is the built-in comment composer/thread, which keeps comments for
|
|
3585
|
+
* the session; `images` is the Image tool's file picker plus drag-and-drop.
|
|
3586
|
+
* Turn either off when the Host handles `comment-draft-request`/
|
|
3587
|
+
* `comment-open-request` or `import-request` itself.
|
|
3588
|
+
*/
|
|
3589
|
+
type DefaultUIRegion = "tools" | "history" | "view" | "style" | "search" | "import" | "export" | "inlineEditing" | "styleShelf" | "focusedItemToolbar" | "emptyState" | "pages" | "comments" | "images";
|
|
1679
3590
|
/** Props for the toolbar/topBar/stylePanel/contextMenu slots. */
|
|
1680
3591
|
interface BoardSlotProps {
|
|
1681
3592
|
controller: BoardController;
|
|
@@ -1703,7 +3614,7 @@ interface DefaultUISlots {
|
|
|
1703
3614
|
*/
|
|
1704
3615
|
contextMenu?: ComponentType<BoardSlotProps> | null;
|
|
1705
3616
|
}
|
|
1706
|
-
declare function DefaultBoardChrome({ controller, snapshot, renderPortal, className, style, regions, slots }: DefaultBoardChromeProps): react.JSX.Element;
|
|
3617
|
+
declare function DefaultBoardChrome({ controller, snapshot, renderPortal, className, style, regions, slots, icons, commentAuthor }: DefaultBoardChromeProps): react.JSX.Element;
|
|
1707
3618
|
|
|
1708
3619
|
interface InlineEditorsProps {
|
|
1709
3620
|
controller: BoardController;
|
|
@@ -1728,12 +3639,68 @@ interface MultiplayerCursorsProps {
|
|
|
1728
3639
|
*/
|
|
1729
3640
|
declare function MultiplayerCursors({ controller, onSelectUser, onJumpToUser }: MultiplayerCursorsProps): react.JSX.Element | null;
|
|
1730
3641
|
|
|
3642
|
+
interface FocusedItemToolbarProps {
|
|
3643
|
+
controller: BoardController;
|
|
3644
|
+
snapshot: BoardSnapshot;
|
|
3645
|
+
}
|
|
3646
|
+
/**
|
|
3647
|
+
* Floating toolbar above the focused note, shape, text block, or semantic
|
|
3648
|
+
* Rectangle/Ellipse — Colour, Size (note/text) or Width (shape), Fill/Stroke
|
|
3649
|
+
* (Rectangle/Ellipse), Lock/Unlock, Duplicate, Delete. Images (photos and
|
|
3650
|
+
* placed emoji/icons) get Lock/Duplicate/Delete only — the native gizmo
|
|
3651
|
+
* already resizes them. Table/timer/custom objects and plain (non-shape) ink
|
|
3652
|
+
* strokes never get a toolbar here — out of scope for this destination.
|
|
3653
|
+
*
|
|
3654
|
+
* Rectangle/Ellipse (Phase 2) are a second, parallel "shape" concept from
|
|
3655
|
+
* the legacy ink-stroke shape below: both draw from the same toolbar
|
|
3656
|
+
* buttons and look the same to a user, but a Rectangle/Ellipse is a real
|
|
3657
|
+
* BoardObject with its own resize handles (independent width/height,
|
|
3658
|
+
* `ShapeResizeHandle`), while a legacy shape-stroke keeps going through the
|
|
3659
|
+
* native selection gizmo. This duality is deliberate and temporary — see
|
|
3660
|
+
* docs/reports/phase-2-document-object-model.md.
|
|
3661
|
+
*
|
|
3662
|
+
* Lock/Unlock carries no per-user ownership gating: nothing else in this
|
|
3663
|
+
* SDK enforces lock ownership either (`content.update` never checks
|
|
3664
|
+
* `locked`, and the engine's own internal unlock check has no way for a
|
|
3665
|
+
* Host to ever supply a real user id) — locking is advisory UI state
|
|
3666
|
+
* throughout, and this toolbar matches that rather than inventing an
|
|
3667
|
+
* enforcement story alone.
|
|
3668
|
+
*
|
|
3669
|
+
* A shape can be several strokes sharing one `clusterId` (e.g. an arrow's
|
|
3670
|
+
* shaft + head) — every action here applies to the whole cluster. Resolved
|
|
3671
|
+
* via `query.all()` + `clusterId`, not `snapshot.selection`: a canvas click
|
|
3672
|
+
* on one member selects every member internally, but the public selection
|
|
3673
|
+
* bridge only ever reports one id (`FocusedItem.id` is singular by design),
|
|
3674
|
+
* so reconstructing the cluster from the document is the reliable path
|
|
3675
|
+
* regardless of how the selection was made.
|
|
3676
|
+
*
|
|
3677
|
+
* A focused note also gets drag-to-resize corner handles — notes have no
|
|
3678
|
+
* gizmo of their own (the native selection gizmo is ink-stroke/shape-only),
|
|
3679
|
+
* so this is that capability's default-ui-owned equivalent. Proportional
|
|
3680
|
+
* (always-square) resize from the note's own centre, clamped to
|
|
3681
|
+
* `[MIN_SIZE, MAX_SIZE]`, independent of which corner is grabbed.
|
|
3682
|
+
*/
|
|
3683
|
+
declare function FocusedItemToolbar({ controller, snapshot }: FocusedItemToolbarProps): react.JSX.Element | null;
|
|
3684
|
+
|
|
1731
3685
|
interface StyleShelfProps {
|
|
1732
3686
|
controller: BoardController;
|
|
1733
3687
|
snapshot: BoardSnapshot;
|
|
3688
|
+
/**
|
|
3689
|
+
* Root-relative point beside the active tool. DefaultBoardChrome supplies
|
|
3690
|
+
* this automatically and keeps the shelf within the board bounds.
|
|
3691
|
+
*/
|
|
3692
|
+
anchor?: {
|
|
3693
|
+
x: number;
|
|
3694
|
+
y: number;
|
|
3695
|
+
};
|
|
3696
|
+
/**
|
|
3697
|
+
* Called after an option is picked (not while typing table dimensions or a
|
|
3698
|
+
* sticker search). DefaultBoardChrome uses it to close the shelf.
|
|
3699
|
+
*/
|
|
3700
|
+
onChoose?(): void;
|
|
1734
3701
|
}
|
|
1735
3702
|
/** Contextual per-tool style controls — visible while a styleable tool is active. */
|
|
1736
|
-
declare function StyleShelf({ controller, snapshot }: StyleShelfProps): react.JSX.Element | null;
|
|
3703
|
+
declare function StyleShelf({ controller, snapshot, anchor, onChoose }: StyleShelfProps): react.JSX.Element | null;
|
|
1737
3704
|
|
|
1738
3705
|
type ThemeStyle = CSSProperties & Record<`--scrawl-${string}`, string | number | undefined>;
|
|
1739
3706
|
interface ScrawlProviderProps {
|
|
@@ -1747,6 +3714,16 @@ interface ScrawlProviderProps {
|
|
|
1747
3714
|
className?: string;
|
|
1748
3715
|
style?: ThemeStyle;
|
|
1749
3716
|
}
|
|
3717
|
+
/**
|
|
3718
|
+
* Wraps a Board `controller` you created yourself (via `createBoardController`)
|
|
3719
|
+
* so its descendants can use `useScrawlController`/`useScrawlSnapshot`/etc.
|
|
3720
|
+
* and render its default UI pieces (`DefaultBoardChrome`, `StyleShelf`, ...).
|
|
3721
|
+
* Prefer `Scrawl` unless you need to construct or own the controller's
|
|
3722
|
+
* lifecycle yourself (e.g. you create it outside React, or need it before
|
|
3723
|
+
* first render). Set `disposeOnUnmount` to have this provider call
|
|
3724
|
+
* `controller.dispose()` on unmount; otherwise disposal remains your own
|
|
3725
|
+
* responsibility.
|
|
3726
|
+
*/
|
|
1750
3727
|
declare function ScrawlProvider({ controller, children, preset, theme, portalContainer: customPortal, disposeOnUnmount, onThemeDiagnostic, className, style }: ScrawlProviderProps): react.JSX.Element;
|
|
1751
3728
|
interface ScrawlProps extends Omit<CreateBoardControllerOptions, "canvas"> {
|
|
1752
3729
|
children?: ReactNode;
|
|
@@ -1760,8 +3737,25 @@ interface ScrawlProps extends Omit<CreateBoardControllerOptions, "canvas"> {
|
|
|
1760
3737
|
onThemeDiagnostic?: (diagnostic: ScrawlThemeDiagnostic) => void;
|
|
1761
3738
|
/** Provide null for a headless Board, or an existing canvas to control its identity. */
|
|
1762
3739
|
canvas?: HTMLCanvasElement | null;
|
|
3740
|
+
/**
|
|
3741
|
+
* Per-tool icon override for the default toolbar and More tools buttons —
|
|
3742
|
+
* only applies when you don't supply `children` (i.e. you're using the
|
|
3743
|
+
* SDK's default UI). See DefaultBoardChromeProps.icons.
|
|
3744
|
+
*/
|
|
3745
|
+
icons?: Partial<Record<BuiltInTool, ReactNode>>;
|
|
1763
3746
|
}
|
|
1764
|
-
|
|
3747
|
+
/**
|
|
3748
|
+
* The fastest path to an embedded Board: creates and owns a
|
|
3749
|
+
* `BoardController` for you (constructed once, disposed on unmount) and
|
|
3750
|
+
* renders it into a canvas. Render with no `children` to get the SDK's
|
|
3751
|
+
* default toolbar/UI chrome, or supply your own `children` (using the
|
|
3752
|
+
* `useScrawlController`/`useScrawlSnapshot` hooks, or the exported
|
|
3753
|
+
* `DefaultBoardChrome`/`StyleShelf`/etc. pieces) to build a custom UI on
|
|
3754
|
+
* top of the same controller. Accepts every `CreateBoardControllerOptions`
|
|
3755
|
+
* field except `canvas` (pass `canvas={null}` for a headless board, or an
|
|
3756
|
+
* existing `<canvas>` element to control its identity yourself).
|
|
3757
|
+
*/
|
|
3758
|
+
declare function Scrawl({ children, preset, theme, portalContainer, className, style, onReady, onError, onThemeDiagnostic, canvas: suppliedCanvas, icons, ...options }: ScrawlProps): react.JSX.Element;
|
|
1765
3759
|
interface ScrawlCanvasProps {
|
|
1766
3760
|
element?: HTMLCanvasElement;
|
|
1767
3761
|
className?: string;
|
|
@@ -1776,14 +3770,50 @@ interface ScrawlDefaultUIProps {
|
|
|
1776
3770
|
regions?: Partial<Record<DefaultUIRegion, boolean>>;
|
|
1777
3771
|
/** Replace (component) or hide (null) a coarse region; omit for the SDK default. */
|
|
1778
3772
|
slots?: DefaultUISlots;
|
|
3773
|
+
/** Per-tool icon override for the toolbar and More tools buttons — see DefaultBoardChromeProps.icons. */
|
|
3774
|
+
icons?: Partial<Record<BuiltInTool, ReactNode>>;
|
|
3775
|
+
/** Author shown on comments left through the default comment UI — see DefaultBoardChromeProps.commentAuthor. */
|
|
3776
|
+
commentAuthor?: CommentAuthor;
|
|
1779
3777
|
}
|
|
1780
|
-
declare function ScrawlDefaultUI({ className, style, regions, slots }: ScrawlDefaultUIProps): react.JSX.Element;
|
|
3778
|
+
declare function ScrawlDefaultUI({ className, style, regions, slots, icons, commentAuthor }: ScrawlDefaultUIProps): react.JSX.Element;
|
|
1781
3779
|
declare function ScrawlPortal({ children }: {
|
|
1782
3780
|
children: ReactNode;
|
|
1783
3781
|
}): react.ReactPortal | null;
|
|
1784
3782
|
declare function useScrawlController(): BoardController;
|
|
1785
3783
|
declare function useScrawlTheme(): ScrawlResolvedTheme;
|
|
1786
3784
|
declare function useScrawlSnapshot(): BoardSnapshot;
|
|
3785
|
+
/**
|
|
3786
|
+
* A thin, purely-derived convenience over
|
|
3787
|
+
* `useScrawlSnapshot().connection.persistence` (Phase 5) — for a Host
|
|
3788
|
+
* component that only cares about save status (e.g. a
|
|
3789
|
+
* "Saving…"/"Saved"/"Offline" indicator) and would otherwise re-derive
|
|
3790
|
+
* this same field access itself. Adds no state and no behavior of its
|
|
3791
|
+
* own: React still owns none of persistence, exactly as before — this
|
|
3792
|
+
* hook only re-reads what the controller already tracks.
|
|
3793
|
+
*
|
|
3794
|
+
* Deliberately does *not* go through `useScrawlSnapshot()` (Phase 9):
|
|
3795
|
+
* `changed()` rebuilds the whole `BoardSnapshot` — including a fresh
|
|
3796
|
+
* `connection.persistence` object — on every document mutation, even ones
|
|
3797
|
+
* that never touch persistence at all, so a component using only this
|
|
3798
|
+
* hook would otherwise re-render on every stroke draw. `useSyncStatus`
|
|
3799
|
+
* below memoizes on the value (`state`/`error`), not just the object
|
|
3800
|
+
* reference, and returns the same cached value across renders where
|
|
3801
|
+
* nothing relevant changed — the same shape `usePresence` already uses
|
|
3802
|
+
* for its own independently-scoped store.
|
|
3803
|
+
*/
|
|
3804
|
+
declare function usePersistenceStatus(): PersistenceSnapshot;
|
|
3805
|
+
/** The `CollaborationSnapshot` equivalent of `usePersistenceStatus` (Phase 6) — same value-memoized convenience over `useScrawlSnapshot().connection.collaboration` (Phase 9: see `usePersistenceStatus`'s doc comment for why it doesn't derive from the full snapshot). */
|
|
3806
|
+
declare function useCollaborationStatus(): CollaborationSnapshot;
|
|
3807
|
+
/**
|
|
3808
|
+
* The current presence roster (Phase 6), live-updating — wraps
|
|
3809
|
+
* `controller.presence.list()`/`subscribe` the same way `useScrawlSnapshot`
|
|
3810
|
+
* wraps the controller's main snapshot, via `useSyncExternalStore`. Does
|
|
3811
|
+
* not re-render on every remote pointer move by itself; it re-renders
|
|
3812
|
+
* whenever the roster the controller already tracks changes, at whatever
|
|
3813
|
+
* rate that arrives at (throttled adapter-side, per `presenceThrottleMs`).
|
|
3814
|
+
*/
|
|
3815
|
+
declare function usePresence(): readonly PresenceUser[];
|
|
3816
|
+
/** @deprecated Use `Scrawl` instead — this predates the full capability-grouped `BoardController` and only exposes the stroke-only `LocalBoard` surface. */
|
|
1787
3817
|
type ScrawlBoardProps = {
|
|
1788
3818
|
documentId: string;
|
|
1789
3819
|
initialDocument?: SerializedBoardDocument;
|
|
@@ -1791,7 +3821,8 @@ type ScrawlBoardProps = {
|
|
|
1791
3821
|
className?: string;
|
|
1792
3822
|
style?: CSSProperties;
|
|
1793
3823
|
};
|
|
3824
|
+
/** @deprecated Use `Scrawl` instead — this predates the full capability-grouped `BoardController` and only exposes the stroke-only `LocalBoard` surface. */
|
|
1794
3825
|
declare function ScrawlBoard({ documentId, initialDocument, onReady, className, style }: ScrawlBoardProps): react.JSX.Element;
|
|
1795
3826
|
|
|
1796
|
-
export { AddImageCommand, AddNoteCommand, AddStrokesCommand, AddTableCommand, AddTextCommand, AddTimerCommand,
|
|
1797
|
-
export type { ApplyOpsResult,
|
|
3827
|
+
export { ASSET_CACHE_BYTES_DEFAULT, ASSET_CACHE_BYTES_MAX, ASSET_CACHE_BYTES_MIN, ASSET_EXPORT_MAX_DECODED_MEGAPIXELS, ASSET_EXPORT_MAX_ENCODED_BYTES, ASSET_MAX_CONCURRENT_RESOLUTIONS, ASSET_MAX_DECODED_MEGAPIXELS, ASSET_MAX_DIMENSION_PX, ASSET_MAX_ENCODED_BYTES, ASSET_REF_MAX_BYTES, ASSET_REF_PATTERN, AddArrowCommand, AddEllipseCommand, AddGroupCommand, AddHeartCommand, AddImageCommand, AddLineCommand, AddNoteCommand, AddPolygonCommand, AddRectangleCommand, AddStarCommand, AddStrokesCommand, AddTableCommand, AddTextCommand, AddTimerCommand, AssetResolutionError, BEACON_INSET, BoardDocument, CURRENT_DOCUMENT_SCHEMA_VERSION, ClusterStore, CommandBatch, DefaultBoardChrome, DeleteArrowCommand, DeleteEllipseCommand, DeleteGroupCommand, DeleteHeartCommand, DeleteImageCommand, DeleteLineCommand, DeleteNoteCommand, DeletePolygonCommand, DeleteRectangleCommand, DeleteStarCommand, DeleteStrokesCommand, DeleteTableCommand, DeleteTextCommand, DeleteTimerCommand, DocumentRecoveryError, EMOJI_LIBRARY, END_TAPER, ERASE_THRESHOLD, EraseCommand, FOG_COLOR, FocusedItemToolbar, HIGHLIGHT_COLORS, History, ICON_LIBRARY, IDENTITY, INK_COLORS, InlineEditors, LockItemsCommand, MIN_WIDTH_FACTOR, MultiplayerCursors, NOTE_COLORS, NOTE_DEFAULT_SIZE, NOTE_DEFAULT_Z, NOTE_MAX_Z, NOTE_MIN_Z, NOTE_PEEL_STEP, ReorderObjectCommand, SDK_DEVELOPMENT_VERSION, SDK_PACKAGE_NAME, SHAPE_DEFAULT_STROKE, SHAPE_DEFAULT_STROKE_WIDTH, SHAPE_MIN_SIZE, SHAPE_STYLE_DEFAULTS, STAMPS, STAMP_SIZE, SUPPORTED_ASSET_MEDIA_TYPES, Scrawl, ScrawlBoard, ScrawlCanvas, ScrawlDefaultUI, ScrawlPortal, ScrawlProvider, SpatialIndex, StyleShelf, TABLE_DEFAULT_CELL_HEIGHT, TABLE_DEFAULT_CELL_WIDTH, TABLE_DEFAULT_FONT_SIZE, TEXT_DEFAULT_SIZE, TIMER_DEFAULT_DURATION_MS, TIMER_DEFAULT_SIZE, TIMER_PRESETS_MS, TransformCommand, TransformObjectsCommand, UpdateArrowCommand, UpdateEllipseCommand, UpdateGroupCommand, UpdateHeartCommand, UpdateImageCommand, UpdateLineCommand, UpdateNoteCommand, UpdatePolygonCommand, UpdateRectangleCommand, UpdateStarCommand, UpdateTableCommand, UpdateTextCommand, UpdateTimerCommand, apply, applyItemLock, assetRef, avgScale, canUnlockItem, changeToOps, clampAssetCacheBytes, cloneArrow, cloneCustomObject, cloneEllipse, cloneGroup, cloneHeart, cloneImage, cloneLine, cloneNote, clonePolygon, cloneRectangle, cloneStar, cloneStroke, cloneTable, cloneText, cloneTimer, createBoardController, createLocalBoard, createMemoryPersistence, documentId, documentSize, documentToSVG, emojiDataUrl, emojiDefinition, formatTimer, iconCategories, iconDataUrl, iconDefinition, invert, isAssetRef, isEmojiName, isIconName, isIdentity, isStampKind, isStickerKind, loadDocumentBytes, measureTable, measureTextBlock, migrateDocument, mul, pauseTimer, placePresenceBeacon, resolveScrawlTheme, ribbonEdges, rotationAbout, scalingAbout, scrawlThemePresets, searchBoard, serializeDocument, serializeLock, serializeStroke, setTimerDuration, stampDataUrl, startTimer, stickerDataUrl, stickerLabel, strokeId, timerExpired, timerRemaining, toggleTimer, translation, useCollaborationStatus, usePersistenceStatus, usePresence, useScrawlController, useScrawlSnapshot, useScrawlTheme, validateScrawlTheme };
|
|
3828
|
+
export type { ApplyOpsResult, ArrowHeadStyle, ArrowObject, AssetDiagnostic, AssetExportFailure, AssetIngestRequest, AssetIngestResult, AssetIngestor, AssetKind, AssetPurpose, AssetRef, AssetResolutionErrorCode, AssetResolveRequest, AssetResolveResult, AssetResolver, BBox, BoardController, BoardControllerError, BoardEventMap, BoardKeyInput, BoardNotebook, BoardObject, BoardObjectInput, BoardObjectPatch, BoardPage, BoardPoint, BoardPointerInput, BoardScene, BoardSection, BoardSlotProps, BoardSnapshot, BoardStroke, BoardStyle, BoardThemeOptions, BoardView, BuiltInTool, ClusterIdFactory, CollaborationAckDiagnostic, CollaborationAdapter, CollaborationReceiver, CollaborationSession, CollaborationSnapshot, CollaborationSyncResult, CollaboratorIdentity, Command, CommandKind, CommentAuthor, CommentMarker, ConnectorBinding, ConnectorBox, ConnectorTarget, ControllerOp, CreateBoardControllerOptions, CreateMemoryPersistenceOptions, CurrentSerializedDocument, CurrentSerializedStroke, CustomBoardObject, CustomObjectAddInput, CustomObjectDefinition, CustomTool, CustomToolDefinition, DeepReadonly, DefaultBoardChromeProps, DefaultUIRegion, DefaultUISlot, DefaultUISlots, DialogSlotProps, DocumentChange, DocumentContext, DocumentId, DocumentLoadResult, DocumentRecoveryCode, EllipseObject, EmojiDefinition, EmojiName, ExportDocumentSVGOptions, ExportDocumentSVGResult, ExtensionCommand, ExtensionDiagnostic, ExtensionHitResult, ExtensionId, ExtensionRequirement, FocusedItem, FocusedItemToolbarProps, GroupObject, HeartObject, IconDefinition, IconName, ImageBlock, InlineEditorsProps, InputModifiers, JsonObject, JsonValue, KitchenTimer, LineObject, LoadResult, LocalBoard, LocalBoardOptions, LocalBoardSnapshot, LocalPresence, LockHolder, LockTarget, Lockable, Mat2x3, MultiplayerCursorsProps, NoteVote, ObjectDescribeContext, ObjectIntent, ObjectType, Op, OpCollection, PersistenceAdapter, PersistenceDiagnostic, PersistenceSnapshot, PolygonObject, PresenceCursor, PresencePlacement, PresenceUser, PresenceView, QueryableBoardObject, ReadonlyBoardDocument, ReadonlyCustomObject, ReadonlyDocumentChange, RectangleObject, ReorderDirection, ReplaceResult, RibbonEdgePoint, SceneEllipse, SceneGroup, SceneImage, ScenePath, SceneRect, SceneText, ScrawlBoardProps, ScrawlCanvasProps, ScrawlDefaultUIProps, ScrawlDensity, ScrawlExtension, ScrawlGridMode, ScrawlProps, ScrawlProviderProps, ScrawlResolvedTheme, ScrawlSurfaceTexture, ScrawlTheme, ScrawlThemeDiagnostic, ScrawlThemePreset, ScreenPoint, ScreenRect, SearchHit, SearchHitKind, SearchableBoard, SearchableComment, SerializedBoardDocument, SerializedBoardPage, SerializedBoardStroke, SerializedPoint, SerializedStroke, StampKind, StarObject, StickerKind, StickyNote, Stroke, StrokeId, StrokePoint, StrokeTool, StyleShelfProps, SupportedAssetMediaType, TableBlock, TextBlock, ToolCancelReason, ToolCapabilities, ToolCursor, ToolId, ViewportInset };
|