@scrawl-board/board 0.1.0-beta.6 → 0.1.0-beta.8
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/README.md +11 -5
- package/dist/browser.d.ts +593 -15
- package/dist/browser.js +6277 -34044
- package/dist/core.d.ts +711 -16
- package/dist/core.js +2015 -51
- package/dist/index.d.ts +1171 -26
- package/dist/index.js +6209 -33607
- package/dist/local.d.ts +469 -0
- package/dist/local.js +234 -0
- package/dist/react.d.ts +635 -19
- package/dist/react.js +6142 -33785
- package/package.json +17 -3
package/dist/index.d.ts
CHANGED
|
@@ -184,6 +184,21 @@ interface CustomObjectDefinition<Props extends JsonValue = JsonValue> {
|
|
|
184
184
|
/** One pure, synchronous step per consecutive schema version. */
|
|
185
185
|
migrate?: Readonly<Record<number, (oldProps: JsonValue) => JsonValue>>;
|
|
186
186
|
describe(object: ReadonlyCustomObject<Props>, context: ObjectDescribeContext): BoardScene;
|
|
187
|
+
/**
|
|
188
|
+
* Optional point-level hit-test precision (Phase 8). Every custom object
|
|
189
|
+
* hit-tests against its bounding box (`fallback.bounds`) by default — this
|
|
190
|
+
* lets a non-rectangular shape (e.g. a circular card, an L-shaped region)
|
|
191
|
+
* reject a point that's inside that box but outside its actual visible
|
|
192
|
+
* silhouette, tightening a click/marquee/raycast hit to the shape's real
|
|
193
|
+
* outline. `point` is in this object's own local space — the same
|
|
194
|
+
* untransformed space `describe`'s returned geometry already lives in
|
|
195
|
+
* (the caller inverse-transforms the pointer's board point through
|
|
196
|
+
* `object.transform` before calling this). Absent means every point
|
|
197
|
+
* inside the bounding box hits, matching pre-Phase-8 behavior exactly.
|
|
198
|
+
* Rejecting a point here does not fall through to whatever's underneath —
|
|
199
|
+
* the gesture simply misses this object, same as clicking empty space.
|
|
200
|
+
*/
|
|
201
|
+
hitTest?(object: ReadonlyCustomObject<Props>, point: BoardPoint): boolean;
|
|
187
202
|
}
|
|
188
203
|
interface SceneNodeBase {
|
|
189
204
|
key: string;
|
|
@@ -217,6 +232,15 @@ interface SceneGroup extends SceneNodeBase {
|
|
|
217
232
|
kind: "group";
|
|
218
233
|
children: readonly BoardScene[];
|
|
219
234
|
}
|
|
235
|
+
/**
|
|
236
|
+
* **No renderer or SVG-export interpreter exists for this node kind yet**
|
|
237
|
+
* (tracked as deferred work — see `renderer/shapes/customObjects.ts`'s
|
|
238
|
+
* `"path"` case). Returning a `ScenePath` from `describe()` renders nothing,
|
|
239
|
+
* exports nothing, and contributes no hit-test bounds — it neither errors
|
|
240
|
+
* nor emits a diagnostic. Until an interpreter ships, build custom shapes
|
|
241
|
+
* from `SceneRect`/`SceneEllipse`/`SceneGroup`/`SceneText`/`SceneImage`
|
|
242
|
+
* instead.
|
|
243
|
+
*/
|
|
220
244
|
interface ScenePath extends SceneNodeBase {
|
|
221
245
|
kind: "path";
|
|
222
246
|
/** SVG-style path data, board-local coordinates. */
|
|
@@ -389,8 +413,23 @@ declare function canUnlockItem(item: Lockable | undefined, userId: string | unde
|
|
|
389
413
|
/** Wire fields for a locked item; omitted entirely when unlocked. */
|
|
390
414
|
declare function serializeLock(item: Lockable): Lockable;
|
|
391
415
|
|
|
416
|
+
/**
|
|
417
|
+
* Per-object visibility (Phase 8) — mirrors `itemLock.ts`'s `Lockable`
|
|
418
|
+
* pattern exactly, but simpler: unlike a lock, hidden state carries no
|
|
419
|
+
* holder/ownership concept, so there's no analogue to `LockHolder`/
|
|
420
|
+
* `canUnlockItem`. A hidden object stays fully present in the Document
|
|
421
|
+
* (still serializes, persists, syncs, undoes/redoes) — it just skips
|
|
422
|
+
* rendering and hit-testing/selection candidacy. `hidden` absent or
|
|
423
|
+
* `false` means visible; this keeps every pre-Phase-8 document (which has
|
|
424
|
+
* no `hidden` field on any object at all) implicitly fully visible with
|
|
425
|
+
* zero migration needed.
|
|
426
|
+
*/
|
|
427
|
+
interface Hideable {
|
|
428
|
+
hidden?: boolean;
|
|
429
|
+
}
|
|
430
|
+
|
|
392
431
|
/** A kitchen timer sitting on the board. Remaining time is derived, not ticked. */
|
|
393
|
-
interface KitchenTimer extends Lockable {
|
|
432
|
+
interface KitchenTimer extends Lockable, Hideable {
|
|
394
433
|
id: string;
|
|
395
434
|
x: number;
|
|
396
435
|
y: number;
|
|
@@ -415,6 +454,160 @@ declare function toggleTimer(timer: KitchenTimer, now: number): KitchenTimer;
|
|
|
415
454
|
declare function setTimerDuration(timer: KitchenTimer, durationMs: number): KitchenTimer;
|
|
416
455
|
declare function formatTimer(ms: number): string;
|
|
417
456
|
|
|
457
|
+
declare const SHAPE_MIN_SIZE = 0.5;
|
|
458
|
+
/**
|
|
459
|
+
* Centralized shape style defaults (Phase 8) — one object a Host can read
|
|
460
|
+
* to know (or, by not relying on the standalone constants below, override
|
|
461
|
+
* via its own UI state) what a newly drawn Rectangle/Ellipse/Line/Arrow/
|
|
462
|
+
* Polygon/Star/Heart starts with when the user hasn't picked a stroke/width
|
|
463
|
+
* yet. `shapeTool.ts` (commit-time), `renderer/shapes/lines.ts` (render-time
|
|
464
|
+
* fallback for an object missing these fields), and
|
|
465
|
+
* `persistence/serialization/svg.ts` (export-time fallback) all read from
|
|
466
|
+
* here — the two standalone constants below are kept for source
|
|
467
|
+
* compatibility and simply mirror this object's values, not a second
|
|
468
|
+
* source of truth.
|
|
469
|
+
*/
|
|
470
|
+
declare const SHAPE_STYLE_DEFAULTS: {
|
|
471
|
+
readonly stroke: "#1C1C1E";
|
|
472
|
+
readonly strokeWidth: 0.12;
|
|
473
|
+
};
|
|
474
|
+
declare const SHAPE_DEFAULT_STROKE: "#1C1C1E";
|
|
475
|
+
declare const SHAPE_DEFAULT_STROKE_WIDTH: 0.12;
|
|
476
|
+
interface RectangleObject extends Lockable, Hideable {
|
|
477
|
+
id: string;
|
|
478
|
+
x: number;
|
|
479
|
+
y: number;
|
|
480
|
+
width: number;
|
|
481
|
+
height: number;
|
|
482
|
+
fill?: string;
|
|
483
|
+
stroke?: string;
|
|
484
|
+
strokeWidth?: number;
|
|
485
|
+
/** Corner radius in board units; clamped to at most half the shorter side at render time. */
|
|
486
|
+
cornerRadius?: number;
|
|
487
|
+
/** `[0, 1]`; undefined means fully opaque (Phase 4). */
|
|
488
|
+
opacity?: number;
|
|
489
|
+
/**
|
|
490
|
+
* Radians, about the shape's own center `(x + width/2, y - height/2)`.
|
|
491
|
+
* Undefined means 0 (Phase 3). `x`/`y`/`width`/`height` stay in the
|
|
492
|
+
* shape's own unrotated local frame — rotation is a separate, applied-last
|
|
493
|
+
* transform, not baked into them, matching how Stroke/CustomBoardObject
|
|
494
|
+
* keep geometry and placement independent via their own `matrix`.
|
|
495
|
+
*/
|
|
496
|
+
rotation?: number;
|
|
497
|
+
}
|
|
498
|
+
interface EllipseObject extends Lockable, Hideable {
|
|
499
|
+
id: string;
|
|
500
|
+
x: number;
|
|
501
|
+
y: number;
|
|
502
|
+
width: number;
|
|
503
|
+
height: number;
|
|
504
|
+
fill?: string;
|
|
505
|
+
stroke?: string;
|
|
506
|
+
strokeWidth?: number;
|
|
507
|
+
/** `[0, 1]`; undefined means fully opaque (Phase 4). */
|
|
508
|
+
opacity?: number;
|
|
509
|
+
/** Radians, about the shape's own center — see RectangleObject's `rotation` doc. */
|
|
510
|
+
rotation?: number;
|
|
511
|
+
}
|
|
512
|
+
declare function cloneRectangle(rect: RectangleObject): RectangleObject;
|
|
513
|
+
declare function cloneEllipse(ellipse: EllipseObject): EllipseObject;
|
|
514
|
+
/** `"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. */
|
|
515
|
+
type ArrowHeadStyle = "triangle" | "none";
|
|
516
|
+
interface LineObject extends Lockable, Hideable {
|
|
517
|
+
id: string;
|
|
518
|
+
start: BoardPoint;
|
|
519
|
+
end: BoardPoint;
|
|
520
|
+
stroke?: string;
|
|
521
|
+
strokeWidth?: number;
|
|
522
|
+
opacity?: number;
|
|
523
|
+
}
|
|
524
|
+
interface ArrowObject extends Lockable, Hideable {
|
|
525
|
+
id: string;
|
|
526
|
+
start: BoardPoint;
|
|
527
|
+
end: BoardPoint;
|
|
528
|
+
head?: ArrowHeadStyle;
|
|
529
|
+
stroke?: string;
|
|
530
|
+
strokeWidth?: number;
|
|
531
|
+
opacity?: number;
|
|
532
|
+
}
|
|
533
|
+
/**
|
|
534
|
+
* Triangle(3)/Diamond(4)/Pentagon(5)/Hexagon(6)/Octagon(8) as one shared
|
|
535
|
+
* type instead of five near-duplicate interfaces — a regular N-gon
|
|
536
|
+
* inscribed in the same `x`/`y`/`width`/`height`/`rotation` bounding box
|
|
537
|
+
* Rectangle already uses, parameterized by `sides`. Diamond is exactly a
|
|
538
|
+
* 4-sided regular polygon with vertex 0 pointing right (not up, like
|
|
539
|
+
* Triangle/Pentagon/Hexagon) — see `polygonGeometry.ts`'s
|
|
540
|
+
* `polygonStartAngle`, which encodes each side count's own vertex
|
|
541
|
+
* orientation so the outline always matches the legacy drag-preview shape.
|
|
542
|
+
*/
|
|
543
|
+
interface PolygonObject extends Lockable, Hideable {
|
|
544
|
+
id: string;
|
|
545
|
+
x: number;
|
|
546
|
+
y: number;
|
|
547
|
+
width: number;
|
|
548
|
+
height: number;
|
|
549
|
+
sides: 3 | 4 | 5 | 6 | 8;
|
|
550
|
+
fill?: string;
|
|
551
|
+
stroke?: string;
|
|
552
|
+
strokeWidth?: number;
|
|
553
|
+
opacity?: number;
|
|
554
|
+
/** Radians, about the shape's own center — see RectangleObject's `rotation` doc. */
|
|
555
|
+
rotation?: number;
|
|
556
|
+
}
|
|
557
|
+
declare function clonePolygon(polygon: PolygonObject): PolygonObject;
|
|
558
|
+
/** 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`). */
|
|
559
|
+
interface StarObject extends Lockable, Hideable {
|
|
560
|
+
id: string;
|
|
561
|
+
x: number;
|
|
562
|
+
y: number;
|
|
563
|
+
width: number;
|
|
564
|
+
height: number;
|
|
565
|
+
/** Vertex count; today's only shipped preset is 5, matching the legacy tool. */
|
|
566
|
+
points: number;
|
|
567
|
+
/** `(0, 1)` — inner vertex radius as a fraction of the outer radius. */
|
|
568
|
+
innerRadiusRatio: number;
|
|
569
|
+
fill?: string;
|
|
570
|
+
stroke?: string;
|
|
571
|
+
strokeWidth?: number;
|
|
572
|
+
opacity?: number;
|
|
573
|
+
rotation?: number;
|
|
574
|
+
}
|
|
575
|
+
declare function cloneStar(star: StarObject): StarObject;
|
|
576
|
+
/** 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. */
|
|
577
|
+
interface HeartObject extends Lockable, Hideable {
|
|
578
|
+
id: string;
|
|
579
|
+
x: number;
|
|
580
|
+
y: number;
|
|
581
|
+
width: number;
|
|
582
|
+
height: number;
|
|
583
|
+
fill?: string;
|
|
584
|
+
stroke?: string;
|
|
585
|
+
strokeWidth?: number;
|
|
586
|
+
opacity?: number;
|
|
587
|
+
rotation?: number;
|
|
588
|
+
}
|
|
589
|
+
declare function cloneHeart(heart: HeartObject): HeartObject;
|
|
590
|
+
declare function cloneLine(line: LineObject): LineObject;
|
|
591
|
+
declare function cloneArrow(arrow: ArrowObject): ArrowObject;
|
|
592
|
+
/**
|
|
593
|
+
* A logical grouping of other board objects (Phase 3 — Selection,
|
|
594
|
+
* Transformation & Grouping). Deliberately has no `x`/`y`/`transform` of its
|
|
595
|
+
* own — a group's bounds are always derived on demand from its (recursively
|
|
596
|
+
* resolved) children, and "moving/rotating/scaling the group" is exactly a
|
|
597
|
+
* multi-object transform applied to those children, nothing more. A group
|
|
598
|
+
* has no renderer/mesh of its own; its only visual presence is the
|
|
599
|
+
* selection gizmo's bounding box while it's the current selection.
|
|
600
|
+
*
|
|
601
|
+
* `children` may itself contain other group ids (nested groups) — expanding
|
|
602
|
+
* a group into its leaf members is always done by the caller (recursively,
|
|
603
|
+
* with cycle protection), never assumed here.
|
|
604
|
+
*/
|
|
605
|
+
interface GroupObject extends Lockable, Hideable {
|
|
606
|
+
id: string;
|
|
607
|
+
children: string[];
|
|
608
|
+
}
|
|
609
|
+
declare function cloneGroup(group: GroupObject): GroupObject;
|
|
610
|
+
|
|
418
611
|
interface BoardPoint {
|
|
419
612
|
x: number;
|
|
420
613
|
y: number;
|
|
@@ -435,7 +628,7 @@ interface StrokePoint extends BoardPoint {
|
|
|
435
628
|
* geometric outline, not an expressive ink mark.
|
|
436
629
|
*/
|
|
437
630
|
type StrokeTool = "marker" | "highlighter" | "shape";
|
|
438
|
-
interface Stroke extends Lockable {
|
|
631
|
+
interface Stroke extends Lockable, Hideable {
|
|
439
632
|
id: string;
|
|
440
633
|
color: string;
|
|
441
634
|
baseWidth: number;
|
|
@@ -454,7 +647,7 @@ interface Stroke extends Lockable {
|
|
|
454
647
|
declare const ERASE_THRESHOLD = 0.95;
|
|
455
648
|
declare function cloneStroke(stroke: Stroke): Stroke;
|
|
456
649
|
type SerializedPoint = [number, number, number, number];
|
|
457
|
-
interface SerializedStroke extends Lockable {
|
|
650
|
+
interface SerializedStroke extends Lockable, Hideable {
|
|
458
651
|
id: string;
|
|
459
652
|
color: string;
|
|
460
653
|
baseWidth: number;
|
|
@@ -480,6 +673,31 @@ interface SerializedDocument {
|
|
|
480
673
|
timers?: KitchenTimer[];
|
|
481
674
|
/** Absent in documents saved before Custom board objects existed (ticket #22). */
|
|
482
675
|
customObjects?: CustomBoardObject[];
|
|
676
|
+
/** Absent in documents saved before semantic Rectangle objects existed (Phase 2). */
|
|
677
|
+
rectangles?: RectangleObject[];
|
|
678
|
+
/** Absent in documents saved before semantic Ellipse objects existed (Phase 2). */
|
|
679
|
+
ellipses?: EllipseObject[];
|
|
680
|
+
/** Absent in documents saved before Groups existed (Phase 3). */
|
|
681
|
+
groups?: GroupObject[];
|
|
682
|
+
/** Absent in documents saved before semantic Line objects existed (Phase 4). */
|
|
683
|
+
lines?: LineObject[];
|
|
684
|
+
/** Absent in documents saved before semantic Arrow objects existed (Phase 4). */
|
|
685
|
+
arrows?: ArrowObject[];
|
|
686
|
+
/** Absent in documents saved before semantic Polygon objects existed (Phase 4). */
|
|
687
|
+
polygons?: PolygonObject[];
|
|
688
|
+
/** Absent in documents saved before semantic Star objects existed (Phase 4). */
|
|
689
|
+
stars?: StarObject[];
|
|
690
|
+
/** Absent in documents saved before semantic Heart objects existed (Phase 4). */
|
|
691
|
+
hearts?: HeartObject[];
|
|
692
|
+
/**
|
|
693
|
+
* Every content-object id (every type above except comments, which are
|
|
694
|
+
* host-synced and never enter this schema) in paint order, back to front.
|
|
695
|
+
* Absent in documents saved before per-object z-order existed (Phase 3) —
|
|
696
|
+
* migration synthesizes a default order preserving the old fixed-Z-band
|
|
697
|
+
* visual stacking exactly, so an existing document never visibly changes
|
|
698
|
+
* on load; only an explicit reorder action touches this from then on.
|
|
699
|
+
*/
|
|
700
|
+
objectOrder?: string[];
|
|
483
701
|
}
|
|
484
702
|
declare const INK_COLORS: {
|
|
485
703
|
readonly black: "#1C1C1E";
|
|
@@ -523,7 +741,7 @@ interface NoteVote {
|
|
|
523
741
|
* A sticky note: content floating above the board at a z-offset (pillar 3 —
|
|
524
742
|
* depth as an organizational axis). Center position in board space.
|
|
525
743
|
*/
|
|
526
|
-
interface StickyNote extends Lockable {
|
|
744
|
+
interface StickyNote extends Lockable, Hideable {
|
|
527
745
|
id: string;
|
|
528
746
|
x: number;
|
|
529
747
|
y: number;
|
|
@@ -541,7 +759,7 @@ interface StickyNote extends Lockable {
|
|
|
541
759
|
* top-left corner; lines flow downward (-y). Text joins the clustering
|
|
542
760
|
* system like handwriting (build prompt §6.4).
|
|
543
761
|
*/
|
|
544
|
-
interface TextBlock extends Lockable {
|
|
762
|
+
interface TextBlock extends Lockable, Hideable {
|
|
545
763
|
id: string;
|
|
546
764
|
x: number;
|
|
547
765
|
y: number;
|
|
@@ -571,7 +789,7 @@ declare function cloneNote(note: StickyNote): StickyNote;
|
|
|
571
789
|
* Interactive structured table on the board. Position (x, y) is top-left in board units.
|
|
572
790
|
* Cells are indexed as `${row},${col}` keys mapping to cell text content.
|
|
573
791
|
*/
|
|
574
|
-
interface TableBlock extends Lockable {
|
|
792
|
+
interface TableBlock extends Lockable, Hideable {
|
|
575
793
|
id: string;
|
|
576
794
|
x: number;
|
|
577
795
|
y: number;
|
|
@@ -597,7 +815,7 @@ declare const FOG_COLOR = "#FFFFFF";
|
|
|
597
815
|
* An imported image block on the board plane.
|
|
598
816
|
* Coordinates (x, y) represent the center of the image in board space.
|
|
599
817
|
*/
|
|
600
|
-
interface ImageBlock extends Lockable {
|
|
818
|
+
interface ImageBlock extends Lockable, Hideable {
|
|
601
819
|
id: string;
|
|
602
820
|
/**
|
|
603
821
|
* A legacy, read-only data URL (or, historically, an arbitrary string) —
|
|
@@ -647,6 +865,15 @@ type DocumentLoadResult = {
|
|
|
647
865
|
declare function loadDocumentBytes(originalBytes: string): DocumentLoadResult;
|
|
648
866
|
declare function migrateDocument(raw: unknown): DocumentLoadResult;
|
|
649
867
|
declare function serializeDocument(document: unknown): string;
|
|
868
|
+
/**
|
|
869
|
+
* A Document's serialized size in bytes (Phase 5) — UTF-8, not UTF-16
|
|
870
|
+
* `string.length`, since a Document with non-ASCII note/text content (most
|
|
871
|
+
* of them, eventually) would otherwise under-report. Useful for a Host
|
|
872
|
+
* deciding when to warn about an unusually large board, or for logging/
|
|
873
|
+
* telemetry around save size — not consulted by anything inside this
|
|
874
|
+
* package itself, which has no size limit of its own.
|
|
875
|
+
*/
|
|
876
|
+
declare function documentSize(document: CurrentSerializedDocument): number;
|
|
650
877
|
|
|
651
878
|
interface SearchableComment {
|
|
652
879
|
id: string;
|
|
@@ -731,6 +958,60 @@ interface DocumentChange {
|
|
|
731
958
|
/** Ids of removed custom objects. */
|
|
732
959
|
customObjectsRemoved: string[];
|
|
733
960
|
customObjectsUpdated: CustomBoardObject[];
|
|
961
|
+
/** Semantic Rectangle objects (Phase 2). */
|
|
962
|
+
rectanglesAdded: RectangleObject[];
|
|
963
|
+
/** Ids of removed rectangles. */
|
|
964
|
+
rectanglesRemoved: string[];
|
|
965
|
+
rectanglesUpdated: RectangleObject[];
|
|
966
|
+
/** Semantic Ellipse objects (Phase 2). */
|
|
967
|
+
ellipsesAdded: EllipseObject[];
|
|
968
|
+
/** Ids of removed ellipses. */
|
|
969
|
+
ellipsesRemoved: string[];
|
|
970
|
+
ellipsesUpdated: EllipseObject[];
|
|
971
|
+
/** Groups (Phase 3). */
|
|
972
|
+
groupsAdded: GroupObject[];
|
|
973
|
+
/** Ids of removed groups (ungrouping, or deleting a group). */
|
|
974
|
+
groupsRemoved: string[];
|
|
975
|
+
groupsUpdated: GroupObject[];
|
|
976
|
+
/** Semantic Line objects (Phase 4). */
|
|
977
|
+
linesAdded: LineObject[];
|
|
978
|
+
/** Ids of removed lines. */
|
|
979
|
+
linesRemoved: string[];
|
|
980
|
+
linesUpdated: LineObject[];
|
|
981
|
+
/** Semantic Arrow objects (Phase 4). */
|
|
982
|
+
arrowsAdded: ArrowObject[];
|
|
983
|
+
/** Ids of removed arrows. */
|
|
984
|
+
arrowsRemoved: string[];
|
|
985
|
+
arrowsUpdated: ArrowObject[];
|
|
986
|
+
/** Semantic Polygon objects (Phase 4) — Triangle/Diamond/Pentagon/Hexagon/Octagon. */
|
|
987
|
+
polygonsAdded: PolygonObject[];
|
|
988
|
+
/** Ids of removed polygons. */
|
|
989
|
+
polygonsRemoved: string[];
|
|
990
|
+
polygonsUpdated: PolygonObject[];
|
|
991
|
+
/** Semantic Star objects (Phase 4). */
|
|
992
|
+
starsAdded: StarObject[];
|
|
993
|
+
/** Ids of removed stars. */
|
|
994
|
+
starsRemoved: string[];
|
|
995
|
+
starsUpdated: StarObject[];
|
|
996
|
+
/** Semantic Heart objects (Phase 4). */
|
|
997
|
+
heartsAdded: HeartObject[];
|
|
998
|
+
/** Ids of removed hearts. */
|
|
999
|
+
heartsRemoved: string[];
|
|
1000
|
+
heartsUpdated: HeartObject[];
|
|
1001
|
+
/**
|
|
1002
|
+
* The full current paint order (back to front) of every flat content
|
|
1003
|
+
* object — strokes, texts, tables, images, rectangles, ellipses, lines,
|
|
1004
|
+
* arrows, custom objects, and groups. Populated whenever `objectOrder`
|
|
1005
|
+
* actually changed:
|
|
1006
|
+
* an explicit reorder (`bringForward` etc.), or any add/remove that
|
|
1007
|
+
* touches it — a removal shifts every id after it down one rank, not
|
|
1008
|
+
* just the removed one, so renderers need this to resync everyone, not
|
|
1009
|
+
* only the ids the same change's own `*Added`/`*Removed`/`*Updated`
|
|
1010
|
+
* fields name. Notes (their own `zOffset` peel depth) and Kitchen Timers
|
|
1011
|
+
* (genuine 3D objects, not a flat layer) are intentionally not part of
|
|
1012
|
+
* this order at all.
|
|
1013
|
+
*/
|
|
1014
|
+
orderChanged: readonly string[];
|
|
734
1015
|
}
|
|
735
1016
|
type Listener = (change: DocumentChange) => void;
|
|
736
1017
|
declare class BoardDocument {
|
|
@@ -745,12 +1026,73 @@ declare class BoardDocument {
|
|
|
745
1026
|
private readonly timers;
|
|
746
1027
|
/** All Custom board object types share one map, keyed by id — the envelope is already uniform. */
|
|
747
1028
|
private readonly customObjects;
|
|
1029
|
+
private readonly rectangles;
|
|
1030
|
+
private readonly ellipses;
|
|
1031
|
+
private readonly groups;
|
|
1032
|
+
private readonly lines;
|
|
1033
|
+
private readonly arrows;
|
|
1034
|
+
private readonly polygons;
|
|
1035
|
+
private readonly stars;
|
|
1036
|
+
private readonly hearts;
|
|
748
1037
|
private readonly bboxes;
|
|
749
1038
|
private readonly listeners;
|
|
1039
|
+
/** Paint order (back to front) of every flat content object — see `DocumentChange.orderChanged`'s doc comment. */
|
|
1040
|
+
private objectOrder;
|
|
1041
|
+
private orderIndex;
|
|
750
1042
|
constructor(id: DocumentId);
|
|
1043
|
+
private reindexOrder;
|
|
1044
|
+
/** The full current paint order, back to front. */
|
|
1045
|
+
order(): readonly string[];
|
|
1046
|
+
/** This object's rank in the paint order, or -1 if it doesn't participate (unknown id, a note, or a timer). */
|
|
1047
|
+
orderRank(id: string): number;
|
|
1048
|
+
private bringForward;
|
|
1049
|
+
private sendBackward;
|
|
1050
|
+
private bringToFront;
|
|
1051
|
+
private sendToBack;
|
|
1052
|
+
/** Reorders `id` relative to its current neighbors. A no-op for an id that doesn't participate in paint order (see `orderRank`). */
|
|
1053
|
+
reorder(id: string, direction: "forward" | "backward" | "front" | "back"): void;
|
|
1054
|
+
/**
|
|
1055
|
+
* Overwrites the paint order directly — used only when loading a document
|
|
1056
|
+
* that already carries a persisted `objectOrder`; every other order
|
|
1057
|
+
* mutation goes through `reorder`/the automatic append-on-add tracking in
|
|
1058
|
+
* `emit`. Ids not present in the document are dropped; ids present in the
|
|
1059
|
+
* document but missing from `order` are appended at the back, so a
|
|
1060
|
+
* partially-stale order (e.g. from a schema migration) never silently
|
|
1061
|
+
* drops an object from paint order entirely.
|
|
1062
|
+
*/
|
|
1063
|
+
private setOrder;
|
|
751
1064
|
get(id: string): Stroke | undefined;
|
|
752
1065
|
all(): IterableIterator<Stroke>;
|
|
753
|
-
|
|
1066
|
+
/**
|
|
1067
|
+
* World-space bounds for any content object, of any type. Strokes hit
|
|
1068
|
+
* their cached-on-mutation fast path (`bboxes`, populated by
|
|
1069
|
+
* `addStrokes`/`transformStrokes` — many points, worth caching); every
|
|
1070
|
+
* other type computes on demand via `objectBounds.ts` (cheap arithmetic,
|
|
1071
|
+
* no caching needed). A group's bounds are the union of its (recursively
|
|
1072
|
+
* resolved) children — `seen` guards against a cycle in nested groups.
|
|
1073
|
+
*/
|
|
1074
|
+
bbox(id: string, seen?: Set<string>): BBox | undefined;
|
|
1075
|
+
/**
|
|
1076
|
+
* True if `id` exists and is locked, for any type — the same per-type
|
|
1077
|
+
* probe pattern as `bbox`, for interactive gestures (drag/transform) that
|
|
1078
|
+
* need to gate on lock state regardless of what's selected. Custom
|
|
1079
|
+
* objects are deliberately excluded: their `lock` field is a different
|
|
1080
|
+
* shape (`{holderId, acquiredAt}`, no display name) with no interactive
|
|
1081
|
+
* lock UI yet, matching the existing, deliberate "always unlockable,
|
|
1082
|
+
* never gates a drag" treatment already established elsewhere (e.g.
|
|
1083
|
+
* `getSelectedItemInfo`'s custom branch hardcodes `isLocked: false`).
|
|
1084
|
+
*/
|
|
1085
|
+
isLocked(id: string): boolean;
|
|
1086
|
+
/**
|
|
1087
|
+
* True if `id` exists and is hidden, for any type — the same per-type
|
|
1088
|
+
* probe pattern as {@link isLocked} (Phase 8). Custom objects are
|
|
1089
|
+
* excluded for the same reason `isLocked` excludes them: they have no
|
|
1090
|
+
* `Hideable` field at all, so "hidden" isn't a concept that applies to
|
|
1091
|
+
* them yet. Used by marquee selection (`selectTool.ts`) to keep a hidden
|
|
1092
|
+
* object out of a rubber-band selection even for object types whose own
|
|
1093
|
+
* renderer doesn't yet suppress click-based hit-testing.
|
|
1094
|
+
*/
|
|
1095
|
+
isHidden(id: string): boolean;
|
|
754
1096
|
subscribe(listener: Listener): () => void;
|
|
755
1097
|
addStrokes(strokes: Stroke[]): void;
|
|
756
1098
|
removeStrokes(ids: string[]): void;
|
|
@@ -793,6 +1135,54 @@ declare class BoardDocument {
|
|
|
793
1135
|
removeCustomObjects(ids: string[]): void;
|
|
794
1136
|
/** Replace a custom object's contents under the same id. */
|
|
795
1137
|
setCustomObject(object: CustomBoardObject): void;
|
|
1138
|
+
getRectangle(id: string): RectangleObject | undefined;
|
|
1139
|
+
allRectangles(): IterableIterator<RectangleObject>;
|
|
1140
|
+
addRectangles(rectangles: RectangleObject[]): void;
|
|
1141
|
+
removeRectangles(ids: string[]): void;
|
|
1142
|
+
/** Replace a rectangle's contents (move, resize, restyle) under the same id. */
|
|
1143
|
+
setRectangle(rect: RectangleObject): void;
|
|
1144
|
+
getEllipse(id: string): EllipseObject | undefined;
|
|
1145
|
+
allEllipses(): IterableIterator<EllipseObject>;
|
|
1146
|
+
addEllipses(ellipses: EllipseObject[]): void;
|
|
1147
|
+
removeEllipses(ids: string[]): void;
|
|
1148
|
+
/** Replace an ellipse's contents (move, resize, restyle) under the same id. */
|
|
1149
|
+
setEllipse(ellipse: EllipseObject): void;
|
|
1150
|
+
getGroup(id: string): GroupObject | undefined;
|
|
1151
|
+
allGroups(): IterableIterator<GroupObject>;
|
|
1152
|
+
addGroups(groups: GroupObject[]): void;
|
|
1153
|
+
removeGroups(ids: string[]): void;
|
|
1154
|
+
/** Replace a group's contents (its children list) under the same id. */
|
|
1155
|
+
setGroup(group: GroupObject): void;
|
|
1156
|
+
getLine(id: string): LineObject | undefined;
|
|
1157
|
+
allLines(): IterableIterator<LineObject>;
|
|
1158
|
+
addLines(lines: LineObject[]): void;
|
|
1159
|
+
removeLines(ids: string[]): void;
|
|
1160
|
+
/** Replace a line's contents (move, restyle) under the same id. */
|
|
1161
|
+
setLine(line: LineObject): void;
|
|
1162
|
+
getArrow(id: string): ArrowObject | undefined;
|
|
1163
|
+
allArrows(): IterableIterator<ArrowObject>;
|
|
1164
|
+
addArrows(arrows: ArrowObject[]): void;
|
|
1165
|
+
removeArrows(ids: string[]): void;
|
|
1166
|
+
/** Replace an arrow's contents (move, restyle, change head) under the same id. */
|
|
1167
|
+
setArrow(arrow: ArrowObject): void;
|
|
1168
|
+
getPolygon(id: string): PolygonObject | undefined;
|
|
1169
|
+
allPolygons(): IterableIterator<PolygonObject>;
|
|
1170
|
+
addPolygons(polygons: PolygonObject[]): void;
|
|
1171
|
+
removePolygons(ids: string[]): void;
|
|
1172
|
+
/** Replace a polygon's contents (move, resize, rotate, restyle) under the same id. */
|
|
1173
|
+
setPolygon(polygon: PolygonObject): void;
|
|
1174
|
+
getStar(id: string): StarObject | undefined;
|
|
1175
|
+
allStars(): IterableIterator<StarObject>;
|
|
1176
|
+
addStars(stars: StarObject[]): void;
|
|
1177
|
+
removeStars(ids: string[]): void;
|
|
1178
|
+
/** Replace a star's contents (move, resize, rotate, restyle) under the same id. */
|
|
1179
|
+
setStar(star: StarObject): void;
|
|
1180
|
+
getHeart(id: string): HeartObject | undefined;
|
|
1181
|
+
allHearts(): IterableIterator<HeartObject>;
|
|
1182
|
+
addHearts(hearts: HeartObject[]): void;
|
|
1183
|
+
removeHearts(ids: string[]): void;
|
|
1184
|
+
/** Replace a heart's contents (move, resize, rotate, restyle) under the same id. */
|
|
1185
|
+
setHeart(heart: HeartObject): void;
|
|
796
1186
|
setStrokeLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
797
1187
|
setStrokesLocked(ids: string[], locked: boolean, by?: LockHolder | null): void;
|
|
798
1188
|
setNoteLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
@@ -800,18 +1190,69 @@ declare class BoardDocument {
|
|
|
800
1190
|
setTableLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
801
1191
|
setImageLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
802
1192
|
setTimerLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
1193
|
+
setRectangleLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
1194
|
+
setEllipseLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
1195
|
+
setGroupLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
1196
|
+
setLineLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
1197
|
+
setArrowLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
1198
|
+
setPolygonLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
1199
|
+
setStarLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
1200
|
+
setHeartLocked(id: string, locked: boolean, by?: LockHolder | null): void;
|
|
803
1201
|
/** Replace all content (initial load). Does not touch `version`. */
|
|
804
|
-
replaceAll(strokes: Stroke[], notes: StickyNote[], texts: TextBlock[], tables?: TableBlock[], images?: ImageBlock[], timers?: KitchenTimer[], customObjects?: CustomBoardObject[]
|
|
1202
|
+
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[],
|
|
1203
|
+
/** Persisted paint order; absent for a document saved before Phase 3, in which case one is synthesized (see `ORDERED_ADDED_FIELDS`'s doc comment). */
|
|
1204
|
+
objectOrder?: readonly string[]): void;
|
|
805
1205
|
/** Apply incremental real-time change received from a remote collaborator over WebSocket. */
|
|
806
1206
|
applyRemoteChange(change: Partial<DocumentChange>): void;
|
|
807
1207
|
toJSON(): SerializedDocument;
|
|
1208
|
+
static deserializeGroups(data: SerializedDocument): GroupObject[];
|
|
1209
|
+
static deserializeLines(data: SerializedDocument): LineObject[];
|
|
1210
|
+
static deserializeArrows(data: SerializedDocument): ArrowObject[];
|
|
1211
|
+
static deserializePolygons(data: SerializedDocument): PolygonObject[];
|
|
1212
|
+
static deserializeStars(data: SerializedDocument): StarObject[];
|
|
1213
|
+
static deserializeHearts(data: SerializedDocument): HeartObject[];
|
|
808
1214
|
static deserializeCustomObjects(data: SerializedDocument): CustomBoardObject[];
|
|
1215
|
+
static deserializeRectangles(data: SerializedDocument): RectangleObject[];
|
|
1216
|
+
static deserializeEllipses(data: SerializedDocument): EllipseObject[];
|
|
809
1217
|
static deserializeImages(data: SerializedDocument): ImageBlock[];
|
|
810
1218
|
static deserializeTimers(data: SerializedDocument): KitchenTimer[];
|
|
811
1219
|
static deserializeNotes(data: SerializedDocument): StickyNote[];
|
|
812
1220
|
static deserializeTexts(data: SerializedDocument): TextBlock[];
|
|
813
1221
|
static deserializeTables(data: SerializedDocument): TableBlock[];
|
|
814
|
-
|
|
1222
|
+
/**
|
|
1223
|
+
* `onSkip` (Phase 9) replaces an unconditional `console.error` — `core`
|
|
1224
|
+
* must never do raw console I/O (no dev-gate, no way for a Host to
|
|
1225
|
+
* suppress or redirect it), so a skipped stroke is now reported only if
|
|
1226
|
+
* the caller asks for it, via whatever diagnostic channel it already
|
|
1227
|
+
* has (e.g. `controller-internal.ts` routes this into the same typed
|
|
1228
|
+
* `"error"` event every other diagnostic already uses). Silent by
|
|
1229
|
+
* default, matching how every other `deserialize*` method here already
|
|
1230
|
+
* behaves (no diagnostics at all).
|
|
1231
|
+
*/
|
|
1232
|
+
static deserializeStrokes(data: SerializedDocument, onSkip?: (id: string, cause: unknown) => void): Stroke[];
|
|
1233
|
+
/**
|
|
1234
|
+
* Every mutation funnels through here, so paint-order tracking lives in
|
|
1235
|
+
* exactly one place rather than at every individual add/remove call site
|
|
1236
|
+
* (18 of them, times `replaceAll`/`applyRemoteChange`) — new ids are
|
|
1237
|
+
* appended to the back (front-most) of `objectOrder`, removed ids are
|
|
1238
|
+
* spliced out. An explicit reorder (`reorder`/`setOrder`) updates
|
|
1239
|
+
* `objectOrder` itself before calling this, so this step is a no-op for
|
|
1240
|
+
* ids already tracked (idempotent by construction: `orderIndex.has` gates
|
|
1241
|
+
* every append).
|
|
1242
|
+
*
|
|
1243
|
+
* `orderChanged` (Phase 9) reports exactly the ids whose rank actually
|
|
1244
|
+
* changed, using `orderIndex` throughout instead of `indexOf` — a pure
|
|
1245
|
+
* append never shifts any existing id's rank (new ids land at the tail,
|
|
1246
|
+
* already covered by this same change's own `Added` field, so
|
|
1247
|
+
* `orderChanged` stays unset), while a removal shifts every id at-or-
|
|
1248
|
+
* after the lowest removed rank down by one, computed in a single O(n)
|
|
1249
|
+
* filter pass (not one `indexOf`+`splice` per removed id) regardless of
|
|
1250
|
+
* how many ids this one change removes. Every renderer's `onChange` now
|
|
1251
|
+
* looks up only the ids actually in `orderChanged` instead of walking
|
|
1252
|
+
* its entire mesh map on any order-touching change — a broad, unfiltered
|
|
1253
|
+
* `orderChanged` here would silently defeat that fix, not just waste
|
|
1254
|
+
* cycles here.
|
|
1255
|
+
*/
|
|
815
1256
|
private emit;
|
|
816
1257
|
}
|
|
817
1258
|
|
|
@@ -847,6 +1288,21 @@ interface Command {
|
|
|
847
1288
|
apply(doc: BoardDocument): void;
|
|
848
1289
|
revert(doc: BoardDocument): void;
|
|
849
1290
|
}
|
|
1291
|
+
/**
|
|
1292
|
+
* Several commands applied/reverted together as one undo step (Phase 3
|
|
1293
|
+
* consolidation — this exact class used to be hand-duplicated as a private
|
|
1294
|
+
* `CommandBatch` in `controller-internal.ts` and an exported
|
|
1295
|
+
* `ExtensionCommandBatch` in `interaction/tools/customTool.ts`; both now
|
|
1296
|
+
* import this one instead). Revert runs in reverse order, so a batch that
|
|
1297
|
+
* depends on ordering (e.g. add-then-reference) undoes cleanly.
|
|
1298
|
+
*/
|
|
1299
|
+
declare class CommandBatch implements Command {
|
|
1300
|
+
readonly label: string;
|
|
1301
|
+
private readonly commands;
|
|
1302
|
+
constructor(label: string, commands: readonly Command[]);
|
|
1303
|
+
apply(doc: BoardDocument): void;
|
|
1304
|
+
revert(doc: BoardDocument): void;
|
|
1305
|
+
}
|
|
850
1306
|
/** How a command reached the document — undo/redo are audited distinctly. */
|
|
851
1307
|
type CommandKind = "do" | "undo" | "redo";
|
|
852
1308
|
declare class History {
|
|
@@ -911,6 +1367,40 @@ declare class TransformCommand implements Command {
|
|
|
911
1367
|
revert(doc: BoardDocument): void;
|
|
912
1368
|
private compose;
|
|
913
1369
|
}
|
|
1370
|
+
/**
|
|
1371
|
+
* One move gesture over a mixed-type selection (Phase 3) — the general
|
|
1372
|
+
* successor to `TransformCommand` for translation. `TransformCommand`
|
|
1373
|
+
* itself stays as-is (still used for scale/rotate, which remain stroke-only
|
|
1374
|
+
* this phase — see `interaction/tools/selectTool.ts`'s `TransformState`):
|
|
1375
|
+
* its own per-id `doc.get(id)` check already no-ops safely for any id that
|
|
1376
|
+
* isn't a stroke, so it doesn't need touching for that narrower case.
|
|
1377
|
+
*
|
|
1378
|
+
* `delta` here is always a pure translation (never scale/rotate), so
|
|
1379
|
+
* `apply(delta, point)` is a safe, uniform way to move every position-only
|
|
1380
|
+
* type's `x`/`y` — translating commutes trivially regardless of a shape's
|
|
1381
|
+
* own rotation. Matrix-carrying types (stroke, Custom) instead compose
|
|
1382
|
+
* `delta` onto their existing matrix/transform, matching `TransformCommand`.
|
|
1383
|
+
*
|
|
1384
|
+
* A group id in `ids` is expanded into its (recursively resolved, cycle-
|
|
1385
|
+
* safe) children every time `compose` runs — deterministic, since group
|
|
1386
|
+
* membership never changes mid-command — so moving a selected group means
|
|
1387
|
+
* moving every one of its members by the same delta. This expansion is
|
|
1388
|
+
* `TransformObjectsCommand`'s own responsibility precisely so a caller that
|
|
1389
|
+
* doesn't itself expand groups (e.g. `ScrawlEngine.nudgeSelection`) still
|
|
1390
|
+
* gets correct behavior; `interaction/tools/selectTool.ts`'s `TransformState`
|
|
1391
|
+
* separately expands for its own reason (live per-child drag preview), which
|
|
1392
|
+
* makes this a no-op re-expansion for that caller, not a conflict.
|
|
1393
|
+
*/
|
|
1394
|
+
declare class TransformObjectsCommand implements Command {
|
|
1395
|
+
private readonly ids;
|
|
1396
|
+
private readonly delta;
|
|
1397
|
+
readonly label = "move selection";
|
|
1398
|
+
private readonly inverse;
|
|
1399
|
+
constructor(ids: readonly string[], delta: Mat2x3);
|
|
1400
|
+
apply(doc: BoardDocument): void;
|
|
1401
|
+
revert(doc: BoardDocument): void;
|
|
1402
|
+
private compose;
|
|
1403
|
+
}
|
|
914
1404
|
declare class AddNoteCommand implements Command {
|
|
915
1405
|
readonly label = "add note";
|
|
916
1406
|
private readonly note;
|
|
@@ -1032,8 +1522,213 @@ declare class DeleteTimerCommand implements Command {
|
|
|
1032
1522
|
apply(doc: BoardDocument): void;
|
|
1033
1523
|
revert(doc: BoardDocument): void;
|
|
1034
1524
|
}
|
|
1525
|
+
/** Add/update/delete for semantic Rectangle objects (Phase 2). */
|
|
1526
|
+
declare class AddRectangleCommand implements Command {
|
|
1527
|
+
readonly label = "add rectangle";
|
|
1528
|
+
private readonly rect;
|
|
1529
|
+
constructor(rect: RectangleObject);
|
|
1530
|
+
apply(doc: BoardDocument): void;
|
|
1531
|
+
revert(doc: BoardDocument): void;
|
|
1532
|
+
}
|
|
1533
|
+
declare class UpdateRectangleCommand implements Command {
|
|
1534
|
+
readonly label = "update rectangle";
|
|
1535
|
+
private readonly before;
|
|
1536
|
+
private readonly after;
|
|
1537
|
+
constructor(before: RectangleObject, after: RectangleObject);
|
|
1538
|
+
apply(doc: BoardDocument): void;
|
|
1539
|
+
revert(doc: BoardDocument): void;
|
|
1540
|
+
}
|
|
1541
|
+
declare class DeleteRectangleCommand implements Command {
|
|
1542
|
+
readonly label = "delete rectangle";
|
|
1543
|
+
private readonly rect;
|
|
1544
|
+
constructor(rect: RectangleObject);
|
|
1545
|
+
apply(doc: BoardDocument): void;
|
|
1546
|
+
revert(doc: BoardDocument): void;
|
|
1547
|
+
}
|
|
1548
|
+
/** Add/update/delete for semantic Ellipse objects (Phase 2). */
|
|
1549
|
+
declare class AddEllipseCommand implements Command {
|
|
1550
|
+
readonly label = "add ellipse";
|
|
1551
|
+
private readonly ellipse;
|
|
1552
|
+
constructor(ellipse: EllipseObject);
|
|
1553
|
+
apply(doc: BoardDocument): void;
|
|
1554
|
+
revert(doc: BoardDocument): void;
|
|
1555
|
+
}
|
|
1556
|
+
declare class UpdateEllipseCommand implements Command {
|
|
1557
|
+
readonly label = "update ellipse";
|
|
1558
|
+
private readonly before;
|
|
1559
|
+
private readonly after;
|
|
1560
|
+
constructor(before: EllipseObject, after: EllipseObject);
|
|
1561
|
+
apply(doc: BoardDocument): void;
|
|
1562
|
+
revert(doc: BoardDocument): void;
|
|
1563
|
+
}
|
|
1564
|
+
declare class DeleteEllipseCommand implements Command {
|
|
1565
|
+
readonly label = "delete ellipse";
|
|
1566
|
+
private readonly ellipse;
|
|
1567
|
+
constructor(ellipse: EllipseObject);
|
|
1568
|
+
apply(doc: BoardDocument): void;
|
|
1569
|
+
revert(doc: BoardDocument): void;
|
|
1570
|
+
}
|
|
1571
|
+
/** Add/update/delete for semantic Line objects (Phase 4). */
|
|
1572
|
+
declare class AddLineCommand implements Command {
|
|
1573
|
+
readonly label = "add line";
|
|
1574
|
+
private readonly line;
|
|
1575
|
+
constructor(line: LineObject);
|
|
1576
|
+
apply(doc: BoardDocument): void;
|
|
1577
|
+
revert(doc: BoardDocument): void;
|
|
1578
|
+
}
|
|
1579
|
+
declare class UpdateLineCommand implements Command {
|
|
1580
|
+
readonly label = "update line";
|
|
1581
|
+
private readonly before;
|
|
1582
|
+
private readonly after;
|
|
1583
|
+
constructor(before: LineObject, after: LineObject);
|
|
1584
|
+
apply(doc: BoardDocument): void;
|
|
1585
|
+
revert(doc: BoardDocument): void;
|
|
1586
|
+
}
|
|
1587
|
+
declare class DeleteLineCommand implements Command {
|
|
1588
|
+
readonly label = "delete line";
|
|
1589
|
+
private readonly line;
|
|
1590
|
+
constructor(line: LineObject);
|
|
1591
|
+
apply(doc: BoardDocument): void;
|
|
1592
|
+
revert(doc: BoardDocument): void;
|
|
1593
|
+
}
|
|
1594
|
+
/** Add/update/delete for semantic Arrow objects (Phase 4). */
|
|
1595
|
+
declare class AddArrowCommand implements Command {
|
|
1596
|
+
readonly label = "add arrow";
|
|
1597
|
+
private readonly arrow;
|
|
1598
|
+
constructor(arrow: ArrowObject);
|
|
1599
|
+
apply(doc: BoardDocument): void;
|
|
1600
|
+
revert(doc: BoardDocument): void;
|
|
1601
|
+
}
|
|
1602
|
+
declare class UpdateArrowCommand implements Command {
|
|
1603
|
+
readonly label = "update arrow";
|
|
1604
|
+
private readonly before;
|
|
1605
|
+
private readonly after;
|
|
1606
|
+
constructor(before: ArrowObject, after: ArrowObject);
|
|
1607
|
+
apply(doc: BoardDocument): void;
|
|
1608
|
+
revert(doc: BoardDocument): void;
|
|
1609
|
+
}
|
|
1610
|
+
declare class DeleteArrowCommand implements Command {
|
|
1611
|
+
readonly label = "delete arrow";
|
|
1612
|
+
private readonly arrow;
|
|
1613
|
+
constructor(arrow: ArrowObject);
|
|
1614
|
+
apply(doc: BoardDocument): void;
|
|
1615
|
+
revert(doc: BoardDocument): void;
|
|
1616
|
+
}
|
|
1617
|
+
/** Add/update/delete for semantic Polygon objects (Phase 4) — Triangle/Diamond/Pentagon/Hexagon/Octagon. */
|
|
1618
|
+
declare class AddPolygonCommand implements Command {
|
|
1619
|
+
readonly label = "add polygon";
|
|
1620
|
+
private readonly polygon;
|
|
1621
|
+
constructor(polygon: PolygonObject);
|
|
1622
|
+
apply(doc: BoardDocument): void;
|
|
1623
|
+
revert(doc: BoardDocument): void;
|
|
1624
|
+
}
|
|
1625
|
+
declare class UpdatePolygonCommand implements Command {
|
|
1626
|
+
readonly label = "update polygon";
|
|
1627
|
+
private readonly before;
|
|
1628
|
+
private readonly after;
|
|
1629
|
+
constructor(before: PolygonObject, after: PolygonObject);
|
|
1630
|
+
apply(doc: BoardDocument): void;
|
|
1631
|
+
revert(doc: BoardDocument): void;
|
|
1632
|
+
}
|
|
1633
|
+
declare class DeletePolygonCommand implements Command {
|
|
1634
|
+
readonly label = "delete polygon";
|
|
1635
|
+
private readonly polygon;
|
|
1636
|
+
constructor(polygon: PolygonObject);
|
|
1637
|
+
apply(doc: BoardDocument): void;
|
|
1638
|
+
revert(doc: BoardDocument): void;
|
|
1639
|
+
}
|
|
1640
|
+
/** Add/update/delete for semantic Star objects (Phase 4). */
|
|
1641
|
+
declare class AddStarCommand implements Command {
|
|
1642
|
+
readonly label = "add star";
|
|
1643
|
+
private readonly star;
|
|
1644
|
+
constructor(star: StarObject);
|
|
1645
|
+
apply(doc: BoardDocument): void;
|
|
1646
|
+
revert(doc: BoardDocument): void;
|
|
1647
|
+
}
|
|
1648
|
+
declare class UpdateStarCommand implements Command {
|
|
1649
|
+
readonly label = "update star";
|
|
1650
|
+
private readonly before;
|
|
1651
|
+
private readonly after;
|
|
1652
|
+
constructor(before: StarObject, after: StarObject);
|
|
1653
|
+
apply(doc: BoardDocument): void;
|
|
1654
|
+
revert(doc: BoardDocument): void;
|
|
1655
|
+
}
|
|
1656
|
+
declare class DeleteStarCommand implements Command {
|
|
1657
|
+
readonly label = "delete star";
|
|
1658
|
+
private readonly star;
|
|
1659
|
+
constructor(star: StarObject);
|
|
1660
|
+
apply(doc: BoardDocument): void;
|
|
1661
|
+
revert(doc: BoardDocument): void;
|
|
1662
|
+
}
|
|
1663
|
+
/** Add/update/delete for semantic Heart objects (Phase 4). */
|
|
1664
|
+
declare class AddHeartCommand implements Command {
|
|
1665
|
+
readonly label = "add heart";
|
|
1666
|
+
private readonly heart;
|
|
1667
|
+
constructor(heart: HeartObject);
|
|
1668
|
+
apply(doc: BoardDocument): void;
|
|
1669
|
+
revert(doc: BoardDocument): void;
|
|
1670
|
+
}
|
|
1671
|
+
declare class UpdateHeartCommand implements Command {
|
|
1672
|
+
readonly label = "update heart";
|
|
1673
|
+
private readonly before;
|
|
1674
|
+
private readonly after;
|
|
1675
|
+
constructor(before: HeartObject, after: HeartObject);
|
|
1676
|
+
apply(doc: BoardDocument): void;
|
|
1677
|
+
revert(doc: BoardDocument): void;
|
|
1678
|
+
}
|
|
1679
|
+
declare class DeleteHeartCommand implements Command {
|
|
1680
|
+
readonly label = "delete heart";
|
|
1681
|
+
private readonly heart;
|
|
1682
|
+
constructor(heart: HeartObject);
|
|
1683
|
+
apply(doc: BoardDocument): void;
|
|
1684
|
+
revert(doc: BoardDocument): void;
|
|
1685
|
+
}
|
|
1686
|
+
/** Add/update/delete for Groups (Phase 3). */
|
|
1687
|
+
declare class AddGroupCommand implements Command {
|
|
1688
|
+
readonly label = "group";
|
|
1689
|
+
private readonly group;
|
|
1690
|
+
constructor(group: GroupObject);
|
|
1691
|
+
apply(doc: BoardDocument): void;
|
|
1692
|
+
revert(doc: BoardDocument): void;
|
|
1693
|
+
}
|
|
1694
|
+
declare class UpdateGroupCommand implements Command {
|
|
1695
|
+
readonly label = "update group";
|
|
1696
|
+
private readonly before;
|
|
1697
|
+
private readonly after;
|
|
1698
|
+
constructor(before: GroupObject, after: GroupObject);
|
|
1699
|
+
apply(doc: BoardDocument): void;
|
|
1700
|
+
revert(doc: BoardDocument): void;
|
|
1701
|
+
}
|
|
1702
|
+
declare class DeleteGroupCommand implements Command {
|
|
1703
|
+
readonly label = "ungroup";
|
|
1704
|
+
private readonly group;
|
|
1705
|
+
constructor(group: GroupObject);
|
|
1706
|
+
apply(doc: BoardDocument): void;
|
|
1707
|
+
revert(doc: BoardDocument): void;
|
|
1708
|
+
}
|
|
1709
|
+
type ReorderDirection = "forward" | "backward" | "front" | "back";
|
|
1710
|
+
/**
|
|
1711
|
+
* Bring-forward / send-backward / bring-to-front / send-to-back (Phase 3) —
|
|
1712
|
+
* one command family covering all four directions rather than four
|
|
1713
|
+
* near-identical classes, since the only difference between them is which
|
|
1714
|
+
* `BoardDocument.reorder` direction to replay. Captures the full paint order
|
|
1715
|
+
* on first `apply` rather than in the constructor — `BoardDocument.order()`
|
|
1716
|
+
* needs the doc, which a `Command` only ever receives via `apply`/`revert` —
|
|
1717
|
+
* so `revert` can restore it exactly; redo re-runs the same `reorder` call,
|
|
1718
|
+
* which is deterministic because `revert` always restores the identical
|
|
1719
|
+
* starting order first.
|
|
1720
|
+
*/
|
|
1721
|
+
declare class ReorderObjectCommand implements Command {
|
|
1722
|
+
private readonly id;
|
|
1723
|
+
private readonly direction;
|
|
1724
|
+
readonly label: string;
|
|
1725
|
+
private before;
|
|
1726
|
+
constructor(id: string, direction: ReorderDirection);
|
|
1727
|
+
apply(doc: BoardDocument): void;
|
|
1728
|
+
revert(doc: BoardDocument): void;
|
|
1729
|
+
}
|
|
1035
1730
|
interface LockTarget {
|
|
1036
|
-
type: "stroke" | "note" | "text" | "table" | "image" | "timer";
|
|
1731
|
+
type: "stroke" | "note" | "text" | "table" | "image" | "timer" | "rectangle" | "ellipse" | "group" | "line" | "arrow" | "polygon" | "star" | "heart";
|
|
1037
1732
|
id: string;
|
|
1038
1733
|
locked: boolean;
|
|
1039
1734
|
lockedBy?: string;
|
|
@@ -1050,7 +1745,7 @@ declare class LockItemsCommand implements Command {
|
|
|
1050
1745
|
private applyLock;
|
|
1051
1746
|
}
|
|
1052
1747
|
|
|
1053
|
-
type OpCollection = "strokes" | "notes" | "textBlocks" | "tables" | "images" | "timers" | "customObjects";
|
|
1748
|
+
type OpCollection = "strokes" | "notes" | "textBlocks" | "tables" | "images" | "timers" | "rectangles" | "ellipses" | "groups" | "lines" | "arrows" | "polygons" | "stars" | "hearts" | "customObjects";
|
|
1054
1749
|
type Op = {
|
|
1055
1750
|
kind: "upsert";
|
|
1056
1751
|
collection: OpCollection;
|
|
@@ -1094,10 +1789,10 @@ declare function ribbonEdges(points: StrokePoint[], baseWidth: number, handDrawn
|
|
|
1094
1789
|
declare class SpatialIndex {
|
|
1095
1790
|
private readonly doc;
|
|
1096
1791
|
private readonly cells;
|
|
1097
|
-
private readonly
|
|
1792
|
+
private readonly objectCells;
|
|
1098
1793
|
private readonly unsubscribe;
|
|
1099
1794
|
constructor(doc: BoardDocument);
|
|
1100
|
-
/** Ids of
|
|
1795
|
+
/** Ids of content objects (any type except groups) whose bbox may overlap the query rect. */
|
|
1101
1796
|
query(minX: number, minY: number, maxX: number, maxY: number): Set<string>;
|
|
1102
1797
|
dispose(): void;
|
|
1103
1798
|
private insert;
|
|
@@ -1252,7 +1947,7 @@ interface BoardSnapshot {
|
|
|
1252
1947
|
* here, matching the engine's own internal selection-badge behavior.
|
|
1253
1948
|
*/
|
|
1254
1949
|
interface FocusedItem {
|
|
1255
|
-
readonly type: "stroke" | "note" | "text" | "table" | "image" | "timer" | "custom";
|
|
1950
|
+
readonly type: "stroke" | "note" | "text" | "table" | "image" | "timer" | "rectangle" | "ellipse" | "group" | "line" | "arrow" | "polygon" | "star" | "heart" | "custom";
|
|
1256
1951
|
readonly id: string;
|
|
1257
1952
|
readonly locked: boolean;
|
|
1258
1953
|
readonly lockedBy?: string;
|
|
@@ -1333,6 +2028,8 @@ interface BoardEventMap {
|
|
|
1333
2028
|
"asset-diagnostic": AssetDiagnostic;
|
|
1334
2029
|
/** A batch of Ops was reconciled (not applied as-sent) by the persistence adapter (ticket #24). */
|
|
1335
2030
|
"persistence-diagnostic": PersistenceDiagnostic;
|
|
2031
|
+
/** 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. */
|
|
2032
|
+
"collaboration-ops-acknowledged": CollaborationAckDiagnostic;
|
|
1336
2033
|
audit: unknown;
|
|
1337
2034
|
error: BoardControllerError;
|
|
1338
2035
|
disposed: undefined;
|
|
@@ -1396,9 +2093,12 @@ interface PresenceView {
|
|
|
1396
2093
|
readonly height: number;
|
|
1397
2094
|
}
|
|
1398
2095
|
/**
|
|
1399
|
-
* A
|
|
1400
|
-
*
|
|
1401
|
-
*
|
|
2096
|
+
* A collaborator, synced in for cursor/roster rendering only. Presence is
|
|
2097
|
+
* ephemeral — it never touches the Document, Ops, undo/redo, or persistence
|
|
2098
|
+
* (ADR 0006/0007). Two ways a roster gets populated (`presence.sync`
|
|
2099
|
+
* directly, or a `CollaborationAdapter`'s optional presence channel —
|
|
2100
|
+
* Phase 6, ADR 0015) both feed the exact same read/query capability below;
|
|
2101
|
+
* a Host picks one, not both, for a given controller.
|
|
1402
2102
|
*/
|
|
1403
2103
|
interface PresenceUser {
|
|
1404
2104
|
readonly id: string;
|
|
@@ -1407,6 +2107,14 @@ interface PresenceUser {
|
|
|
1407
2107
|
readonly tool?: string;
|
|
1408
2108
|
readonly cursor?: PresenceCursor;
|
|
1409
2109
|
readonly view?: PresenceView;
|
|
2110
|
+
/** Host-supplied extras (avatar URL, role, etc.) — opaque to Scrawl, never interpreted. */
|
|
2111
|
+
readonly metadata?: Record<string, unknown>;
|
|
2112
|
+
}
|
|
2113
|
+
/** This client's own local presence, published via `presence.broadcast()` (Phase 6). */
|
|
2114
|
+
interface LocalPresence {
|
|
2115
|
+
readonly cursor?: PresenceCursor | null;
|
|
2116
|
+
readonly view?: PresenceView | null;
|
|
2117
|
+
readonly tool?: string;
|
|
1410
2118
|
}
|
|
1411
2119
|
/**
|
|
1412
2120
|
* The Custom arm wraps `CustomBoardObject` under the same `type` discriminant
|
|
@@ -1434,6 +2142,22 @@ type BoardObject = ({
|
|
|
1434
2142
|
} & ImageBlock) | ({
|
|
1435
2143
|
type: "timer";
|
|
1436
2144
|
} & KitchenTimer) | ({
|
|
2145
|
+
type: "rectangle";
|
|
2146
|
+
} & RectangleObject) | ({
|
|
2147
|
+
type: "ellipse";
|
|
2148
|
+
} & EllipseObject) | ({
|
|
2149
|
+
type: "group";
|
|
2150
|
+
} & GroupObject) | ({
|
|
2151
|
+
type: "line";
|
|
2152
|
+
} & LineObject) | ({
|
|
2153
|
+
type: "arrow";
|
|
2154
|
+
} & ArrowObject) | ({
|
|
2155
|
+
type: "polygon";
|
|
2156
|
+
} & PolygonObject) | ({
|
|
2157
|
+
type: "star";
|
|
2158
|
+
} & StarObject) | ({
|
|
2159
|
+
type: "heart";
|
|
2160
|
+
} & HeartObject) | ({
|
|
1437
2161
|
type: "custom";
|
|
1438
2162
|
customType: ObjectType;
|
|
1439
2163
|
} & Omit<CustomBoardObject, "type">);
|
|
@@ -1462,6 +2186,30 @@ type BoardObjectInput = {
|
|
|
1462
2186
|
type: "timer";
|
|
1463
2187
|
id?: string;
|
|
1464
2188
|
} & Omit<KitchenTimer, "id">) | ({
|
|
2189
|
+
type: "rectangle";
|
|
2190
|
+
id?: string;
|
|
2191
|
+
} & Omit<RectangleObject, "id">) | ({
|
|
2192
|
+
type: "ellipse";
|
|
2193
|
+
id?: string;
|
|
2194
|
+
} & Omit<EllipseObject, "id">) | ({
|
|
2195
|
+
type: "group";
|
|
2196
|
+
id?: string;
|
|
2197
|
+
} & Omit<GroupObject, "id">) | ({
|
|
2198
|
+
type: "line";
|
|
2199
|
+
id?: string;
|
|
2200
|
+
} & Omit<LineObject, "id">) | ({
|
|
2201
|
+
type: "arrow";
|
|
2202
|
+
id?: string;
|
|
2203
|
+
} & Omit<ArrowObject, "id">) | ({
|
|
2204
|
+
type: "polygon";
|
|
2205
|
+
id?: string;
|
|
2206
|
+
} & Omit<PolygonObject, "id">) | ({
|
|
2207
|
+
type: "star";
|
|
2208
|
+
id?: string;
|
|
2209
|
+
} & Omit<StarObject, "id">) | ({
|
|
2210
|
+
type: "heart";
|
|
2211
|
+
id?: string;
|
|
2212
|
+
} & Omit<HeartObject, "id">) | ({
|
|
1465
2213
|
type: "custom";
|
|
1466
2214
|
id?: string;
|
|
1467
2215
|
customType: ObjectType;
|
|
@@ -1492,10 +2240,34 @@ type LoadResult = {
|
|
|
1492
2240
|
} | {
|
|
1493
2241
|
state: "missing";
|
|
1494
2242
|
};
|
|
2243
|
+
/**
|
|
2244
|
+
* Result of a whole-document `PersistenceAdapter.replace()` call (ADR 0006:
|
|
2245
|
+
* "Whole-document writes survive only for create, clear-board and import,
|
|
2246
|
+
* where replacing everything is the actual intent"). Revision-gated, unlike
|
|
2247
|
+
* `applyOps` — `conflict` means `baseRevision` was stale (someone else's
|
|
2248
|
+
* write landed first); the caller must reload and never overwrites blind.
|
|
2249
|
+
*/
|
|
2250
|
+
type ReplaceResult = {
|
|
2251
|
+
state: "applied";
|
|
2252
|
+
revision: string;
|
|
2253
|
+
} | {
|
|
2254
|
+
state: "conflict";
|
|
2255
|
+
currentRevision: string;
|
|
2256
|
+
};
|
|
2257
|
+
/**
|
|
2258
|
+
* The one sanctioned seam for persisting a Board's Document to a Host's own
|
|
2259
|
+
* storage — implement this against a database, an HTTP API, IndexedDB
|
|
2260
|
+
* (see `@scrawl-board/board/local`'s `createIndexedDBPersistence`), or
|
|
2261
|
+
* anything else. `load()` fetches the current state on connect; `applyOps()`
|
|
2262
|
+
* streams incremental Ops as edits happen; `replace()` is only for
|
|
2263
|
+
* whole-document writes (create, clear-board, import — see ADR 0006) and is
|
|
2264
|
+
* revision-gated so a stale write never silently clobbers a newer one.
|
|
2265
|
+
* Passed via `createBoardController({ adapters: { persistence } })`.
|
|
2266
|
+
*/
|
|
1495
2267
|
interface PersistenceAdapter {
|
|
1496
2268
|
load(context: DocumentContext): Promise<LoadResult>;
|
|
1497
2269
|
applyOps(context: DocumentContext, ops: readonly ControllerOp[]): Promise<ApplyOpsResult>;
|
|
1498
|
-
replace(context: DocumentContext, document: CurrentSerializedDocument, baseRevision: string): Promise<
|
|
2270
|
+
replace(context: DocumentContext, document: CurrentSerializedDocument, baseRevision: string): Promise<ReplaceResult>;
|
|
1499
2271
|
}
|
|
1500
2272
|
/**
|
|
1501
2273
|
* `"reconcile"` (ticket #24) means the server authoritatively resolved the
|
|
@@ -1519,14 +2291,59 @@ interface PersistenceDiagnostic {
|
|
|
1519
2291
|
rejectedOpIds: readonly string[];
|
|
1520
2292
|
revision: string;
|
|
1521
2293
|
}
|
|
2294
|
+
/** Emitted as `"collaboration-ops-acknowledged"` (Phase 7) — the server has confirmed receipt of these op ids on the live pipe. */
|
|
2295
|
+
interface CollaborationAckDiagnostic {
|
|
2296
|
+
opIds: readonly string[];
|
|
2297
|
+
}
|
|
1522
2298
|
interface ControllerOp {
|
|
1523
2299
|
id: string;
|
|
1524
2300
|
schemaVersion: 1;
|
|
1525
2301
|
kind: "upsert" | "restore" | "remove";
|
|
1526
|
-
objectType: "stroke" | "note" | "text" | "table" | "image" | "timer" | "custom"
|
|
2302
|
+
objectType: "stroke" | "note" | "text" | "table" | "image" | "timer" | "rectangle" | "ellipse" | "group" | "line" | "arrow" | "polygon" | "star" | "heart" | "custom"
|
|
2303
|
+
/**
|
|
2304
|
+
* A whole-document paint-order sync (Phase 3), not a per-object type —
|
|
2305
|
+
* `objectId` is always the fixed sentinel `"order"` and `payload` is
|
|
2306
|
+
* `{ order: string[] }`. The only `objectType` with no matching
|
|
2307
|
+
* `BoardObject`/document collection; kept in this same union (rather
|
|
2308
|
+
* than a separate wire message) so it flows through the existing
|
|
2309
|
+
* `PersistenceAdapter`/`CollaborationAdapter` opaquely, unchanged.
|
|
2310
|
+
*/
|
|
2311
|
+
| "order";
|
|
1527
2312
|
objectId: string;
|
|
1528
2313
|
payload?: unknown;
|
|
2314
|
+
/**
|
|
2315
|
+
* This op's position in its own originating client's local sequence
|
|
2316
|
+
* (Phase 7) — 1, 2, 3, ... per controller instance, distinct from `id`
|
|
2317
|
+
* (an opaque, globally-unique identifier used for dedup/ack, not
|
|
2318
|
+
* ordering) and from a server's own authoritative ordering (e.g.
|
|
2319
|
+
* `referenceCollaborationServer.ts`'s per-room `version` counter).
|
|
2320
|
+
* Present on every op this SDK originates locally; a remote peer's op
|
|
2321
|
+
* carries whatever its own origin set, unchanged — never renumbered in
|
|
2322
|
+
* transit. Absent on an op minted by decoding the legacy wire envelope
|
|
2323
|
+
* (`scrawlOpEnvelope.ts`), which predates this field and has no
|
|
2324
|
+
* per-client sequence concept of its own.
|
|
2325
|
+
*/
|
|
2326
|
+
clientSequence?: number;
|
|
2327
|
+
/**
|
|
2328
|
+
* The `CollaboratorIdentity.id` of this op's originating client (Phase
|
|
2329
|
+
* 7) — set for every op this SDK originates locally when `identity` is
|
|
2330
|
+
* configured, omitted entirely otherwise (never sent as `undefined`).
|
|
2331
|
+
* The explicit foundation for a future per-author undo filter (a local
|
|
2332
|
+
* user's own undo should only ever touch their own ops) — no undo-stack
|
|
2333
|
+
* behavior itself changes this phase.
|
|
2334
|
+
*/
|
|
2335
|
+
clientId?: string;
|
|
1529
2336
|
}
|
|
2337
|
+
/**
|
|
2338
|
+
* The one sanctioned seam for real-time multiplayer — implement this against
|
|
2339
|
+
* a Host's own collaboration backend (WebSocket relay, CRDT server, etc.).
|
|
2340
|
+
* `connect()` is called once per controller with the local user's
|
|
2341
|
+
* `identity` and a `receive` callback the adapter invokes with incoming
|
|
2342
|
+
* Ops, presence updates, acks, and connection status; it resolves with a
|
|
2343
|
+
* `CollaborationSession` the controller uses to send local Ops and presence
|
|
2344
|
+
* back out. Passed via `createBoardController({ adapters: { collaboration } })`;
|
|
2345
|
+
* omit it entirely to run single-player.
|
|
2346
|
+
*/
|
|
1530
2347
|
interface CollaborationAdapter {
|
|
1531
2348
|
connect(options: DocumentContext & {
|
|
1532
2349
|
identity: CollaboratorIdentity;
|
|
@@ -1537,14 +2354,72 @@ interface CollaboratorIdentity {
|
|
|
1537
2354
|
id: string;
|
|
1538
2355
|
name: string;
|
|
1539
2356
|
color?: string;
|
|
2357
|
+
/** Host-supplied extras (avatar URL, role, etc.) — opaque to Scrawl, forwarded into any resulting `PresenceUser` unread and never interpreted. */
|
|
2358
|
+
metadata?: Record<string, unknown>;
|
|
1540
2359
|
}
|
|
1541
2360
|
interface CollaborationReceiver {
|
|
1542
2361
|
ops(ops: readonly ControllerOp[]): void;
|
|
2362
|
+
/**
|
|
2363
|
+
* The current presence roster (Phase 6, ADR 0015) — always a full
|
|
2364
|
+
* replacement, never a delta, matching `presence.sync`'s existing
|
|
2365
|
+
* semantics exactly (an adapter that aggregates wire deltas into a full
|
|
2366
|
+
* roster before calling this is the adapter's own job, not the
|
|
2367
|
+
* controller's). Required on this interface (not optional) because a
|
|
2368
|
+
* Host only ever *consumes* `CollaborationReceiver` — never implements
|
|
2369
|
+
* it — so adding a required method here cannot break an existing custom
|
|
2370
|
+
* `CollaborationAdapter`. An adapter with no presence support simply
|
|
2371
|
+
* never calls it.
|
|
2372
|
+
*/
|
|
2373
|
+
presence(users: readonly PresenceUser[]): void;
|
|
2374
|
+
/**
|
|
2375
|
+
* The server has confirmed receipt of these op ids (Phase 7) —
|
|
2376
|
+
* distinguishes "sent" from "server accepted," which `sendOps` alone
|
|
2377
|
+
* (fire-and-forget) cannot. Required for the same reason `presence` is:
|
|
2378
|
+
* Hosts only ever consume this interface, never implement it, so this
|
|
2379
|
+
* cannot break an existing custom `CollaborationAdapter`. An adapter with
|
|
2380
|
+
* no ack support simply never calls it — the collaboration pipe still
|
|
2381
|
+
* works exactly as it did before this existed, just without the
|
|
2382
|
+
* bookkeeping/observability this enables.
|
|
2383
|
+
*/
|
|
2384
|
+
acknowledged(opIds: readonly string[]): void;
|
|
1543
2385
|
status(state: "online" | "reconnecting" | "offline"): void;
|
|
1544
2386
|
error(cause: unknown): void;
|
|
1545
2387
|
}
|
|
2388
|
+
/**
|
|
2389
|
+
* Result of `CollaborationSession.requestSync()` (Phase 7). `"ops"` means
|
|
2390
|
+
* the adapter's own live-pipe cache fully covered the gap since the
|
|
2391
|
+
* caller's last known revision — apply `ops` and the client is caught up,
|
|
2392
|
+
* no persistence reload needed. `"unavailable"` means it couldn't (gap too
|
|
2393
|
+
* large, server restarted, or the adapter has no retained history at all)
|
|
2394
|
+
* — the caller must fall back to a persistence-backed reload. This is a
|
|
2395
|
+
* best-effort *liveness* cache, deliberately never a durable source of
|
|
2396
|
+
* truth (ADR 0006's "collaboration is never a second source of document
|
|
2397
|
+
* truth" — see ADR 0015's own extension of that principle to presence,
|
|
2398
|
+
* now extended once more, the same way, to this).
|
|
2399
|
+
*/
|
|
2400
|
+
type CollaborationSyncResult = {
|
|
2401
|
+
state: "ops";
|
|
2402
|
+
ops: readonly ControllerOp[];
|
|
2403
|
+
serverRevision: string;
|
|
2404
|
+
} | {
|
|
2405
|
+
state: "unavailable";
|
|
2406
|
+
};
|
|
1546
2407
|
interface CollaborationSession {
|
|
1547
2408
|
sendOps(ops: readonly ControllerOp[]): void;
|
|
2409
|
+
/**
|
|
2410
|
+
* Publishes this client's own local presence (Phase 6, ADR 0015) —
|
|
2411
|
+
* best-effort, unordered, never persisted, never an Op. Optional: an
|
|
2412
|
+
* adapter that doesn't support presence simply omits this method, and
|
|
2413
|
+
* `presence.broadcast()` becomes a silent no-op.
|
|
2414
|
+
*/
|
|
2415
|
+
updatePresence?(presence: LocalPresence): void;
|
|
2416
|
+
/**
|
|
2417
|
+
* Requests an incremental catch-up after a reconnect (Phase 7) — optional;
|
|
2418
|
+
* an adapter that doesn't support this simply omits the method, and the
|
|
2419
|
+
* caller (`resyncAfterReconnect`) goes straight to its existing
|
|
2420
|
+
* persistence-backed full reload, unchanged from Phase 6.
|
|
2421
|
+
*/
|
|
2422
|
+
requestSync?(): Promise<CollaborationSyncResult>;
|
|
1548
2423
|
close(): Promise<void>;
|
|
1549
2424
|
}
|
|
1550
2425
|
interface CreateBoardControllerOptions {
|
|
@@ -1584,6 +2459,87 @@ interface CreateBoardControllerOptions {
|
|
|
1584
2459
|
* doesn't use the React theme system can set this directly instead.
|
|
1585
2460
|
*/
|
|
1586
2461
|
boardTheme?: BoardThemeOptions;
|
|
2462
|
+
/**
|
|
2463
|
+
* Debounced auto-flush of pending persistence Ops after document changes
|
|
2464
|
+
* settle (Phase 5). Enabled by default (1000ms debounce) whenever
|
|
2465
|
+
* `adapters.persistence` is configured — today, without this, a Host must
|
|
2466
|
+
* call `flush()` manually after every edit for anything to persist. Pass
|
|
2467
|
+
* `false` to opt out entirely and drive `flush()` yourself, preserving
|
|
2468
|
+
* prior behavior exactly. Never fires on a per-change basis — rapid edits
|
|
2469
|
+
* coalesce into one flush of their final state (ADR 0006).
|
|
2470
|
+
*/
|
|
2471
|
+
autosave?: boolean | {
|
|
2472
|
+
debounceMs?: number;
|
|
2473
|
+
};
|
|
2474
|
+
/**
|
|
2475
|
+
* Throttle for `presence.broadcast()` (Phase 6, ADR 0015) — the minimum
|
|
2476
|
+
* interval between outgoing presence updates sent via the configured
|
|
2477
|
+
* `CollaborationAdapter`. Defaults to 50ms. A trailing throttle: the
|
|
2478
|
+
* latest value passed to `broadcast()` always eventually sends, even if
|
|
2479
|
+
* calls arrive faster than this interval.
|
|
2480
|
+
*/
|
|
2481
|
+
presenceThrottleMs?: number;
|
|
2482
|
+
/**
|
|
2483
|
+
* Caps how many `ControllerOp`s can sit queued, unsent, for the
|
|
2484
|
+
* persistence pipe (`pendingOps`) or the collaboration pipe
|
|
2485
|
+
* (`pendingCollaborationOps`) at once (Phase 7) — each pipe is capped
|
|
2486
|
+
* independently. Prevents unbounded memory growth from a long-lived
|
|
2487
|
+
* offline session or a stuck adapter. Exceeding it never fails or drops
|
|
2488
|
+
* the local edit itself (the Document already applied it optimistically)
|
|
2489
|
+
* — only queueing for that one pipe is skipped, and a
|
|
2490
|
+
* `{code:"queue-overflow", retryable:false}` error is emitted so a Host
|
|
2491
|
+
* can react. Defaults to 1000 — the Phase 9 collaboration coalescing
|
|
2492
|
+
* above (`collaborationCoalesceMs`) already keeps a busy drag from
|
|
2493
|
+
* approaching this on its own, so hitting it in practice means a pipe
|
|
2494
|
+
* has been offline/stuck for a genuinely long editing session.
|
|
2495
|
+
*
|
|
2496
|
+
* **Recovery** (Phase 9): the dropped op itself is gone from that one
|
|
2497
|
+
* pipe's queue — there is no automatic backfill, and the live
|
|
2498
|
+
* controller keeps running with that pipe now silently missing one
|
|
2499
|
+
* edit. Two things stay true regardless: (1) the in-memory Document is
|
|
2500
|
+
* never affected — a queue-overflow can never corrupt or roll back a
|
|
2501
|
+
* local edit, only skip sending it; (2) staleness is per-object, not
|
|
2502
|
+
* permanent — any *later* edit to that same object produces a brand
|
|
2503
|
+
* new, undropped Op carrying its full current state, which naturally
|
|
2504
|
+
* supersedes the gap (the Op model is already last-write-wins/
|
|
2505
|
+
* idempotent, so a superseding Op doesn't need the earlier one to have
|
|
2506
|
+
* arrived). The real risk is an object that's dropped and never edited
|
|
2507
|
+
* again before the controller is disposed or the page reloads — a Host
|
|
2508
|
+
* that needs strict durability should treat `queue-overflow` as a
|
|
2509
|
+
* signal to check `persistence.state`/`pendingOps` pressure (via
|
|
2510
|
+
* `usePersistenceStatus`/`getSnapshot().connection.persistence`)
|
|
2511
|
+
* before disposing, not assume disposing and reconnecting alone
|
|
2512
|
+
* repairs the gap (a fresh `load()` only returns what the backend
|
|
2513
|
+
* already has, which is exactly what's missing the dropped edit).
|
|
2514
|
+
*/
|
|
2515
|
+
maxPendingOps?: number;
|
|
2516
|
+
/**
|
|
2517
|
+
* Coalescing window (ms) for outgoing collaboration Ops (Phase 9) — same
|
|
2518
|
+
* trailing-throttle shape as `presenceThrottleMs`: the first Op after an
|
|
2519
|
+
* idle period sends immediately, and subsequent Ops for the *same*
|
|
2520
|
+
* object within this window replace each other (latest value wins,
|
|
2521
|
+
* matching the already-idempotent Op model) rather than each triggering
|
|
2522
|
+
* its own send. A multi-second drag that previously sent one full Op per
|
|
2523
|
+
* pointer-move now sends at most one per window per touched object.
|
|
2524
|
+
* Persistence (`pendingOps`) is unaffected — it already debounces via
|
|
2525
|
+
* `autosave`, so this option only changes live collaboration traffic.
|
|
2526
|
+
* Defaults to 50ms.
|
|
2527
|
+
*/
|
|
2528
|
+
collaborationCoalesceMs?: number;
|
|
2529
|
+
/**
|
|
2530
|
+
* Caps how many resolved objects a single `content.copy`/`content.cut`
|
|
2531
|
+
* (or their Cmd/Ctrl+C/X keyboard equivalents) will hold in the
|
|
2532
|
+
* in-memory clipboard at once (Phase 9) — `expandSelection` recursively
|
|
2533
|
+
* expands groups, so an unbounded selection (a huge group, or thousands
|
|
2534
|
+
* of individually selected strokes) could otherwise clone and retain an
|
|
2535
|
+
* arbitrarily large snapshot indefinitely, until the next copy/cut
|
|
2536
|
+
* replaces it. Exceeding it rejects the whole copy/cut (nothing is
|
|
2537
|
+
* cloned, and — for cut — nothing is removed from the Document either,
|
|
2538
|
+
* never a partial copy of an arbitrary subset) and emits a
|
|
2539
|
+
* `{code:"clipboard-overflow", retryable:false}` error. Defaults to
|
|
2540
|
+
* 5000.
|
|
2541
|
+
*/
|
|
2542
|
+
maxClipboardItems?: number;
|
|
1587
2543
|
}
|
|
1588
2544
|
interface BoardController {
|
|
1589
2545
|
readonly document: ReadonlyBoardDocument;
|
|
@@ -1615,6 +2571,15 @@ interface BoardController {
|
|
|
1615
2571
|
};
|
|
1616
2572
|
readonly view: {
|
|
1617
2573
|
fit(): void;
|
|
2574
|
+
/**
|
|
2575
|
+
* Frame the current selection (Phase 8), the same way `fit()` frames the
|
|
2576
|
+
* whole board. A no-op with nothing selected — deliberately doesn't fall
|
|
2577
|
+
* back to `fit()`'s "frame everything," which would be a surprising
|
|
2578
|
+
* result for an empty selection. On a headless board this can only
|
|
2579
|
+
* re-center the view (no viewport to compute a real zoom-to-fit from),
|
|
2580
|
+
* matching `fit()`'s own headless limitation exactly.
|
|
2581
|
+
*/
|
|
2582
|
+
zoomToSelection(): void;
|
|
1618
2583
|
zoomTo(value: number): void;
|
|
1619
2584
|
centerOn(point: BoardPoint): void;
|
|
1620
2585
|
get(): BoardView;
|
|
@@ -1636,6 +2601,64 @@ interface BoardController {
|
|
|
1636
2601
|
* Unknown ids are silently skipped, matching `remove`'s convention.
|
|
1637
2602
|
*/
|
|
1638
2603
|
duplicate(ids: readonly string[]): readonly string[];
|
|
2604
|
+
/**
|
|
2605
|
+
* Creates a new Group referencing `ids` as its children and returns its
|
|
2606
|
+
* id, as one undoable step. Unknown ids are silently skipped, matching
|
|
2607
|
+
* `duplicate`/`remove`'s convention. A child id that's itself a group
|
|
2608
|
+
* makes a nested group — expanding nested groups into their leaf
|
|
2609
|
+
* members is always the caller's job, never assumed here (matches the
|
|
2610
|
+
* document-model `GroupObject` itself).
|
|
2611
|
+
*/
|
|
2612
|
+
group(ids: readonly string[]): string;
|
|
2613
|
+
/**
|
|
2614
|
+
* Dissolves one group, returning its immediate children's ids (a nested
|
|
2615
|
+
* subgroup among them stays intact, itself still a group) — the group
|
|
2616
|
+
* record itself is removed, the children are untouched. A no-op
|
|
2617
|
+
* (returns `[]`) if `groupId` isn't a group.
|
|
2618
|
+
*/
|
|
2619
|
+
ungroup(groupId: string): readonly string[];
|
|
2620
|
+
/**
|
|
2621
|
+
* Aligns every given object's matching edge/center to the corresponding
|
|
2622
|
+
* edge/center of their combined bounding box, as one undoable step.
|
|
2623
|
+
* `"top"`/`"bottom"` follow board space's Y-up convention (`"top"` is
|
|
2624
|
+
* the larger Y). Ids that don't resolve, or resolve to a Group (which
|
|
2625
|
+
* has no position of its own), are skipped. A no-op under 2 resolvable
|
|
2626
|
+
* ids — there's nothing to align relative to.
|
|
2627
|
+
*/
|
|
2628
|
+
align(ids: readonly string[], edge: "left" | "right" | "top" | "bottom" | "centerX" | "centerY"): void;
|
|
2629
|
+
/**
|
|
2630
|
+
* Spaces the middle objects' centers evenly between the first and last
|
|
2631
|
+
* (sorted along `axis`), as one undoable step — the two endpoints don't
|
|
2632
|
+
* move. Ids that don't resolve, or resolve to a Group, are skipped. A
|
|
2633
|
+
* no-op under 3 resolvable ids — there's no "middle" to distribute.
|
|
2634
|
+
*/
|
|
2635
|
+
distribute(ids: readonly string[], axis: "x" | "y"): void;
|
|
2636
|
+
/**
|
|
2637
|
+
* Snapshots `ids` (recursively expanded through any group, same as
|
|
2638
|
+
* `duplicate`) into an internal in-memory clipboard — never
|
|
2639
|
+
* `navigator.clipboard`, scoped to this one controller instance and
|
|
2640
|
+
* replaced wholesale by the next `copy`/`cut`. Read-only; works even
|
|
2641
|
+
* on a read-only board.
|
|
2642
|
+
*/
|
|
2643
|
+
copy(ids: readonly string[]): void;
|
|
2644
|
+
/** `copy`, then removes every resolved object (recursively through any group) as one undoable step. */
|
|
2645
|
+
cut(ids: readonly string[]): void;
|
|
2646
|
+
/**
|
|
2647
|
+
* Clones the current clipboard contents onto the board as one undoable
|
|
2648
|
+
* step, offset the same small cascade `duplicate` uses (no cursor
|
|
2649
|
+
* position to paste relative to yet). Returns the new top-level ids —
|
|
2650
|
+
* a pasted group's own id stands for its (also-pasted) children, which
|
|
2651
|
+
* aren't listed separately. `[]` when the clipboard is empty.
|
|
2652
|
+
*/
|
|
2653
|
+
paste(): readonly string[];
|
|
2654
|
+
/**
|
|
2655
|
+
* Select every top-level object (Phase 8) — a group's own id stands for
|
|
2656
|
+
* its children, which aren't selected separately, matching `paste`'s own
|
|
2657
|
+
* "what the user sees" id list. Hidden objects are excluded, consistent
|
|
2658
|
+
* with them already being excluded from marquee selection. Works with
|
|
2659
|
+
* no canvas/engine, same as {@link toggleSelectionVisibility}.
|
|
2660
|
+
*/
|
|
2661
|
+
selectAll(): void;
|
|
1639
2662
|
table: {
|
|
1640
2663
|
addRow(tableId: string): void;
|
|
1641
2664
|
addCol(tableId: string): void;
|
|
@@ -1646,6 +2669,29 @@ interface BoardController {
|
|
|
1646
2669
|
};
|
|
1647
2670
|
select(ids: readonly string[]): void;
|
|
1648
2671
|
import(document: SerializedBoardDocument): readonly string[];
|
|
2672
|
+
/**
|
|
2673
|
+
* Toggle lock state for the current selection (or focused note/text/
|
|
2674
|
+
* table/image/timer), matching whatever a single Host lock/unlock
|
|
2675
|
+
* control already does per object type. A no-op with nothing selected,
|
|
2676
|
+
* on a headless board, or when every actionable target is locked by
|
|
2677
|
+
* another collaborator who isn't the current lock holder.
|
|
2678
|
+
*/
|
|
2679
|
+
toggleSelectionLock(): void;
|
|
2680
|
+
/**
|
|
2681
|
+
* Toggle hidden state for the current selection, as one undo entry
|
|
2682
|
+
* (Phase 8). If any selected object is hidden, shows every selected
|
|
2683
|
+
* object; otherwise hides them all — same "any wins" semantics as
|
|
2684
|
+
* {@link toggleSelectionLock}. Hidden objects stay fully present in the
|
|
2685
|
+
* document (they still serialize, persist, sync, undo/redo) — they just
|
|
2686
|
+
* stop rendering and stop being hit-testable/selectable via pointer
|
|
2687
|
+
* interaction. Unlike `toggleSelectionLock`, this works on a headless
|
|
2688
|
+
* board too: it only touches `selection`/the document, no canvas or
|
|
2689
|
+
* engine involved. Custom objects have no visibility concept (no
|
|
2690
|
+
* `Hideable` field) and are silently skipped, matching how
|
|
2691
|
+
* `toggleSelectionLock` already excludes them. A no-op with nothing
|
|
2692
|
+
* selected or when the selection is only custom objects.
|
|
2693
|
+
*/
|
|
2694
|
+
toggleSelectionVisibility(): void;
|
|
1649
2695
|
};
|
|
1650
2696
|
readonly query: {
|
|
1651
2697
|
get(id: string): DeepReadonly<BoardObject> | undefined;
|
|
@@ -1674,6 +2720,15 @@ interface BoardController {
|
|
|
1674
2720
|
follow(view: PresenceView): void;
|
|
1675
2721
|
/** Ease the camera to a peer's view; returns false (no-op) while mid-stroke. */
|
|
1676
2722
|
gather(view: PresenceView): boolean;
|
|
2723
|
+
/**
|
|
2724
|
+
* Publishes this client's own cursor/tool/view for other collaborators
|
|
2725
|
+
* (Phase 6, ADR 0015), via the configured `CollaborationAdapter` —
|
|
2726
|
+
* throttled internally (`presenceThrottleMs` option, default 50ms) so
|
|
2727
|
+
* a raw pointermove stream never becomes a message-per-event flood. A
|
|
2728
|
+
* no-op if no collaboration adapter is configured, or if the
|
|
2729
|
+
* configured one doesn't implement `updatePresence`.
|
|
2730
|
+
*/
|
|
2731
|
+
broadcast(local: LocalPresence): void;
|
|
1677
2732
|
};
|
|
1678
2733
|
readonly export: {
|
|
1679
2734
|
svg(): string;
|
|
@@ -1699,7 +2754,19 @@ interface BoardController {
|
|
|
1699
2754
|
}>;
|
|
1700
2755
|
dispose(): Promise<void>;
|
|
1701
2756
|
}
|
|
2757
|
+
/**
|
|
2758
|
+
* Creates a {@link BoardController} — the SDK's canonical, capability-grouped
|
|
2759
|
+
* entry point (`content`, `tools`, `style`, `history`, `view`, `query`,
|
|
2760
|
+
* `comments`, `presence`, `export`, `assets`, plus top-level `getSnapshot`/
|
|
2761
|
+
* `subscribe`/`on`/`setReadOnly`/`flush`/`dispose`) for driving a Board
|
|
2762
|
+
* imperatively from any JS/TS runtime. Supply a `canvas` to render, or omit
|
|
2763
|
+
* it to run headless (SSR, tests, or a document/history-only integration).
|
|
2764
|
+
* Persistence and Collaboration are opt-in via `options.adapters` — without
|
|
2765
|
+
* them the controller runs entirely in memory. Call `dispose()` when done to
|
|
2766
|
+
* release the renderer, adapters, and any pending timers.
|
|
2767
|
+
*/
|
|
1702
2768
|
declare function createBoardController(options: CreateBoardControllerOptions): BoardController;
|
|
2769
|
+
/** @deprecated Use `BoardController.getSnapshot()`'s return type instead. */
|
|
1703
2770
|
type LocalBoardSnapshot = {
|
|
1704
2771
|
documentId: string;
|
|
1705
2772
|
selectedStrokeId: string | null;
|
|
@@ -1708,10 +2775,12 @@ type LocalBoardSnapshot = {
|
|
|
1708
2775
|
canRedo: boolean;
|
|
1709
2776
|
disposed: boolean;
|
|
1710
2777
|
};
|
|
2778
|
+
/** @deprecated Use `CreateBoardControllerOptions` with `createBoardController` instead. */
|
|
1711
2779
|
type LocalBoardOptions = {
|
|
1712
2780
|
documentId: string;
|
|
1713
2781
|
initialDocument?: SerializedBoardDocument;
|
|
1714
2782
|
};
|
|
2783
|
+
/** @deprecated Use `BoardController` (from `createBoardController`) instead — this stroke-only, single-tool surface predates the full capability-grouped controller. */
|
|
1715
2784
|
type LocalBoard = {
|
|
1716
2785
|
drawStroke(stroke: Stroke): void;
|
|
1717
2786
|
selectAt(point: BoardPoint): string | null;
|
|
@@ -1722,8 +2791,21 @@ type LocalBoard = {
|
|
|
1722
2791
|
subscribe(listener: () => void): () => void;
|
|
1723
2792
|
dispose(): Promise<void>;
|
|
1724
2793
|
};
|
|
2794
|
+
/** @deprecated Use `createBoardController` instead — this is a thin, stroke-only wrapper kept for the original Phase 1 tracer's compatibility. */
|
|
1725
2795
|
declare function createLocalBoard(options: LocalBoardOptions): LocalBoard;
|
|
1726
2796
|
|
|
2797
|
+
interface CreateMemoryPersistenceOptions {
|
|
2798
|
+
/** Pre-seeded documents, keyed by document id — as if a prior session had already saved them. Seeded documents start at revision "1". */
|
|
2799
|
+
seed?: Record<string, CurrentSerializedDocument>;
|
|
2800
|
+
}
|
|
2801
|
+
/**
|
|
2802
|
+
* Creates a real, in-memory `PersistenceAdapter`. One instance can back
|
|
2803
|
+
* multiple documents (keyed by `DocumentContext.documentId`, like every
|
|
2804
|
+
* other adapter in this package). State lives only in this instance —
|
|
2805
|
+
* discarded on garbage collection, never written to disk.
|
|
2806
|
+
*/
|
|
2807
|
+
declare function createMemoryPersistence(options?: CreateMemoryPersistenceOptions): PersistenceAdapter;
|
|
2808
|
+
|
|
1727
2809
|
type ScrawlThemePreset = "light" | "dark";
|
|
1728
2810
|
type ScrawlDensity = "comfortable" | "compact";
|
|
1729
2811
|
type ScrawlGridMode = "none" | "line" | "dot";
|
|
@@ -1899,10 +2981,19 @@ interface FocusedItemToolbarProps {
|
|
|
1899
2981
|
snapshot: BoardSnapshot;
|
|
1900
2982
|
}
|
|
1901
2983
|
/**
|
|
1902
|
-
* Floating toolbar above the focused note, shape,
|
|
1903
|
-
* Size (note/text) or Width (shape),
|
|
1904
|
-
* Table/image/timer/
|
|
1905
|
-
*
|
|
2984
|
+
* Floating toolbar above the focused note, shape, text block, or semantic
|
|
2985
|
+
* Rectangle/Ellipse — Colour, Size (note/text) or Width (shape), Fill/Stroke
|
|
2986
|
+
* (Rectangle/Ellipse), Lock/Unlock, Duplicate, Delete. Table/image/timer/
|
|
2987
|
+
* custom objects and plain (non-shape) ink strokes never get a toolbar here
|
|
2988
|
+
* — out of scope for this destination.
|
|
2989
|
+
*
|
|
2990
|
+
* Rectangle/Ellipse (Phase 2) are a second, parallel "shape" concept from
|
|
2991
|
+
* the legacy ink-stroke shape below: both draw from the same toolbar
|
|
2992
|
+
* buttons and look the same to a user, but a Rectangle/Ellipse is a real
|
|
2993
|
+
* BoardObject with its own resize handles (independent width/height,
|
|
2994
|
+
* `ShapeResizeHandle`), while a legacy shape-stroke keeps going through the
|
|
2995
|
+
* native selection gizmo. This duality is deliberate and temporary — see
|
|
2996
|
+
* docs/reports/phase-2-document-object-model.md.
|
|
1906
2997
|
*
|
|
1907
2998
|
* Lock/Unlock carries no per-user ownership gating: nothing else in this
|
|
1908
2999
|
* SDK enforces lock ownership either (`content.update` never checks
|
|
@@ -1957,6 +3048,16 @@ interface ScrawlProviderProps {
|
|
|
1957
3048
|
className?: string;
|
|
1958
3049
|
style?: ThemeStyle;
|
|
1959
3050
|
}
|
|
3051
|
+
/**
|
|
3052
|
+
* Wraps a Board `controller` you created yourself (via `createBoardController`)
|
|
3053
|
+
* so its descendants can use `useScrawlController`/`useScrawlSnapshot`/etc.
|
|
3054
|
+
* and render its default UI pieces (`DefaultBoardChrome`, `StyleShelf`, ...).
|
|
3055
|
+
* Prefer `Scrawl` unless you need to construct or own the controller's
|
|
3056
|
+
* lifecycle yourself (e.g. you create it outside React, or need it before
|
|
3057
|
+
* first render). Set `disposeOnUnmount` to have this provider call
|
|
3058
|
+
* `controller.dispose()` on unmount; otherwise disposal remains your own
|
|
3059
|
+
* responsibility.
|
|
3060
|
+
*/
|
|
1960
3061
|
declare function ScrawlProvider({ controller, children, preset, theme, portalContainer: customPortal, disposeOnUnmount, onThemeDiagnostic, className, style }: ScrawlProviderProps): react.JSX.Element;
|
|
1961
3062
|
interface ScrawlProps extends Omit<CreateBoardControllerOptions, "canvas"> {
|
|
1962
3063
|
children?: ReactNode;
|
|
@@ -1977,6 +3078,17 @@ interface ScrawlProps extends Omit<CreateBoardControllerOptions, "canvas"> {
|
|
|
1977
3078
|
*/
|
|
1978
3079
|
icons?: Partial<Record<BuiltInTool, ReactNode>>;
|
|
1979
3080
|
}
|
|
3081
|
+
/**
|
|
3082
|
+
* The fastest path to an embedded Board: creates and owns a
|
|
3083
|
+
* `BoardController` for you (constructed once, disposed on unmount) and
|
|
3084
|
+
* renders it into a canvas. Render with no `children` to get the SDK's
|
|
3085
|
+
* default toolbar/UI chrome, or supply your own `children` (using the
|
|
3086
|
+
* `useScrawlController`/`useScrawlSnapshot` hooks, or the exported
|
|
3087
|
+
* `DefaultBoardChrome`/`StyleShelf`/etc. pieces) to build a custom UI on
|
|
3088
|
+
* top of the same controller. Accepts every `CreateBoardControllerOptions`
|
|
3089
|
+
* field except `canvas` (pass `canvas={null}` for a headless board, or an
|
|
3090
|
+
* existing `<canvas>` element to control its identity yourself).
|
|
3091
|
+
*/
|
|
1980
3092
|
declare function Scrawl({ children, preset, theme, portalContainer, className, style, onReady, onError, onThemeDiagnostic, canvas: suppliedCanvas, icons, ...options }: ScrawlProps): react.JSX.Element;
|
|
1981
3093
|
interface ScrawlCanvasProps {
|
|
1982
3094
|
element?: HTMLCanvasElement;
|
|
@@ -2002,6 +3114,38 @@ declare function ScrawlPortal({ children }: {
|
|
|
2002
3114
|
declare function useScrawlController(): BoardController;
|
|
2003
3115
|
declare function useScrawlTheme(): ScrawlResolvedTheme;
|
|
2004
3116
|
declare function useScrawlSnapshot(): BoardSnapshot;
|
|
3117
|
+
/**
|
|
3118
|
+
* A thin, purely-derived convenience over
|
|
3119
|
+
* `useScrawlSnapshot().connection.persistence` (Phase 5) — for a Host
|
|
3120
|
+
* component that only cares about save status (e.g. a
|
|
3121
|
+
* "Saving…"/"Saved"/"Offline" indicator) and would otherwise re-derive
|
|
3122
|
+
* this same field access itself. Adds no state and no behavior of its
|
|
3123
|
+
* own: React still owns none of persistence, exactly as before — this
|
|
3124
|
+
* hook only re-reads what the controller already tracks.
|
|
3125
|
+
*
|
|
3126
|
+
* Deliberately does *not* go through `useScrawlSnapshot()` (Phase 9):
|
|
3127
|
+
* `changed()` rebuilds the whole `BoardSnapshot` — including a fresh
|
|
3128
|
+
* `connection.persistence` object — on every document mutation, even ones
|
|
3129
|
+
* that never touch persistence at all, so a component using only this
|
|
3130
|
+
* hook would otherwise re-render on every stroke draw. `useSyncStatus`
|
|
3131
|
+
* below memoizes on the value (`state`/`error`), not just the object
|
|
3132
|
+
* reference, and returns the same cached value across renders where
|
|
3133
|
+
* nothing relevant changed — the same shape `usePresence` already uses
|
|
3134
|
+
* for its own independently-scoped store.
|
|
3135
|
+
*/
|
|
3136
|
+
declare function usePersistenceStatus(): PersistenceSnapshot;
|
|
3137
|
+
/** 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). */
|
|
3138
|
+
declare function useCollaborationStatus(): CollaborationSnapshot;
|
|
3139
|
+
/**
|
|
3140
|
+
* The current presence roster (Phase 6), live-updating — wraps
|
|
3141
|
+
* `controller.presence.list()`/`subscribe` the same way `useScrawlSnapshot`
|
|
3142
|
+
* wraps the controller's main snapshot, via `useSyncExternalStore`. Does
|
|
3143
|
+
* not re-render on every remote pointer move by itself; it re-renders
|
|
3144
|
+
* whenever the roster the controller already tracks changes, at whatever
|
|
3145
|
+
* rate that arrives at (throttled adapter-side, per `presenceThrottleMs`).
|
|
3146
|
+
*/
|
|
3147
|
+
declare function usePresence(): readonly PresenceUser[];
|
|
3148
|
+
/** @deprecated Use `Scrawl` instead — this predates the full capability-grouped `BoardController` and only exposes the stroke-only `LocalBoard` surface. */
|
|
2005
3149
|
type ScrawlBoardProps = {
|
|
2006
3150
|
documentId: string;
|
|
2007
3151
|
initialDocument?: SerializedBoardDocument;
|
|
@@ -2009,7 +3153,8 @@ type ScrawlBoardProps = {
|
|
|
2009
3153
|
className?: string;
|
|
2010
3154
|
style?: CSSProperties;
|
|
2011
3155
|
};
|
|
3156
|
+
/** @deprecated Use `Scrawl` instead — this predates the full capability-grouped `BoardController` and only exposes the stroke-only `LocalBoard` surface. */
|
|
2012
3157
|
declare function ScrawlBoard({ documentId, initialDocument, onReady, className, style }: ScrawlBoardProps): react.JSX.Element;
|
|
2013
3158
|
|
|
2014
|
-
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, AddImageCommand, AddNoteCommand, AddStrokesCommand, AddTableCommand, AddTextCommand, AddTimerCommand, AssetResolutionError, BEACON_INSET, BoardDocument, CURRENT_DOCUMENT_SCHEMA_VERSION, ClusterStore, DefaultBoardChrome, DeleteImageCommand, DeleteNoteCommand, DeleteStrokesCommand, DeleteTableCommand, DeleteTextCommand, DeleteTimerCommand, DocumentRecoveryError, END_TAPER, ERASE_THRESHOLD, EraseCommand, FOG_COLOR, FocusedItemToolbar, HIGHLIGHT_COLORS, History, 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, SDK_DEVELOPMENT_VERSION, SDK_PACKAGE_NAME, 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, UpdateImageCommand, UpdateNoteCommand, UpdateTableCommand, UpdateTextCommand, UpdateTimerCommand, apply, applyItemLock, assetRef, avgScale, canUnlockItem, changeToOps, clampAssetCacheBytes, cloneCustomObject, cloneImage, cloneNote, cloneStroke, cloneTable, cloneText, cloneTimer, createBoardController, createLocalBoard, documentId, documentToSVG, formatTimer, invert, isAssetRef, isIdentity, isStampKind, loadDocumentBytes, measureTable, measureTextBlock, migrateDocument, mul, pauseTimer, placePresenceBeacon, resolveScrawlTheme, ribbonEdges, rotationAbout, scalingAbout, scrawlThemePresets, searchBoard, serializeDocument, serializeLock, serializeStroke, setTimerDuration, stampDataUrl, startTimer, strokeId, timerExpired, timerRemaining, toggleTimer, translation, useScrawlController, useScrawlSnapshot, useScrawlTheme, validateScrawlTheme };
|
|
2015
|
-
export type { ApplyOpsResult, AssetDiagnostic, AssetIngestRequest, AssetIngestResult, AssetIngestor, AssetKind, AssetPurpose, AssetRef, AssetResolutionErrorCode, AssetResolveRequest, AssetResolveResult, AssetResolver, BBox, BoardController, BoardControllerError, BoardEventMap, BoardKeyInput, BoardObject, BoardObjectInput, BoardObjectPatch, BoardPoint, BoardPointerInput, BoardScene, BoardSlotProps, BoardSnapshot, BoardStroke, BoardStyle, BoardThemeOptions, BoardView, BuiltInTool, ClusterIdFactory, CollaborationAdapter, CollaborationReceiver, CollaborationSession, CollaborationSnapshot, CollaboratorIdentity, Command, CommandKind, CommentMarker, ControllerOp, CreateBoardControllerOptions, CurrentSerializedDocument, CurrentSerializedStroke, CustomBoardObject, CustomObjectAddInput, CustomObjectDefinition, CustomTool, CustomToolDefinition, DeepReadonly, DefaultBoardChromeProps, DefaultUIRegion, DefaultUISlot, DefaultUISlots, DialogSlotProps, DocumentChange, DocumentContext, DocumentId, DocumentLoadResult, DocumentRecoveryCode, ExtensionCommand, ExtensionDiagnostic, ExtensionHitResult, ExtensionId, ExtensionRequirement, FocusedItem, FocusedItemToolbarProps, ImageBlock, InlineEditorsProps, InputModifiers, JsonObject, JsonValue, KitchenTimer, LoadResult, LocalBoard, LocalBoardOptions, LocalBoardSnapshot, LockHolder, LockTarget, Lockable, Mat2x3, MultiplayerCursorsProps, NoteVote, ObjectDescribeContext, ObjectIntent, ObjectType, Op, OpCollection, PersistenceAdapter, PersistenceDiagnostic, PersistenceSnapshot, PresenceCursor, PresencePlacement, PresenceUser, PresenceView, QueryableBoardObject, ReadonlyBoardDocument, ReadonlyCustomObject, ReadonlyDocumentChange, 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, SerializedBoardStroke, SerializedPoint, SerializedStroke, StampKind, StickyNote, Stroke, StrokeId, StrokePoint, StrokeTool, StyleShelfProps, SupportedAssetMediaType, TableBlock, TextBlock, ToolCancelReason, ToolCapabilities, ToolCursor, ToolId, ViewportInset };
|
|
3159
|
+
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, END_TAPER, ERASE_THRESHOLD, EraseCommand, FOG_COLOR, FocusedItemToolbar, HIGHLIGHT_COLORS, History, 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, formatTimer, invert, isAssetRef, isIdentity, isStampKind, loadDocumentBytes, measureTable, measureTextBlock, migrateDocument, mul, pauseTimer, placePresenceBeacon, resolveScrawlTheme, ribbonEdges, rotationAbout, scalingAbout, scrawlThemePresets, searchBoard, serializeDocument, serializeLock, serializeStroke, setTimerDuration, stampDataUrl, startTimer, strokeId, timerExpired, timerRemaining, toggleTimer, translation, useCollaborationStatus, usePersistenceStatus, usePresence, useScrawlController, useScrawlSnapshot, useScrawlTheme, validateScrawlTheme };
|
|
3160
|
+
export type { ApplyOpsResult, ArrowHeadStyle, ArrowObject, AssetDiagnostic, AssetExportFailure, AssetIngestRequest, AssetIngestResult, AssetIngestor, AssetKind, AssetPurpose, AssetRef, AssetResolutionErrorCode, AssetResolveRequest, AssetResolveResult, AssetResolver, BBox, BoardController, BoardControllerError, BoardEventMap, BoardKeyInput, BoardObject, BoardObjectInput, BoardObjectPatch, BoardPoint, BoardPointerInput, BoardScene, BoardSlotProps, BoardSnapshot, BoardStroke, BoardStyle, BoardThemeOptions, BoardView, BuiltInTool, ClusterIdFactory, CollaborationAckDiagnostic, CollaborationAdapter, CollaborationReceiver, CollaborationSession, CollaborationSnapshot, CollaborationSyncResult, CollaboratorIdentity, Command, CommandKind, CommentMarker, ControllerOp, CreateBoardControllerOptions, CreateMemoryPersistenceOptions, CurrentSerializedDocument, CurrentSerializedStroke, CustomBoardObject, CustomObjectAddInput, CustomObjectDefinition, CustomTool, CustomToolDefinition, DeepReadonly, DefaultBoardChromeProps, DefaultUIRegion, DefaultUISlot, DefaultUISlots, DialogSlotProps, DocumentChange, DocumentContext, DocumentId, DocumentLoadResult, DocumentRecoveryCode, EllipseObject, ExportDocumentSVGOptions, ExportDocumentSVGResult, ExtensionCommand, ExtensionDiagnostic, ExtensionHitResult, ExtensionId, ExtensionRequirement, FocusedItem, FocusedItemToolbarProps, GroupObject, HeartObject, 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, SerializedBoardStroke, SerializedPoint, SerializedStroke, StampKind, StarObject, StickyNote, Stroke, StrokeId, StrokePoint, StrokeTool, StyleShelfProps, SupportedAssetMediaType, TableBlock, TextBlock, ToolCancelReason, ToolCapabilities, ToolCursor, ToolId, ViewportInset };
|