@scrawl-board/board 0.1.0-beta.0 → 0.1.0-beta.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/NOTICE +8 -0
- package/README.md +11 -5
- package/dist/Inter-OFL.txt +92 -0
- package/dist/browser.d.ts +1314 -29
- package/dist/browser.js +11407 -36607
- package/dist/core.d.ts +1455 -61
- package/dist/core.js +3003 -85
- package/dist/index.d.ts +2109 -78
- package/dist/index.js +14291 -37063
- package/dist/local.d.ts +518 -0
- package/dist/local.js +342 -0
- package/dist/react.d.ts +1444 -34
- package/dist/react.js +13757 -36879
- package/dist/styles.css +619 -58
- package/package.json +17 -3
package/dist/react.d.ts
CHANGED
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
2
|
import { ReactNode, ComponentType, CSSProperties } from 'react';
|
|
3
3
|
|
|
4
|
-
declare const strokeIdBrand: unique symbol;
|
|
5
|
-
type StrokeId = string & {
|
|
6
|
-
readonly [strokeIdBrand]: "StrokeId";
|
|
7
|
-
};
|
|
8
|
-
|
|
9
4
|
/** Wire grammar: `asset:<namespace>:<opaque-id>`. Interpreted only by the Host. */
|
|
10
5
|
type AssetRef = string;
|
|
6
|
+
declare function isAssetRef(value: unknown): value is AssetRef;
|
|
7
|
+
/** Throws on malformed input; use `isAssetRef` where a boolean is wanted instead. */
|
|
8
|
+
declare function assetRef(value: string): AssetRef;
|
|
11
9
|
type AssetKind = "image";
|
|
12
10
|
type AssetPurpose = "render" | "thumbnail" | "export";
|
|
13
11
|
interface AssetResolveRequest {
|
|
@@ -49,6 +47,12 @@ interface AssetIngestor {
|
|
|
49
47
|
ingest(request: AssetIngestRequest): Promise<AssetIngestResult>;
|
|
50
48
|
}
|
|
51
49
|
type AssetResolutionErrorCode = "resolver-unavailable" | "not-found" | "forbidden" | "offline" | "unsupported-type" | "too-large" | "invalid-content" | "decode-failed" | "budget-exceeded" | "aborted" | "unknown";
|
|
50
|
+
declare class AssetResolutionError extends Error {
|
|
51
|
+
readonly code: AssetResolutionErrorCode;
|
|
52
|
+
readonly retryable: boolean;
|
|
53
|
+
readonly ref?: AssetRef | undefined;
|
|
54
|
+
constructor(code: AssetResolutionErrorCode, retryable: boolean, message: string, ref?: AssetRef | undefined);
|
|
55
|
+
}
|
|
52
56
|
/** Runtime event for a resolution/ingestion failure — never carries credentials or a fetchable location. */
|
|
53
57
|
interface AssetDiagnostic {
|
|
54
58
|
code: AssetResolutionErrorCode;
|
|
@@ -57,6 +61,9 @@ interface AssetDiagnostic {
|
|
|
57
61
|
objectKind: "image" | "custom";
|
|
58
62
|
retryable: boolean;
|
|
59
63
|
}
|
|
64
|
+
declare const SUPPORTED_ASSET_MEDIA_TYPES: readonly ["image/png", "image/jpeg", "image/webp"];
|
|
65
|
+
type SupportedAssetMediaType = (typeof SUPPORTED_ASSET_MEDIA_TYPES)[number];
|
|
66
|
+
declare function clampAssetCacheBytes(value: number | undefined): number;
|
|
60
67
|
|
|
61
68
|
type Mat2x3 = [number, number, number, number, number, number];
|
|
62
69
|
|
|
@@ -104,6 +111,7 @@ interface CustomBoardObject {
|
|
|
104
111
|
};
|
|
105
112
|
props: JsonValue;
|
|
106
113
|
}
|
|
114
|
+
declare function cloneCustomObject(object: CustomBoardObject): CustomBoardObject;
|
|
107
115
|
/**
|
|
108
116
|
* The read-only view handed to `describe`. Deep-readonly by construction
|
|
109
117
|
* (not derived via a shallow `Readonly<>`) because `describe` must treat its
|
|
@@ -142,6 +150,21 @@ interface CustomObjectDefinition<Props extends JsonValue = JsonValue> {
|
|
|
142
150
|
/** One pure, synchronous step per consecutive schema version. */
|
|
143
151
|
migrate?: Readonly<Record<number, (oldProps: JsonValue) => JsonValue>>;
|
|
144
152
|
describe(object: ReadonlyCustomObject<Props>, context: ObjectDescribeContext): BoardScene;
|
|
153
|
+
/**
|
|
154
|
+
* Optional point-level hit-test precision (Phase 8). Every custom object
|
|
155
|
+
* hit-tests against its bounding box (`fallback.bounds`) by default — this
|
|
156
|
+
* lets a non-rectangular shape (e.g. a circular card, an L-shaped region)
|
|
157
|
+
* reject a point that's inside that box but outside its actual visible
|
|
158
|
+
* silhouette, tightening a click/marquee/raycast hit to the shape's real
|
|
159
|
+
* outline. `point` is in this object's own local space — the same
|
|
160
|
+
* untransformed space `describe`'s returned geometry already lives in
|
|
161
|
+
* (the caller inverse-transforms the pointer's board point through
|
|
162
|
+
* `object.transform` before calling this). Absent means every point
|
|
163
|
+
* inside the bounding box hits, matching pre-Phase-8 behavior exactly.
|
|
164
|
+
* Rejecting a point here does not fall through to whatever's underneath —
|
|
165
|
+
* the gesture simply misses this object, same as clicking empty space.
|
|
166
|
+
*/
|
|
167
|
+
hitTest?(object: ReadonlyCustomObject<Props>, point: BoardPoint): boolean;
|
|
145
168
|
}
|
|
146
169
|
interface SceneNodeBase {
|
|
147
170
|
key: string;
|
|
@@ -175,6 +198,15 @@ interface SceneGroup extends SceneNodeBase {
|
|
|
175
198
|
kind: "group";
|
|
176
199
|
children: readonly BoardScene[];
|
|
177
200
|
}
|
|
201
|
+
/**
|
|
202
|
+
* **No renderer or SVG-export interpreter exists for this node kind yet**
|
|
203
|
+
* (tracked as deferred work — see `renderer/shapes/customObjects.ts`'s
|
|
204
|
+
* `"path"` case). Returning a `ScenePath` from `describe()` renders nothing,
|
|
205
|
+
* exports nothing, and contributes no hit-test bounds — it neither errors
|
|
206
|
+
* nor emits a diagnostic. Until an interpreter ships, build custom shapes
|
|
207
|
+
* from `SceneRect`/`SceneEllipse`/`SceneGroup`/`SceneText`/`SceneImage`
|
|
208
|
+
* instead.
|
|
209
|
+
*/
|
|
178
210
|
interface ScenePath extends SceneNodeBase {
|
|
179
211
|
kind: "path";
|
|
180
212
|
/** SVG-style path data, board-local coordinates. */
|
|
@@ -330,8 +362,23 @@ interface Lockable {
|
|
|
330
362
|
lockedByName?: string;
|
|
331
363
|
}
|
|
332
364
|
|
|
365
|
+
/**
|
|
366
|
+
* Per-object visibility (Phase 8) — mirrors `itemLock.ts`'s `Lockable`
|
|
367
|
+
* pattern exactly, but simpler: unlike a lock, hidden state carries no
|
|
368
|
+
* holder/ownership concept, so there's no analogue to `LockHolder`/
|
|
369
|
+
* `canUnlockItem`. A hidden object stays fully present in the Document
|
|
370
|
+
* (still serializes, persists, syncs, undoes/redoes) — it just skips
|
|
371
|
+
* rendering and hit-testing/selection candidacy. `hidden` absent or
|
|
372
|
+
* `false` means visible; this keeps every pre-Phase-8 document (which has
|
|
373
|
+
* no `hidden` field on any object at all) implicitly fully visible with
|
|
374
|
+
* zero migration needed.
|
|
375
|
+
*/
|
|
376
|
+
interface Hideable {
|
|
377
|
+
hidden?: boolean;
|
|
378
|
+
}
|
|
379
|
+
|
|
333
380
|
/** A kitchen timer sitting on the board. Remaining time is derived, not ticked. */
|
|
334
|
-
interface KitchenTimer extends Lockable {
|
|
381
|
+
interface KitchenTimer extends Lockable, Hideable {
|
|
335
382
|
id: string;
|
|
336
383
|
x: number;
|
|
337
384
|
y: number;
|
|
@@ -345,6 +392,146 @@ interface KitchenTimer extends Lockable {
|
|
|
345
392
|
runningSince?: number;
|
|
346
393
|
}
|
|
347
394
|
|
|
395
|
+
interface RectangleObject extends Lockable, Hideable {
|
|
396
|
+
id: string;
|
|
397
|
+
x: number;
|
|
398
|
+
y: number;
|
|
399
|
+
width: number;
|
|
400
|
+
height: number;
|
|
401
|
+
fill?: string;
|
|
402
|
+
stroke?: string;
|
|
403
|
+
strokeWidth?: number;
|
|
404
|
+
/** Corner radius in board units; clamped to at most half the shorter side at render time. */
|
|
405
|
+
cornerRadius?: number;
|
|
406
|
+
/** `[0, 1]`; undefined means fully opaque (Phase 4). */
|
|
407
|
+
opacity?: number;
|
|
408
|
+
/**
|
|
409
|
+
* Radians, about the shape's own center `(x + width/2, y - height/2)`.
|
|
410
|
+
* Undefined means 0 (Phase 3). `x`/`y`/`width`/`height` stay in the
|
|
411
|
+
* shape's own unrotated local frame — rotation is a separate, applied-last
|
|
412
|
+
* transform, not baked into them, matching how Stroke/CustomBoardObject
|
|
413
|
+
* keep geometry and placement independent via their own `matrix`.
|
|
414
|
+
*/
|
|
415
|
+
rotation?: number;
|
|
416
|
+
}
|
|
417
|
+
interface EllipseObject extends Lockable, Hideable {
|
|
418
|
+
id: string;
|
|
419
|
+
x: number;
|
|
420
|
+
y: number;
|
|
421
|
+
width: number;
|
|
422
|
+
height: number;
|
|
423
|
+
fill?: string;
|
|
424
|
+
stroke?: string;
|
|
425
|
+
strokeWidth?: number;
|
|
426
|
+
/** `[0, 1]`; undefined means fully opaque (Phase 4). */
|
|
427
|
+
opacity?: number;
|
|
428
|
+
/** Radians, about the shape's own center — see RectangleObject's `rotation` doc. */
|
|
429
|
+
rotation?: number;
|
|
430
|
+
}
|
|
431
|
+
/** `"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. */
|
|
432
|
+
type ArrowHeadStyle = "triangle" | "none";
|
|
433
|
+
interface LineObject extends Lockable, Hideable {
|
|
434
|
+
id: string;
|
|
435
|
+
start: BoardPoint;
|
|
436
|
+
end: BoardPoint;
|
|
437
|
+
stroke?: string;
|
|
438
|
+
strokeWidth?: number;
|
|
439
|
+
opacity?: number;
|
|
440
|
+
}
|
|
441
|
+
/** An endpoint anchored to a normalized position on another object's bounds. */
|
|
442
|
+
interface ConnectorBinding {
|
|
443
|
+
objectId: string;
|
|
444
|
+
/** Horizontal and vertical fractions of the target's axis-aligned bounds. */
|
|
445
|
+
x: number;
|
|
446
|
+
y: number;
|
|
447
|
+
}
|
|
448
|
+
interface ArrowObject extends Lockable, Hideable {
|
|
449
|
+
/** Omitted for legacy arrows. Connectors share Arrow's persistence/history contract. */
|
|
450
|
+
routing?: "straight" | "curved" | "polyline";
|
|
451
|
+
/** Intermediate board-space vertices for a multi-point connector. */
|
|
452
|
+
waypoints?: BoardPoint[];
|
|
453
|
+
startBinding?: ConnectorBinding;
|
|
454
|
+
endBinding?: ConnectorBinding;
|
|
455
|
+
id: string;
|
|
456
|
+
start: BoardPoint;
|
|
457
|
+
end: BoardPoint;
|
|
458
|
+
head?: ArrowHeadStyle;
|
|
459
|
+
stroke?: string;
|
|
460
|
+
strokeWidth?: number;
|
|
461
|
+
opacity?: number;
|
|
462
|
+
}
|
|
463
|
+
/**
|
|
464
|
+
* Triangle(3)/Diamond(4)/Pentagon(5)/Hexagon(6)/Octagon(8) as one shared
|
|
465
|
+
* type instead of five near-duplicate interfaces — a regular N-gon
|
|
466
|
+
* inscribed in the same `x`/`y`/`width`/`height`/`rotation` bounding box
|
|
467
|
+
* Rectangle already uses, parameterized by `sides`. Diamond is exactly a
|
|
468
|
+
* 4-sided regular polygon with vertex 0 pointing right (not up, like
|
|
469
|
+
* Triangle/Pentagon/Hexagon) — see `polygonGeometry.ts`'s
|
|
470
|
+
* `polygonStartAngle`, which encodes each side count's own vertex
|
|
471
|
+
* orientation so the outline always matches the legacy drag-preview shape.
|
|
472
|
+
*/
|
|
473
|
+
interface PolygonObject extends Lockable, Hideable {
|
|
474
|
+
id: string;
|
|
475
|
+
x: number;
|
|
476
|
+
y: number;
|
|
477
|
+
width: number;
|
|
478
|
+
height: number;
|
|
479
|
+
sides: 3 | 4 | 5 | 6 | 8;
|
|
480
|
+
fill?: string;
|
|
481
|
+
stroke?: string;
|
|
482
|
+
strokeWidth?: number;
|
|
483
|
+
opacity?: number;
|
|
484
|
+
/** Radians, about the shape's own center — see RectangleObject's `rotation` doc. */
|
|
485
|
+
rotation?: number;
|
|
486
|
+
}
|
|
487
|
+
/** 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`). */
|
|
488
|
+
interface StarObject extends Lockable, Hideable {
|
|
489
|
+
id: string;
|
|
490
|
+
x: number;
|
|
491
|
+
y: number;
|
|
492
|
+
width: number;
|
|
493
|
+
height: number;
|
|
494
|
+
/** Vertex count; today's only shipped preset is 5, matching the legacy tool. */
|
|
495
|
+
points: number;
|
|
496
|
+
/** `(0, 1)` — inner vertex radius as a fraction of the outer radius. */
|
|
497
|
+
innerRadiusRatio: number;
|
|
498
|
+
fill?: string;
|
|
499
|
+
stroke?: string;
|
|
500
|
+
strokeWidth?: number;
|
|
501
|
+
opacity?: number;
|
|
502
|
+
rotation?: number;
|
|
503
|
+
}
|
|
504
|
+
/** 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. */
|
|
505
|
+
interface HeartObject extends Lockable, Hideable {
|
|
506
|
+
id: string;
|
|
507
|
+
x: number;
|
|
508
|
+
y: number;
|
|
509
|
+
width: number;
|
|
510
|
+
height: number;
|
|
511
|
+
fill?: string;
|
|
512
|
+
stroke?: string;
|
|
513
|
+
strokeWidth?: number;
|
|
514
|
+
opacity?: number;
|
|
515
|
+
rotation?: number;
|
|
516
|
+
}
|
|
517
|
+
/**
|
|
518
|
+
* A logical grouping of other board objects (Phase 3 — Selection,
|
|
519
|
+
* Transformation & Grouping). Deliberately has no `x`/`y`/`transform` of its
|
|
520
|
+
* own — a group's bounds are always derived on demand from its (recursively
|
|
521
|
+
* resolved) children, and "moving/rotating/scaling the group" is exactly a
|
|
522
|
+
* multi-object transform applied to those children, nothing more. A group
|
|
523
|
+
* has no renderer/mesh of its own; its only visual presence is the
|
|
524
|
+
* selection gizmo's bounding box while it's the current selection.
|
|
525
|
+
*
|
|
526
|
+
* `children` may itself contain other group ids (nested groups) — expanding
|
|
527
|
+
* a group into its leaf members is always done by the caller (recursively,
|
|
528
|
+
* with cycle protection), never assumed here.
|
|
529
|
+
*/
|
|
530
|
+
interface GroupObject extends Lockable, Hideable {
|
|
531
|
+
id: string;
|
|
532
|
+
children: string[];
|
|
533
|
+
}
|
|
534
|
+
|
|
348
535
|
interface BoardPoint {
|
|
349
536
|
x: number;
|
|
350
537
|
y: number;
|
|
@@ -358,9 +545,14 @@ interface StrokePoint extends BoardPoint {
|
|
|
358
545
|
*/
|
|
359
546
|
erase?: number;
|
|
360
547
|
}
|
|
361
|
-
/**
|
|
362
|
-
|
|
363
|
-
|
|
548
|
+
/**
|
|
549
|
+
* Which drawing tool made a stroke; undefined means marker (back-compat).
|
|
550
|
+
* `"shape"` (rect/ellipse/line/arrow/polygon/star/heart) renders at its
|
|
551
|
+
* exact configured width with no pressure variance or end taper — a
|
|
552
|
+
* geometric outline, not an expressive ink mark.
|
|
553
|
+
*/
|
|
554
|
+
type StrokeTool = "marker" | "highlighter" | "shape";
|
|
555
|
+
interface Stroke extends Lockable, Hideable {
|
|
364
556
|
id: string;
|
|
365
557
|
color: string;
|
|
366
558
|
baseWidth: number;
|
|
@@ -376,7 +568,7 @@ interface Stroke extends Lockable {
|
|
|
376
568
|
clusterId?: string;
|
|
377
569
|
}
|
|
378
570
|
type SerializedPoint = [number, number, number, number];
|
|
379
|
-
interface SerializedStroke extends Lockable {
|
|
571
|
+
interface SerializedStroke extends Lockable, Hideable {
|
|
380
572
|
id: string;
|
|
381
573
|
color: string;
|
|
382
574
|
baseWidth: number;
|
|
@@ -386,7 +578,36 @@ interface SerializedStroke extends Lockable {
|
|
|
386
578
|
matrix?: [number, number, number, number, number, number];
|
|
387
579
|
clusterId?: string;
|
|
388
580
|
}
|
|
581
|
+
/** A named notebook containing sections. */
|
|
582
|
+
interface BoardNotebook {
|
|
583
|
+
readonly id: string;
|
|
584
|
+
readonly name: string;
|
|
585
|
+
readonly color: string;
|
|
586
|
+
}
|
|
587
|
+
/** A named section containing pages. */
|
|
588
|
+
interface BoardSection {
|
|
589
|
+
readonly id: string;
|
|
590
|
+
readonly notebookId: string;
|
|
591
|
+
readonly name: string;
|
|
592
|
+
readonly color: string;
|
|
593
|
+
}
|
|
594
|
+
/** An additional page. The first page remains in the document's top-level collections. */
|
|
595
|
+
interface SerializedBoardPage {
|
|
596
|
+
id: string;
|
|
597
|
+
sectionId?: string;
|
|
598
|
+
name: string;
|
|
599
|
+
content: SerializedDocument;
|
|
600
|
+
}
|
|
389
601
|
interface SerializedDocument {
|
|
602
|
+
/** Notebook and section metadata. Older boards use an implicit default of each. */
|
|
603
|
+
notebooks?: BoardNotebook[];
|
|
604
|
+
sections?: BoardSection[];
|
|
605
|
+
/** Section of the original first page; defaults to `default`. */
|
|
606
|
+
pageSectionId?: string;
|
|
607
|
+
/** Display name of the first page (id `default`). */
|
|
608
|
+
pageName?: string;
|
|
609
|
+
/** Additional pages, in sidebar order. Nested pages are not allowed. */
|
|
610
|
+
pages?: SerializedBoardPage[];
|
|
390
611
|
/** Absent in every historical document; current saves always write 1. */
|
|
391
612
|
schemaVersion?: 1;
|
|
392
613
|
strokes: SerializedStroke[];
|
|
@@ -402,6 +623,31 @@ interface SerializedDocument {
|
|
|
402
623
|
timers?: KitchenTimer[];
|
|
403
624
|
/** Absent in documents saved before Custom board objects existed (ticket #22). */
|
|
404
625
|
customObjects?: CustomBoardObject[];
|
|
626
|
+
/** Absent in documents saved before semantic Rectangle objects existed (Phase 2). */
|
|
627
|
+
rectangles?: RectangleObject[];
|
|
628
|
+
/** Absent in documents saved before semantic Ellipse objects existed (Phase 2). */
|
|
629
|
+
ellipses?: EllipseObject[];
|
|
630
|
+
/** Absent in documents saved before Groups existed (Phase 3). */
|
|
631
|
+
groups?: GroupObject[];
|
|
632
|
+
/** Absent in documents saved before semantic Line objects existed (Phase 4). */
|
|
633
|
+
lines?: LineObject[];
|
|
634
|
+
/** Absent in documents saved before semantic Arrow objects existed (Phase 4). */
|
|
635
|
+
arrows?: ArrowObject[];
|
|
636
|
+
/** Absent in documents saved before semantic Polygon objects existed (Phase 4). */
|
|
637
|
+
polygons?: PolygonObject[];
|
|
638
|
+
/** Absent in documents saved before semantic Star objects existed (Phase 4). */
|
|
639
|
+
stars?: StarObject[];
|
|
640
|
+
/** Absent in documents saved before semantic Heart objects existed (Phase 4). */
|
|
641
|
+
hearts?: HeartObject[];
|
|
642
|
+
/**
|
|
643
|
+
* Every content-object id (every type above except comments, which are
|
|
644
|
+
* host-synced and never enter this schema) in paint order, back to front.
|
|
645
|
+
* Absent in documents saved before per-object z-order existed (Phase 3) —
|
|
646
|
+
* migration synthesizes a default order preserving the old fixed-Z-band
|
|
647
|
+
* visual stacking exactly, so an existing document never visibly changes
|
|
648
|
+
* on load; only an explicit reorder action touches this from then on.
|
|
649
|
+
*/
|
|
650
|
+
objectOrder?: string[];
|
|
405
651
|
}
|
|
406
652
|
/**
|
|
407
653
|
* One collaborator's vote on a note. One per person; toggling removes it.
|
|
@@ -415,7 +661,7 @@ interface NoteVote {
|
|
|
415
661
|
* A sticky note: content floating above the board at a z-offset (pillar 3 —
|
|
416
662
|
* depth as an organizational axis). Center position in board space.
|
|
417
663
|
*/
|
|
418
|
-
interface StickyNote extends Lockable {
|
|
664
|
+
interface StickyNote extends Lockable, Hideable {
|
|
419
665
|
id: string;
|
|
420
666
|
x: number;
|
|
421
667
|
y: number;
|
|
@@ -433,7 +679,7 @@ interface StickyNote extends Lockable {
|
|
|
433
679
|
* top-left corner; lines flow downward (-y). Text joins the clustering
|
|
434
680
|
* system like handwriting (build prompt §6.4).
|
|
435
681
|
*/
|
|
436
|
-
interface TextBlock extends Lockable {
|
|
682
|
+
interface TextBlock extends Lockable, Hideable {
|
|
437
683
|
id: string;
|
|
438
684
|
x: number;
|
|
439
685
|
y: number;
|
|
@@ -447,7 +693,7 @@ interface TextBlock extends Lockable {
|
|
|
447
693
|
* Interactive structured table on the board. Position (x, y) is top-left in board units.
|
|
448
694
|
* Cells are indexed as `${row},${col}` keys mapping to cell text content.
|
|
449
695
|
*/
|
|
450
|
-
interface TableBlock extends Lockable {
|
|
696
|
+
interface TableBlock extends Lockable, Hideable {
|
|
451
697
|
id: string;
|
|
452
698
|
x: number;
|
|
453
699
|
y: number;
|
|
@@ -464,7 +710,7 @@ interface TableBlock extends Lockable {
|
|
|
464
710
|
* An imported image block on the board plane.
|
|
465
711
|
* Coordinates (x, y) represent the center of the image in board space.
|
|
466
712
|
*/
|
|
467
|
-
interface ImageBlock extends Lockable {
|
|
713
|
+
interface ImageBlock extends Lockable, Hideable {
|
|
468
714
|
id: string;
|
|
469
715
|
/**
|
|
470
716
|
* A legacy, read-only data URL (or, historically, an arbitrary string) —
|
|
@@ -487,13 +733,23 @@ interface ImageBlock extends Lockable {
|
|
|
487
733
|
stamp?: string;
|
|
488
734
|
}
|
|
489
735
|
|
|
736
|
+
declare const strokeIdBrand: unique symbol;
|
|
737
|
+
type StrokeId = string & {
|
|
738
|
+
readonly [strokeIdBrand]: "StrokeId";
|
|
739
|
+
};
|
|
740
|
+
|
|
490
741
|
declare const CURRENT_DOCUMENT_SCHEMA_VERSION: 1;
|
|
491
742
|
type CurrentSerializedStroke = Omit<SerializedStroke, "id"> & {
|
|
492
743
|
id: StrokeId;
|
|
493
744
|
};
|
|
494
|
-
interface CurrentSerializedDocument extends Required<SerializedDocument
|
|
745
|
+
interface CurrentSerializedDocument extends Required<Omit<SerializedDocument, "pages" | "pageName" | "notebooks" | "sections" | "pageSectionId">> {
|
|
495
746
|
schemaVersion: typeof CURRENT_DOCUMENT_SCHEMA_VERSION;
|
|
496
747
|
strokes: CurrentSerializedStroke[];
|
|
748
|
+
pageName?: string;
|
|
749
|
+
notebooks?: BoardNotebook[];
|
|
750
|
+
sections?: BoardSection[];
|
|
751
|
+
pageSectionId?: string;
|
|
752
|
+
pages?: SerializedBoardPage[];
|
|
497
753
|
}
|
|
498
754
|
|
|
499
755
|
interface SearchableComment {
|
|
@@ -519,6 +775,490 @@ interface SearchHit {
|
|
|
519
775
|
/** A stamp is a small sticker dropped on the board — not ink, not a photo. */
|
|
520
776
|
type StampKind = "star" | "check" | "ship" | "heart" | "plus" | "fire";
|
|
521
777
|
|
|
778
|
+
declare const ICON_LIBRARY: readonly [{
|
|
779
|
+
readonly name: "lucide:check";
|
|
780
|
+
readonly label: "Check";
|
|
781
|
+
readonly category: "Status";
|
|
782
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M20 6L9 17l-5-5\"/>";
|
|
783
|
+
}, {
|
|
784
|
+
readonly name: "lucide:x";
|
|
785
|
+
readonly label: "Cross";
|
|
786
|
+
readonly category: "Status";
|
|
787
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M18 6L6 18M6 6l12 12\"/>";
|
|
788
|
+
}, {
|
|
789
|
+
readonly name: "lucide:star";
|
|
790
|
+
readonly label: "Star";
|
|
791
|
+
readonly category: "Status";
|
|
792
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M11.525 2.295a.53.53 0 0 1 .95 0l2.31 4.679a2.12 2.12 0 0 0 1.595 1.16l5.166.756a.53.53 0 0 1 .294.904l-3.736 3.638a2.12 2.12 0 0 0-.611 1.878l.882 5.14a.53.53 0 0 1-.771.56l-4.618-2.428a2.12 2.12 0 0 0-1.973 0L6.396 21.01a.53.53 0 0 1-.77-.56l.881-5.139a2.12 2.12 0 0 0-.611-1.879L2.16 9.795a.53.53 0 0 1 .294-.906l5.165-.755a2.12 2.12 0 0 0 1.597-1.16z\"/>";
|
|
793
|
+
}, {
|
|
794
|
+
readonly name: "lucide:heart";
|
|
795
|
+
readonly label: "Heart";
|
|
796
|
+
readonly category: "Status";
|
|
797
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M2 9.5a5.5 5.5 0 0 1 9.591-3.676a.56.56 0 0 0 .818 0A5.49 5.49 0 0 1 22 9.5c0 2.29-1.5 4-3 5.5l-5.492 5.313a2 2 0 0 1-3 .019L5 15c-1.5-1.5-3-3.2-3-5.5\"/>";
|
|
798
|
+
}, {
|
|
799
|
+
readonly name: "lucide:flag";
|
|
800
|
+
readonly label: "Flag";
|
|
801
|
+
readonly category: "Status";
|
|
802
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M4 22V4a1 1 0 0 1 .4-.8A6 6 0 0 1 8 2c3 0 5 2 7.333 2q2 0 3.067-.8A1 1 0 0 1 20 4v10a1 1 0 0 1-.4.8A6 6 0 0 1 16 16c-3 0-5-2-8-2a6 6 0 0 0-4 1.528\"/>";
|
|
803
|
+
}, {
|
|
804
|
+
readonly name: "lucide:bookmark";
|
|
805
|
+
readonly label: "Bookmark";
|
|
806
|
+
readonly category: "Status";
|
|
807
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M17 3a2 2 0 0 1 2 2v15a1 1 0 0 1-1.496.868l-4.512-2.578a2 2 0 0 0-1.984 0l-4.512 2.578A1 1 0 0 1 5 20V5a2 2 0 0 1 2-2z\"/>";
|
|
808
|
+
}, {
|
|
809
|
+
readonly name: "lucide:bell";
|
|
810
|
+
readonly label: "Bell";
|
|
811
|
+
readonly category: "Status";
|
|
812
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10.268 21a2 2 0 0 0 3.464 0m-10.47-5.674A1 1 0 0 0 4 17h16a1 1 0 0 0 .74-1.673C19.41 13.956 18 12.499 18 8A6 6 0 0 0 6 8c0 4.499-1.411 5.956-2.738 7.326\"/>";
|
|
813
|
+
}, {
|
|
814
|
+
readonly name: "lucide:info";
|
|
815
|
+
readonly label: "Info";
|
|
816
|
+
readonly category: "Status";
|
|
817
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 16v-4m0-4h.01\"/></g>";
|
|
818
|
+
}, {
|
|
819
|
+
readonly name: "lucide:circle-alert";
|
|
820
|
+
readonly label: "Alert";
|
|
821
|
+
readonly category: "Status";
|
|
822
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 8v4m0 4h.01\"/></g>";
|
|
823
|
+
}, {
|
|
824
|
+
readonly name: "lucide:circle-question-mark";
|
|
825
|
+
readonly label: "Question";
|
|
826
|
+
readonly category: "Status";
|
|
827
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3m.08 4h.01\"/></g>";
|
|
828
|
+
}, {
|
|
829
|
+
readonly name: "lucide:thumbs-up";
|
|
830
|
+
readonly label: "Thumbs up";
|
|
831
|
+
readonly category: "Status";
|
|
832
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 5.88L14 10h5.83a2 2 0 0 1 1.92 2.56l-2.33 8A2 2 0 0 1 17.5 22H4a2 2 0 0 1-2-2v-8a2 2 0 0 1 2-2h2.76a2 2 0 0 0 1.79-1.11L12 2a3.13 3.13 0 0 1 3 3.88M7 10v12\"/>";
|
|
833
|
+
}, {
|
|
834
|
+
readonly name: "lucide:thumbs-down";
|
|
835
|
+
readonly label: "Thumbs down";
|
|
836
|
+
readonly category: "Status";
|
|
837
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M9 18.12L10 14H4.17a2 2 0 0 1-1.92-2.56l2.33-8A2 2 0 0 1 6.5 2H20a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2h-2.76a2 2 0 0 0-1.79 1.11L12 22a3.13 3.13 0 0 1-3-3.88M17 14V2\"/>";
|
|
838
|
+
}, {
|
|
839
|
+
readonly name: "lucide:arrow-up";
|
|
840
|
+
readonly label: "Arrow up";
|
|
841
|
+
readonly category: "Arrows";
|
|
842
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m5 12l7-7l7 7m-7 7V5\"/>";
|
|
843
|
+
}, {
|
|
844
|
+
readonly name: "lucide:arrow-down";
|
|
845
|
+
readonly label: "Arrow down";
|
|
846
|
+
readonly category: "Arrows";
|
|
847
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 5v14m7-7l-7 7l-7-7\"/>";
|
|
848
|
+
}, {
|
|
849
|
+
readonly name: "lucide:arrow-left";
|
|
850
|
+
readonly label: "Arrow left";
|
|
851
|
+
readonly category: "Arrows";
|
|
852
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m12 19l-7-7l7-7m7 7H5\"/>";
|
|
853
|
+
}, {
|
|
854
|
+
readonly name: "lucide:arrow-right";
|
|
855
|
+
readonly label: "Arrow right";
|
|
856
|
+
readonly category: "Arrows";
|
|
857
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M5 12h14m-7-7l7 7l-7 7\"/>";
|
|
858
|
+
}, {
|
|
859
|
+
readonly name: "lucide:arrow-up-right";
|
|
860
|
+
readonly label: "Arrow up-right";
|
|
861
|
+
readonly category: "Arrows";
|
|
862
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M7 7h10v10M7 17L17 7\"/>";
|
|
863
|
+
}, {
|
|
864
|
+
readonly name: "lucide:refresh-cw";
|
|
865
|
+
readonly label: "Refresh";
|
|
866
|
+
readonly category: "Arrows";
|
|
867
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M3 12a9 9 0 0 1 9-9a9.75 9.75 0 0 1 6.74 2.74L21 8\"/><path d=\"M21 3v5h-5m5 4a9 9 0 0 1-9 9a9.75 9.75 0 0 1-6.74-2.74L3 16\"/><path d=\"M8 16H3v5\"/></g>";
|
|
868
|
+
}, {
|
|
869
|
+
readonly name: "lucide:move";
|
|
870
|
+
readonly label: "Move";
|
|
871
|
+
readonly category: "Arrows";
|
|
872
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 2v20m3-3l-3 3l-3-3M19 9l3 3l-3 3M2 12h20M5 9l-3 3l3 3M9 5l3-3l3 3\"/>";
|
|
873
|
+
}, {
|
|
874
|
+
readonly name: "lucide:shuffle";
|
|
875
|
+
readonly label: "Shuffle";
|
|
876
|
+
readonly category: "Arrows";
|
|
877
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"m18 14l4 4l-4 4m0-20l4 4l-4 4\"/><path d=\"M2 18h1.973a4 4 0 0 0 3.3-1.7l5.454-8.6a4 4 0 0 1 3.3-1.7H22M2 6h1.972a4 4 0 0 1 3.6 2.2M22 18h-6.041a4 4 0 0 1-3.3-1.8l-.359-.45\"/></g>";
|
|
878
|
+
}, {
|
|
879
|
+
readonly name: "lucide:user";
|
|
880
|
+
readonly label: "User";
|
|
881
|
+
readonly category: "People";
|
|
882
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2\"/><circle cx=\"12\" cy=\"7\" r=\"4\"/></g>";
|
|
883
|
+
}, {
|
|
884
|
+
readonly name: "lucide:users";
|
|
885
|
+
readonly label: "Users";
|
|
886
|
+
readonly category: "People";
|
|
887
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2M16 3.128a4 4 0 0 1 0 7.744M22 21v-2a4 4 0 0 0-3-3.87\"/><circle cx=\"9\" cy=\"7\" r=\"4\"/></g>";
|
|
888
|
+
}, {
|
|
889
|
+
readonly name: "lucide:briefcase";
|
|
890
|
+
readonly label: "Briefcase";
|
|
891
|
+
readonly category: "People";
|
|
892
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M16 20V4a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2v16\"/><rect width=\"20\" height=\"14\" x=\"2\" y=\"6\" rx=\"2\"/></g>";
|
|
893
|
+
}, {
|
|
894
|
+
readonly name: "lucide:building";
|
|
895
|
+
readonly label: "Building";
|
|
896
|
+
readonly category: "People";
|
|
897
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M12 10h.01M12 14h.01M12 6h.01M16 10h.01M16 14h.01M16 6h.01M8 10h.01M8 14h.01M8 6h.01M9 22v-3a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v3\"/><rect width=\"16\" height=\"20\" x=\"4\" y=\"2\" rx=\"2\"/></g>";
|
|
898
|
+
}, {
|
|
899
|
+
readonly name: "lucide:calendar";
|
|
900
|
+
readonly label: "Calendar";
|
|
901
|
+
readonly category: "People";
|
|
902
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M8 2v3m8-3v3\"/><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\"/><path d=\"M3 9h18\"/></g>";
|
|
903
|
+
}, {
|
|
904
|
+
readonly name: "lucide:clock";
|
|
905
|
+
readonly label: "Clock";
|
|
906
|
+
readonly category: "People";
|
|
907
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M12 6v6l4 2\"/></g>";
|
|
908
|
+
}, {
|
|
909
|
+
readonly name: "lucide:mail";
|
|
910
|
+
readonly label: "Mail";
|
|
911
|
+
readonly category: "People";
|
|
912
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"m22 7l-8.991 5.727a2 2 0 0 1-2.009 0L2 7\"/><rect width=\"20\" height=\"16\" x=\"2\" y=\"4\" rx=\"2\"/></g>";
|
|
913
|
+
}, {
|
|
914
|
+
readonly name: "lucide:message-circle";
|
|
915
|
+
readonly label: "Message";
|
|
916
|
+
readonly category: "People";
|
|
917
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M2.992 16.342a2 2 0 0 1 .094 1.167l-1.065 3.29a1 1 0 0 0 1.236 1.168l3.413-.998a2 2 0 0 1 1.099.092a10 10 0 1 0-4.777-4.719\"/>";
|
|
918
|
+
}, {
|
|
919
|
+
readonly name: "lucide:file";
|
|
920
|
+
readonly label: "File";
|
|
921
|
+
readonly category: "Objects";
|
|
922
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z\"/><path d=\"M14 2v5a1 1 0 0 0 1 1h5\"/></g>";
|
|
923
|
+
}, {
|
|
924
|
+
readonly name: "lucide:folder";
|
|
925
|
+
readonly label: "Folder";
|
|
926
|
+
readonly category: "Objects";
|
|
927
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z\"/>";
|
|
928
|
+
}, {
|
|
929
|
+
readonly name: "lucide:image";
|
|
930
|
+
readonly label: "Image";
|
|
931
|
+
readonly category: "Objects";
|
|
932
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><rect width=\"18\" height=\"18\" x=\"3\" y=\"3\" rx=\"2\" ry=\"2\"/><circle cx=\"9\" cy=\"9\" r=\"2\"/><path d=\"m21 15l-3.086-3.086a2 2 0 0 0-2.828 0L6 21\"/></g>";
|
|
933
|
+
}, {
|
|
934
|
+
readonly name: "lucide:link";
|
|
935
|
+
readonly label: "Link";
|
|
936
|
+
readonly category: "Objects";
|
|
937
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71\"/><path d=\"M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71\"/></g>";
|
|
938
|
+
}, {
|
|
939
|
+
readonly name: "lucide:paperclip";
|
|
940
|
+
readonly label: "Paperclip";
|
|
941
|
+
readonly category: "Objects";
|
|
942
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m16 6l-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551\"/>";
|
|
943
|
+
}, {
|
|
944
|
+
readonly name: "lucide:pin";
|
|
945
|
+
readonly label: "Pin";
|
|
946
|
+
readonly category: "Objects";
|
|
947
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 17v5M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1a2 2 0 0 0 0-4H8a2 2 0 0 0 0 4a1 1 0 0 1 1 1z\"/>";
|
|
948
|
+
}, {
|
|
949
|
+
readonly name: "lucide:map-pin";
|
|
950
|
+
readonly label: "Map pin";
|
|
951
|
+
readonly category: "Objects";
|
|
952
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M20 10c0 4.993-5.539 10.193-7.399 11.799a1 1 0 0 1-1.202 0C9.539 20.193 4 14.993 4 10a8 8 0 0 1 16 0\"/><circle cx=\"12\" cy=\"10\" r=\"3\"/></g>";
|
|
953
|
+
}, {
|
|
954
|
+
readonly name: "lucide:search";
|
|
955
|
+
readonly label: "Search";
|
|
956
|
+
readonly category: "Objects";
|
|
957
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"m21 21l-4.34-4.34\"/><circle cx=\"11\" cy=\"11\" r=\"8\"/></g>";
|
|
958
|
+
}, {
|
|
959
|
+
readonly name: "lucide:settings";
|
|
960
|
+
readonly label: "Settings";
|
|
961
|
+
readonly category: "Objects";
|
|
962
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M9.671 4.136a2.34 2.34 0 0 1 4.659 0a2.34 2.34 0 0 0 3.319 1.915a2.34 2.34 0 0 1 2.33 4.033a2.34 2.34 0 0 0 0 3.831a2.34 2.34 0 0 1-2.33 4.033a2.34 2.34 0 0 0-3.319 1.915a2.34 2.34 0 0 1-4.659 0a2.34 2.34 0 0 0-3.32-1.915a2.34 2.34 0 0 1-2.33-4.033a2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915\"/><circle cx=\"12\" cy=\"12\" r=\"3\"/></g>";
|
|
963
|
+
}, {
|
|
964
|
+
readonly name: "lucide:trash-2";
|
|
965
|
+
readonly label: "Trash";
|
|
966
|
+
readonly category: "Objects";
|
|
967
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 11v6m4-6v6m5-11v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6M3 6h18M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2\"/>";
|
|
968
|
+
}, {
|
|
969
|
+
readonly name: "lucide:lock";
|
|
970
|
+
readonly label: "Lock";
|
|
971
|
+
readonly category: "Objects";
|
|
972
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><rect width=\"18\" height=\"11\" x=\"3\" y=\"11\" rx=\"2\" ry=\"2\"/><path d=\"M7 11V7a5 5 0 0 1 10 0v4\"/></g>";
|
|
973
|
+
}, {
|
|
974
|
+
readonly name: "lucide:key";
|
|
975
|
+
readonly label: "Key";
|
|
976
|
+
readonly category: "Objects";
|
|
977
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"m15.5 7.5l2.3 2.3a1 1 0 0 0 1.4 0l2.1-2.1a1 1 0 0 0 0-1.4L19 4m2-2l-9.6 9.6\"/><circle cx=\"7.5\" cy=\"15.5\" r=\"5.5\"/></g>";
|
|
978
|
+
}, {
|
|
979
|
+
readonly name: "lucide:code";
|
|
980
|
+
readonly label: "Code";
|
|
981
|
+
readonly category: "Tech";
|
|
982
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"m16 18l6-6l-6-6M8 6l-6 6l6 6\"/>";
|
|
983
|
+
}, {
|
|
984
|
+
readonly name: "lucide:database";
|
|
985
|
+
readonly label: "Database";
|
|
986
|
+
readonly category: "Tech";
|
|
987
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><ellipse cx=\"12\" cy=\"5\" rx=\"9\" ry=\"3\"/><path d=\"M3 5v14a9 3 0 0 0 18 0V5\"/><path d=\"M3 12a9 3 0 0 0 18 0\"/></g>";
|
|
988
|
+
}, {
|
|
989
|
+
readonly name: "lucide:server";
|
|
990
|
+
readonly label: "Server";
|
|
991
|
+
readonly category: "Tech";
|
|
992
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><rect width=\"20\" height=\"8\" x=\"2\" y=\"2\" rx=\"2\" ry=\"2\"/><rect width=\"20\" height=\"8\" x=\"2\" y=\"14\" rx=\"2\" ry=\"2\"/><path d=\"M6 6h.01M6 18h.01\"/></g>";
|
|
993
|
+
}, {
|
|
994
|
+
readonly name: "lucide:cloud";
|
|
995
|
+
readonly label: "Cloud";
|
|
996
|
+
readonly category: "Tech";
|
|
997
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M17.5 19H9a7 7 0 1 1 6.71-9h1.79a4.5 4.5 0 1 1 0 9\"/>";
|
|
998
|
+
}, {
|
|
999
|
+
readonly name: "lucide:git-branch";
|
|
1000
|
+
readonly label: "Branch";
|
|
1001
|
+
readonly category: "Tech";
|
|
1002
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M15 6a9 9 0 0 0-9 9V3\"/><circle cx=\"18\" cy=\"6\" r=\"3\"/><circle cx=\"6\" cy=\"18\" r=\"3\"/></g>";
|
|
1003
|
+
}, {
|
|
1004
|
+
readonly name: "lucide:terminal";
|
|
1005
|
+
readonly label: "Terminal";
|
|
1006
|
+
readonly category: "Tech";
|
|
1007
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M12 19h8M4 17l6-6l-6-6\"/>";
|
|
1008
|
+
}, {
|
|
1009
|
+
readonly name: "lucide:cpu";
|
|
1010
|
+
readonly label: "CPU";
|
|
1011
|
+
readonly category: "Tech";
|
|
1012
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M12 20v2m0-20v2m5 16v2m0-20v2M2 12h2m-2 5h2M2 7h2m16 5h2m-2 5h2M20 7h2M7 20v2M7 2v2\"/><rect width=\"16\" height=\"16\" x=\"4\" y=\"4\" rx=\"2\"/><rect width=\"8\" height=\"8\" x=\"8\" y=\"8\" rx=\"1\"/></g>";
|
|
1013
|
+
}, {
|
|
1014
|
+
readonly name: "lucide:bug";
|
|
1015
|
+
readonly label: "Bug";
|
|
1016
|
+
readonly category: "Tech";
|
|
1017
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M12 20v-9m2-4a4 4 0 0 1 4 4v3a6 6 0 0 1-12 0v-3a4 4 0 0 1 4-4zm.12-3.12L16 2\"/><path d=\"M21 21a4 4 0 0 0-3.81-4M21 5a4 4 0 0 1-3.55 3.97M22 13h-4M3 21a4 4 0 0 1 3.81-4M3 5a4 4 0 0 0 3.55 3.97M6 13H2M8 2l1.88 1.88M9 7.13V6a3 3 0 1 1 6 0v1.13\"/></g>";
|
|
1018
|
+
}, {
|
|
1019
|
+
readonly name: "lucide:lightbulb";
|
|
1020
|
+
readonly label: "Idea";
|
|
1021
|
+
readonly category: "Ideas";
|
|
1022
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15 14c.2-1 .7-1.7 1.5-2.5c1-.9 1.5-2.2 1.5-3.5A6 6 0 0 0 6 8c0 1 .2 2.2 1.5 3.5c.7.7 1.3 1.5 1.5 2.5m0 4h6m-5 4h4\"/>";
|
|
1023
|
+
}, {
|
|
1024
|
+
readonly name: "lucide:zap";
|
|
1025
|
+
readonly label: "Zap";
|
|
1026
|
+
readonly category: "Ideas";
|
|
1027
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M15.914 4a1.5 1.5 0 0 0-2.474-1.561l-9 9A1.5 1.5 0 0 0 5.5 14h4.002a.5.5 0 0 1 .471.666L8.086 20a1.5 1.5 0 0 0 2.475 1.56l9-9A1.5 1.5 0 0 0 18.5 10h-3.997a.5.5 0 0 1-.472-.667z\"/>";
|
|
1028
|
+
}, {
|
|
1029
|
+
readonly name: "lucide:target";
|
|
1030
|
+
readonly label: "Target";
|
|
1031
|
+
readonly category: "Ideas";
|
|
1032
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><circle cx=\"12\" cy=\"12\" r=\"6\"/><circle cx=\"12\" cy=\"12\" r=\"2\"/></g>";
|
|
1033
|
+
}, {
|
|
1034
|
+
readonly name: "lucide:rocket";
|
|
1035
|
+
readonly label: "Rocket";
|
|
1036
|
+
readonly category: "Ideas";
|
|
1037
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M12 15v5s3.03-.55 4-2c1.08-1.62 0-5 0-5M4.5 16.5c-1.5 1.26-2 5-2 5s3.74-.5 5-2c.71-.84.7-2.13-.09-2.91a2.18 2.18 0 0 0-2.91-.09\"/><path d=\"M9 12a22 22 0 0 1 2-3.95A12.88 12.88 0 0 1 22 2c0 2.72-.78 7.5-6 11a22.4 22.4 0 0 1-4 2z\"/><path d=\"M9 12H4s.55-3.03 2-4c1.62-1.08 5 .05 5 .05\"/></g>";
|
|
1038
|
+
}, {
|
|
1039
|
+
readonly name: "lucide:trophy";
|
|
1040
|
+
readonly label: "Trophy";
|
|
1041
|
+
readonly category: "Ideas";
|
|
1042
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M10 14.66V17a1 1 0 0 1-1 1a2 2 0 0 0-2 2v2m7-7.34V17a1 1 0 0 0 1 1a2 2 0 0 1 2 2v2m.916-12H19.5A2.5 2.5 0 0 0 22 7.5V5a1 1 0 0 0-1-1h-3M4 22h16\"/><path d=\"M6 9a6 6 0 0 0 12 0V3a1 1 0 0 0-1-1H7a1 1 0 0 0-1 1z\"/><path d=\"M6.084 10H4.5A2.5 2.5 0 0 1 2 7.5V5a1 1 0 0 1 1-1h3\"/></g>";
|
|
1043
|
+
}, {
|
|
1044
|
+
readonly name: "lucide:gift";
|
|
1045
|
+
readonly label: "Gift";
|
|
1046
|
+
readonly category: "Ideas";
|
|
1047
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><path d=\"M12 7v14m8-10v8a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2v-8m3.5-4a1 1 0 0 1 0-5A4.8 8 0 0 1 12 7a4.8 8 0 0 1 4.5-5a1 1 0 0 1 0 5\"/><rect width=\"18\" height=\"4\" x=\"3\" y=\"7\" rx=\"1\"/></g>";
|
|
1048
|
+
}, {
|
|
1049
|
+
readonly name: "lucide:coffee";
|
|
1050
|
+
readonly label: "Coffee";
|
|
1051
|
+
readonly category: "Ideas";
|
|
1052
|
+
readonly body: "<path fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\" d=\"M10 2v2m4-2v2m2 4a1 1 0 0 1 1 1v8a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4V9a1 1 0 0 1 1-1h14a4 4 0 1 1 0 8h-1M6 2v2\"/>";
|
|
1053
|
+
}, {
|
|
1054
|
+
readonly name: "lucide:smile";
|
|
1055
|
+
readonly label: "Smile";
|
|
1056
|
+
readonly category: "Ideas";
|
|
1057
|
+
readonly body: "<g fill=\"none\" stroke=\"currentColor\" stroke-linecap=\"round\" stroke-linejoin=\"round\" stroke-width=\"2\"><circle cx=\"12\" cy=\"12\" r=\"10\"/><path d=\"M8 14s1.5 2 4 2s4-2 4-2M9 9h.01M15 9h.01\"/></g>";
|
|
1058
|
+
}];
|
|
1059
|
+
/** Every vendored icon's id, as a literal union. */
|
|
1060
|
+
type IconName = (typeof ICON_LIBRARY)[number]["name"];
|
|
1061
|
+
|
|
1062
|
+
declare const EMOJI_LIBRARY: readonly [{
|
|
1063
|
+
readonly name: "emoji:grinning";
|
|
1064
|
+
readonly label: "Grinning face";
|
|
1065
|
+
readonly category: "Smileys";
|
|
1066
|
+
readonly char: "😀";
|
|
1067
|
+
}, {
|
|
1068
|
+
readonly name: "emoji:joy";
|
|
1069
|
+
readonly label: "Tears of joy";
|
|
1070
|
+
readonly category: "Smileys";
|
|
1071
|
+
readonly char: "😂";
|
|
1072
|
+
}, {
|
|
1073
|
+
readonly name: "emoji:smiling";
|
|
1074
|
+
readonly label: "Smiling face";
|
|
1075
|
+
readonly category: "Smileys";
|
|
1076
|
+
readonly char: "😊";
|
|
1077
|
+
}, {
|
|
1078
|
+
readonly name: "emoji:heart-eyes";
|
|
1079
|
+
readonly label: "Heart eyes";
|
|
1080
|
+
readonly category: "Smileys";
|
|
1081
|
+
readonly char: "😍";
|
|
1082
|
+
}, {
|
|
1083
|
+
readonly name: "emoji:thinking";
|
|
1084
|
+
readonly label: "Thinking face";
|
|
1085
|
+
readonly category: "Smileys";
|
|
1086
|
+
readonly char: "🤔";
|
|
1087
|
+
}, {
|
|
1088
|
+
readonly name: "emoji:sunglasses";
|
|
1089
|
+
readonly label: "Sunglasses";
|
|
1090
|
+
readonly category: "Smileys";
|
|
1091
|
+
readonly char: "😎";
|
|
1092
|
+
}, {
|
|
1093
|
+
readonly name: "emoji:crying";
|
|
1094
|
+
readonly label: "Crying face";
|
|
1095
|
+
readonly category: "Smileys";
|
|
1096
|
+
readonly char: "😢";
|
|
1097
|
+
}, {
|
|
1098
|
+
readonly name: "emoji:angry";
|
|
1099
|
+
readonly label: "Angry face";
|
|
1100
|
+
readonly category: "Smileys";
|
|
1101
|
+
readonly char: "😡";
|
|
1102
|
+
}, {
|
|
1103
|
+
readonly name: "emoji:sleeping";
|
|
1104
|
+
readonly label: "Sleeping face";
|
|
1105
|
+
readonly category: "Smileys";
|
|
1106
|
+
readonly char: "😴";
|
|
1107
|
+
}, {
|
|
1108
|
+
readonly name: "emoji:mind-blown";
|
|
1109
|
+
readonly label: "Mind blown";
|
|
1110
|
+
readonly category: "Smileys";
|
|
1111
|
+
readonly char: "🤯";
|
|
1112
|
+
}, {
|
|
1113
|
+
readonly name: "emoji:grimacing";
|
|
1114
|
+
readonly label: "Grimacing face";
|
|
1115
|
+
readonly category: "Smileys";
|
|
1116
|
+
readonly char: "😬";
|
|
1117
|
+
}, {
|
|
1118
|
+
readonly name: "emoji:partying";
|
|
1119
|
+
readonly label: "Partying face";
|
|
1120
|
+
readonly category: "Smileys";
|
|
1121
|
+
readonly char: "🥳";
|
|
1122
|
+
}, {
|
|
1123
|
+
readonly name: "emoji:thumbs-up";
|
|
1124
|
+
readonly label: "Thumbs up emoji";
|
|
1125
|
+
readonly category: "Gestures";
|
|
1126
|
+
readonly char: "👍";
|
|
1127
|
+
}, {
|
|
1128
|
+
readonly name: "emoji:thumbs-down";
|
|
1129
|
+
readonly label: "Thumbs down emoji";
|
|
1130
|
+
readonly category: "Gestures";
|
|
1131
|
+
readonly char: "👎";
|
|
1132
|
+
}, {
|
|
1133
|
+
readonly name: "emoji:clap";
|
|
1134
|
+
readonly label: "Clapping hands";
|
|
1135
|
+
readonly category: "Gestures";
|
|
1136
|
+
readonly char: "👏";
|
|
1137
|
+
}, {
|
|
1138
|
+
readonly name: "emoji:raised-hands";
|
|
1139
|
+
readonly label: "Raised hands";
|
|
1140
|
+
readonly category: "Gestures";
|
|
1141
|
+
readonly char: "🙌";
|
|
1142
|
+
}, {
|
|
1143
|
+
readonly name: "emoji:folded-hands";
|
|
1144
|
+
readonly label: "Folded hands";
|
|
1145
|
+
readonly category: "Gestures";
|
|
1146
|
+
readonly char: "🙏";
|
|
1147
|
+
}, {
|
|
1148
|
+
readonly name: "emoji:eyes";
|
|
1149
|
+
readonly label: "Eyes";
|
|
1150
|
+
readonly category: "Gestures";
|
|
1151
|
+
readonly char: "👀";
|
|
1152
|
+
}, {
|
|
1153
|
+
readonly name: "emoji:muscle";
|
|
1154
|
+
readonly label: "Flexed biceps";
|
|
1155
|
+
readonly category: "Gestures";
|
|
1156
|
+
readonly char: "💪";
|
|
1157
|
+
}, {
|
|
1158
|
+
readonly name: "emoji:wave";
|
|
1159
|
+
readonly label: "Waving hand";
|
|
1160
|
+
readonly category: "Gestures";
|
|
1161
|
+
readonly char: "👋";
|
|
1162
|
+
}, {
|
|
1163
|
+
readonly name: "emoji:red-heart";
|
|
1164
|
+
readonly label: "Red heart";
|
|
1165
|
+
readonly category: "Symbols";
|
|
1166
|
+
readonly char: "❤️";
|
|
1167
|
+
}, {
|
|
1168
|
+
readonly name: "emoji:fire";
|
|
1169
|
+
readonly label: "Flame";
|
|
1170
|
+
readonly category: "Symbols";
|
|
1171
|
+
readonly char: "🔥";
|
|
1172
|
+
}, {
|
|
1173
|
+
readonly name: "emoji:star";
|
|
1174
|
+
readonly label: "Medium star";
|
|
1175
|
+
readonly category: "Symbols";
|
|
1176
|
+
readonly char: "⭐";
|
|
1177
|
+
}, {
|
|
1178
|
+
readonly name: "emoji:check";
|
|
1179
|
+
readonly label: "Check mark button";
|
|
1180
|
+
readonly category: "Symbols";
|
|
1181
|
+
readonly char: "✅";
|
|
1182
|
+
}, {
|
|
1183
|
+
readonly name: "emoji:cross";
|
|
1184
|
+
readonly label: "Cross mark";
|
|
1185
|
+
readonly category: "Symbols";
|
|
1186
|
+
readonly char: "❌";
|
|
1187
|
+
}, {
|
|
1188
|
+
readonly name: "emoji:warning";
|
|
1189
|
+
readonly label: "Warning sign";
|
|
1190
|
+
readonly category: "Symbols";
|
|
1191
|
+
readonly char: "⚠️";
|
|
1192
|
+
}, {
|
|
1193
|
+
readonly name: "emoji:question";
|
|
1194
|
+
readonly label: "Red question mark";
|
|
1195
|
+
readonly category: "Symbols";
|
|
1196
|
+
readonly char: "❓";
|
|
1197
|
+
}, {
|
|
1198
|
+
readonly name: "emoji:hundred";
|
|
1199
|
+
readonly label: "Hundred points";
|
|
1200
|
+
readonly category: "Symbols";
|
|
1201
|
+
readonly char: "💯";
|
|
1202
|
+
}, {
|
|
1203
|
+
readonly name: "emoji:light-bulb";
|
|
1204
|
+
readonly label: "Light bulb";
|
|
1205
|
+
readonly category: "Symbols";
|
|
1206
|
+
readonly char: "💡";
|
|
1207
|
+
}, {
|
|
1208
|
+
readonly name: "emoji:bullseye";
|
|
1209
|
+
readonly label: "Bullseye";
|
|
1210
|
+
readonly category: "Symbols";
|
|
1211
|
+
readonly char: "🎯";
|
|
1212
|
+
}, {
|
|
1213
|
+
readonly name: "emoji:party-popper";
|
|
1214
|
+
readonly label: "Party popper";
|
|
1215
|
+
readonly category: "Objects";
|
|
1216
|
+
readonly char: "🎉";
|
|
1217
|
+
}, {
|
|
1218
|
+
readonly name: "emoji:rocket";
|
|
1219
|
+
readonly label: "Rocket emoji";
|
|
1220
|
+
readonly category: "Objects";
|
|
1221
|
+
readonly char: "🚀";
|
|
1222
|
+
}, {
|
|
1223
|
+
readonly name: "emoji:trophy";
|
|
1224
|
+
readonly label: "Trophy emoji";
|
|
1225
|
+
readonly category: "Objects";
|
|
1226
|
+
readonly char: "🏆";
|
|
1227
|
+
}, {
|
|
1228
|
+
readonly name: "emoji:pushpin";
|
|
1229
|
+
readonly label: "Pushpin";
|
|
1230
|
+
readonly category: "Objects";
|
|
1231
|
+
readonly char: "📌";
|
|
1232
|
+
}, {
|
|
1233
|
+
readonly name: "emoji:memo";
|
|
1234
|
+
readonly label: "Memo";
|
|
1235
|
+
readonly category: "Objects";
|
|
1236
|
+
readonly char: "📝";
|
|
1237
|
+
}, {
|
|
1238
|
+
readonly name: "emoji:calendar";
|
|
1239
|
+
readonly label: "Tear-off calendar";
|
|
1240
|
+
readonly category: "Objects";
|
|
1241
|
+
readonly char: "📅";
|
|
1242
|
+
}, {
|
|
1243
|
+
readonly name: "emoji:alarm-clock";
|
|
1244
|
+
readonly label: "Alarm clock";
|
|
1245
|
+
readonly category: "Objects";
|
|
1246
|
+
readonly char: "⏰";
|
|
1247
|
+
}, {
|
|
1248
|
+
readonly name: "emoji:speech-balloon";
|
|
1249
|
+
readonly label: "Speech balloon";
|
|
1250
|
+
readonly category: "Objects";
|
|
1251
|
+
readonly char: "💬";
|
|
1252
|
+
}];
|
|
1253
|
+
type EmojiName = (typeof EMOJI_LIBRARY)[number]["name"];
|
|
1254
|
+
|
|
1255
|
+
/**
|
|
1256
|
+
* What the stamp tool will drop next: one of the six built-in stamp-pad
|
|
1257
|
+
* stickers, an emoji, or any vendored icon. All land on the board the same way
|
|
1258
|
+
* — as an image whose `stamp` field records which one it was (see `placeStamp`).
|
|
1259
|
+
*/
|
|
1260
|
+
type StickerKind = StampKind | EmojiName | IconName;
|
|
1261
|
+
|
|
522
1262
|
type SerializedBoardDocument = CurrentSerializedDocument;
|
|
523
1263
|
|
|
524
1264
|
interface AssetExportFailure {
|
|
@@ -559,16 +1299,30 @@ interface BoardStyle {
|
|
|
559
1299
|
readonly noteColor: string;
|
|
560
1300
|
readonly tableRows: number;
|
|
561
1301
|
readonly tableCols: number;
|
|
562
|
-
|
|
1302
|
+
/** A stamp-pad kind or any vendored icon name (see `ICON_LIBRARY`). */
|
|
1303
|
+
readonly stampKind: StickerKind;
|
|
563
1304
|
readonly timerDurationMs: number;
|
|
564
1305
|
}
|
|
1306
|
+
/** A page listed in the Board navigator. `default` is the original first page. */
|
|
1307
|
+
interface BoardPage {
|
|
1308
|
+
readonly id: string;
|
|
1309
|
+
readonly name: string;
|
|
1310
|
+
readonly sectionId: string;
|
|
1311
|
+
}
|
|
565
1312
|
interface BoardSnapshot {
|
|
1313
|
+
readonly notebooks: readonly BoardNotebook[];
|
|
1314
|
+
readonly sections: readonly BoardSection[];
|
|
1315
|
+
readonly activeNotebookId: string;
|
|
1316
|
+
readonly activeSectionId: string;
|
|
1317
|
+
readonly pages: readonly BoardPage[];
|
|
1318
|
+
readonly activePageId: string;
|
|
566
1319
|
readonly status: "loading" | "ready" | "disposed";
|
|
567
1320
|
readonly documentId: string;
|
|
568
1321
|
readonly tool: BuiltInTool | (string & {});
|
|
569
1322
|
readonly zoom: number;
|
|
570
1323
|
readonly readOnly: boolean;
|
|
571
1324
|
readonly selection: readonly string[];
|
|
1325
|
+
readonly focusedItem: FocusedItem | null;
|
|
572
1326
|
readonly strokeCount: number;
|
|
573
1327
|
readonly objectCount: number;
|
|
574
1328
|
readonly canUndo: boolean;
|
|
@@ -579,6 +1333,27 @@ interface BoardSnapshot {
|
|
|
579
1333
|
readonly collaboration: CollaborationSnapshot;
|
|
580
1334
|
};
|
|
581
1335
|
}
|
|
1336
|
+
/**
|
|
1337
|
+
* `selection` collapsed into the one thing a selected-object toolbar needs:
|
|
1338
|
+
* what's selected, its lock state, and where to anchor above it. `null`
|
|
1339
|
+
* when nothing is selected, or when the selection mixes types/objects that
|
|
1340
|
+
* don't resolve to a single focus (anything but one object, or several
|
|
1341
|
+
* strokes sharing a `clusterId` — a multi-stroke shape).
|
|
1342
|
+
*
|
|
1343
|
+
* Raw lock fields, not a derived `canUnlock` — this API has no notion of
|
|
1344
|
+
* "the local user" to judge that against (see `canUnlockItem` in `../core`,
|
|
1345
|
+
* which takes a `userId` the Host already owns). A Custom object's lock
|
|
1346
|
+
* shape doesn't carry a holder name, so it always reports `locked: false`
|
|
1347
|
+
* here, matching the engine's own internal selection-badge behavior.
|
|
1348
|
+
*/
|
|
1349
|
+
interface FocusedItem {
|
|
1350
|
+
readonly type: "stroke" | "note" | "text" | "table" | "image" | "timer" | "rectangle" | "ellipse" | "group" | "line" | "arrow" | "polygon" | "star" | "heart" | "custom";
|
|
1351
|
+
readonly id: string;
|
|
1352
|
+
readonly locked: boolean;
|
|
1353
|
+
readonly lockedBy?: string;
|
|
1354
|
+
readonly lockedByName?: string;
|
|
1355
|
+
readonly screenPosition?: ScreenPoint;
|
|
1356
|
+
}
|
|
582
1357
|
interface BoardControllerError {
|
|
583
1358
|
source: "controller" | "renderer" | "persistence" | "collaboration";
|
|
584
1359
|
code: string;
|
|
@@ -587,6 +1362,9 @@ interface BoardControllerError {
|
|
|
587
1362
|
}
|
|
588
1363
|
interface BoardEventMap {
|
|
589
1364
|
change: BoardSnapshot;
|
|
1365
|
+
"page-changing": {
|
|
1366
|
+
pageId: string;
|
|
1367
|
+
};
|
|
590
1368
|
"tool-change": {
|
|
591
1369
|
tool: string;
|
|
592
1370
|
};
|
|
@@ -653,6 +1431,8 @@ interface BoardEventMap {
|
|
|
653
1431
|
"asset-diagnostic": AssetDiagnostic;
|
|
654
1432
|
/** A batch of Ops was reconciled (not applied as-sent) by the persistence adapter (ticket #24). */
|
|
655
1433
|
"persistence-diagnostic": PersistenceDiagnostic;
|
|
1434
|
+
/** 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. */
|
|
1435
|
+
"collaboration-ops-acknowledged": CollaborationAckDiagnostic;
|
|
656
1436
|
audit: unknown;
|
|
657
1437
|
error: BoardControllerError;
|
|
658
1438
|
disposed: undefined;
|
|
@@ -672,6 +1452,27 @@ interface BoardView {
|
|
|
672
1452
|
y: number;
|
|
673
1453
|
zoom: number;
|
|
674
1454
|
}
|
|
1455
|
+
/**
|
|
1456
|
+
* The rendered board surface's color, reference grid, and shape stroke
|
|
1457
|
+
* width — the subset of {@link ScrawlTheme} that reaches the rendering
|
|
1458
|
+
* engine directly (everything else is UI-chrome-only, applied as CSS). Any
|
|
1459
|
+
* field left unset keeps its current value.
|
|
1460
|
+
*/
|
|
1461
|
+
interface BoardThemeOptions {
|
|
1462
|
+
/** The board/canvas background color — distinct from UI chrome panels. */
|
|
1463
|
+
surface?: string;
|
|
1464
|
+
/** `"flat"` (default): a plain, uniform board surface. `"textured"`: a subtle "melamine" micro-noise + satin sheen, like a physical whiteboard. */
|
|
1465
|
+
surfaceTexture?: "flat" | "textured";
|
|
1466
|
+
/** `"none"` (default) keeps the board a plain surface; `"line"`/`"dot"` draw a zoom-adaptive reference grid. */
|
|
1467
|
+
gridMode?: "none" | "line" | "dot";
|
|
1468
|
+
gridColor?: string;
|
|
1469
|
+
/** Grid spacing in board units at 100% zoom. Ignored when `gridMode` is `"none"`. */
|
|
1470
|
+
gridSpacing?: number;
|
|
1471
|
+
/** On-screen grid line/dot width in CSS pixels — stays this width at any zoom, since the grid is a reference aid, not content. */
|
|
1472
|
+
gridLineWidth?: number;
|
|
1473
|
+
/** Shape (rectangle, ellipse, arrow, ...) border width, in board units — scales with zoom like ink, since it's part of the drawn content. */
|
|
1474
|
+
shapeStrokeWidth?: number;
|
|
1475
|
+
}
|
|
675
1476
|
/**
|
|
676
1477
|
* A Host-owned comment, summarized for Board-side search and marker
|
|
677
1478
|
* rendering. Comments are not Document content — they carry no undo
|
|
@@ -695,9 +1496,12 @@ interface PresenceView {
|
|
|
695
1496
|
readonly height: number;
|
|
696
1497
|
}
|
|
697
1498
|
/**
|
|
698
|
-
* A
|
|
699
|
-
*
|
|
700
|
-
*
|
|
1499
|
+
* A collaborator, synced in for cursor/roster rendering only. Presence is
|
|
1500
|
+
* ephemeral — it never touches the Document, Ops, undo/redo, or persistence
|
|
1501
|
+
* (ADR 0006/0007). Two ways a roster gets populated (`presence.sync`
|
|
1502
|
+
* directly, or a `CollaborationAdapter`'s optional presence channel —
|
|
1503
|
+
* Phase 6, ADR 0015) both feed the exact same read/query capability below;
|
|
1504
|
+
* a Host picks one, not both, for a given controller.
|
|
701
1505
|
*/
|
|
702
1506
|
interface PresenceUser {
|
|
703
1507
|
readonly id: string;
|
|
@@ -706,6 +1510,14 @@ interface PresenceUser {
|
|
|
706
1510
|
readonly tool?: string;
|
|
707
1511
|
readonly cursor?: PresenceCursor;
|
|
708
1512
|
readonly view?: PresenceView;
|
|
1513
|
+
/** Host-supplied extras (avatar URL, role, etc.) — opaque to Scrawl, never interpreted. */
|
|
1514
|
+
readonly metadata?: Record<string, unknown>;
|
|
1515
|
+
}
|
|
1516
|
+
/** This client's own local presence, published via `presence.broadcast()` (Phase 6). */
|
|
1517
|
+
interface LocalPresence {
|
|
1518
|
+
readonly cursor?: PresenceCursor | null;
|
|
1519
|
+
readonly view?: PresenceView | null;
|
|
1520
|
+
readonly tool?: string;
|
|
709
1521
|
}
|
|
710
1522
|
/**
|
|
711
1523
|
* The Custom arm wraps `CustomBoardObject` under the same `type` discriminant
|
|
@@ -733,6 +1545,22 @@ type BoardObject = ({
|
|
|
733
1545
|
} & ImageBlock) | ({
|
|
734
1546
|
type: "timer";
|
|
735
1547
|
} & KitchenTimer) | ({
|
|
1548
|
+
type: "rectangle";
|
|
1549
|
+
} & RectangleObject) | ({
|
|
1550
|
+
type: "ellipse";
|
|
1551
|
+
} & EllipseObject) | ({
|
|
1552
|
+
type: "group";
|
|
1553
|
+
} & GroupObject) | ({
|
|
1554
|
+
type: "line";
|
|
1555
|
+
} & LineObject) | ({
|
|
1556
|
+
type: "arrow";
|
|
1557
|
+
} & ArrowObject) | ({
|
|
1558
|
+
type: "polygon";
|
|
1559
|
+
} & PolygonObject) | ({
|
|
1560
|
+
type: "star";
|
|
1561
|
+
} & StarObject) | ({
|
|
1562
|
+
type: "heart";
|
|
1563
|
+
} & HeartObject) | ({
|
|
736
1564
|
type: "custom";
|
|
737
1565
|
customType: ObjectType;
|
|
738
1566
|
} & Omit<CustomBoardObject, "type">);
|
|
@@ -761,6 +1589,30 @@ type BoardObjectInput = {
|
|
|
761
1589
|
type: "timer";
|
|
762
1590
|
id?: string;
|
|
763
1591
|
} & Omit<KitchenTimer, "id">) | ({
|
|
1592
|
+
type: "rectangle";
|
|
1593
|
+
id?: string;
|
|
1594
|
+
} & Omit<RectangleObject, "id">) | ({
|
|
1595
|
+
type: "ellipse";
|
|
1596
|
+
id?: string;
|
|
1597
|
+
} & Omit<EllipseObject, "id">) | ({
|
|
1598
|
+
type: "group";
|
|
1599
|
+
id?: string;
|
|
1600
|
+
} & Omit<GroupObject, "id">) | ({
|
|
1601
|
+
type: "line";
|
|
1602
|
+
id?: string;
|
|
1603
|
+
} & Omit<LineObject, "id">) | ({
|
|
1604
|
+
type: "arrow";
|
|
1605
|
+
id?: string;
|
|
1606
|
+
} & Omit<ArrowObject, "id">) | ({
|
|
1607
|
+
type: "polygon";
|
|
1608
|
+
id?: string;
|
|
1609
|
+
} & Omit<PolygonObject, "id">) | ({
|
|
1610
|
+
type: "star";
|
|
1611
|
+
id?: string;
|
|
1612
|
+
} & Omit<StarObject, "id">) | ({
|
|
1613
|
+
type: "heart";
|
|
1614
|
+
id?: string;
|
|
1615
|
+
} & Omit<HeartObject, "id">) | ({
|
|
764
1616
|
type: "custom";
|
|
765
1617
|
id?: string;
|
|
766
1618
|
customType: ObjectType;
|
|
@@ -791,10 +1643,34 @@ type LoadResult = {
|
|
|
791
1643
|
} | {
|
|
792
1644
|
state: "missing";
|
|
793
1645
|
};
|
|
1646
|
+
/**
|
|
1647
|
+
* Result of a whole-document `PersistenceAdapter.replace()` call (ADR 0006:
|
|
1648
|
+
* "Whole-document writes survive only for create, clear-board and import,
|
|
1649
|
+
* where replacing everything is the actual intent"). Revision-gated, unlike
|
|
1650
|
+
* `applyOps` — `conflict` means `baseRevision` was stale (someone else's
|
|
1651
|
+
* write landed first); the caller must reload and never overwrites blind.
|
|
1652
|
+
*/
|
|
1653
|
+
type ReplaceResult = {
|
|
1654
|
+
state: "applied";
|
|
1655
|
+
revision: string;
|
|
1656
|
+
} | {
|
|
1657
|
+
state: "conflict";
|
|
1658
|
+
currentRevision: string;
|
|
1659
|
+
};
|
|
1660
|
+
/**
|
|
1661
|
+
* The one sanctioned seam for persisting a Board's Document to a Host's own
|
|
1662
|
+
* storage — implement this against a database, an HTTP API, IndexedDB
|
|
1663
|
+
* (see `@scrawl-board/board/local`'s `createIndexedDBPersistence`), or
|
|
1664
|
+
* anything else. `load()` fetches the current state on connect; `applyOps()`
|
|
1665
|
+
* streams incremental Ops as edits happen; `replace()` is only for
|
|
1666
|
+
* whole-document writes (create, clear-board, import — see ADR 0006) and is
|
|
1667
|
+
* revision-gated so a stale write never silently clobbers a newer one.
|
|
1668
|
+
* Passed via `createBoardController({ adapters: { persistence } })`.
|
|
1669
|
+
*/
|
|
794
1670
|
interface PersistenceAdapter {
|
|
795
1671
|
load(context: DocumentContext): Promise<LoadResult>;
|
|
796
1672
|
applyOps(context: DocumentContext, ops: readonly ControllerOp[]): Promise<ApplyOpsResult>;
|
|
797
|
-
replace(context: DocumentContext, document: CurrentSerializedDocument, baseRevision: string): Promise<
|
|
1673
|
+
replace(context: DocumentContext, document: CurrentSerializedDocument, baseRevision: string): Promise<ReplaceResult>;
|
|
798
1674
|
}
|
|
799
1675
|
/**
|
|
800
1676
|
* `"reconcile"` (ticket #24) means the server authoritatively resolved the
|
|
@@ -818,14 +1694,61 @@ interface PersistenceDiagnostic {
|
|
|
818
1694
|
rejectedOpIds: readonly string[];
|
|
819
1695
|
revision: string;
|
|
820
1696
|
}
|
|
1697
|
+
/** Emitted as `"collaboration-ops-acknowledged"` (Phase 7) — the server has confirmed receipt of these op ids on the live pipe. */
|
|
1698
|
+
interface CollaborationAckDiagnostic {
|
|
1699
|
+
opIds: readonly string[];
|
|
1700
|
+
}
|
|
821
1701
|
interface ControllerOp {
|
|
822
1702
|
id: string;
|
|
823
1703
|
schemaVersion: 1;
|
|
824
1704
|
kind: "upsert" | "restore" | "remove";
|
|
825
|
-
objectType: "stroke" | "note" | "text" | "table" | "image" | "timer" | "custom"
|
|
1705
|
+
objectType: "stroke" | "note" | "text" | "table" | "image" | "timer" | "rectangle" | "ellipse" | "group" | "line" | "arrow" | "polygon" | "star" | "heart" | "custom"
|
|
1706
|
+
/**
|
|
1707
|
+
* A whole-document paint-order sync (Phase 3), not a per-object type —
|
|
1708
|
+
* `objectId` is always the fixed sentinel `"order"` and `payload` is
|
|
1709
|
+
* `{ order: string[] }`. The only `objectType` with no matching
|
|
1710
|
+
* `BoardObject`/document collection; kept in this same union (rather
|
|
1711
|
+
* than a separate wire message) so it flows through the existing
|
|
1712
|
+
* `PersistenceAdapter`/`CollaborationAdapter` opaquely, unchanged.
|
|
1713
|
+
*/
|
|
1714
|
+
| "order" | "page" | "notebook" | "section";
|
|
1715
|
+
/** Absent for the original page; scopes content and order Ops to an additional page. */
|
|
1716
|
+
pageId?: string;
|
|
826
1717
|
objectId: string;
|
|
827
1718
|
payload?: unknown;
|
|
1719
|
+
/**
|
|
1720
|
+
* This op's position in its own originating client's local sequence
|
|
1721
|
+
* (Phase 7) — 1, 2, 3, ... per controller instance, distinct from `id`
|
|
1722
|
+
* (an opaque, globally-unique identifier used for dedup/ack, not
|
|
1723
|
+
* ordering) and from a server's own authoritative ordering (e.g.
|
|
1724
|
+
* `referenceCollaborationServer.ts`'s per-room `version` counter).
|
|
1725
|
+
* Present on every op this SDK originates locally; a remote peer's op
|
|
1726
|
+
* carries whatever its own origin set, unchanged — never renumbered in
|
|
1727
|
+
* transit. Absent on an op minted by decoding the legacy wire envelope
|
|
1728
|
+
* (`scrawlOpEnvelope.ts`), which predates this field and has no
|
|
1729
|
+
* per-client sequence concept of its own.
|
|
1730
|
+
*/
|
|
1731
|
+
clientSequence?: number;
|
|
1732
|
+
/**
|
|
1733
|
+
* The `CollaboratorIdentity.id` of this op's originating client (Phase
|
|
1734
|
+
* 7) — set for every op this SDK originates locally when `identity` is
|
|
1735
|
+
* configured, omitted entirely otherwise (never sent as `undefined`).
|
|
1736
|
+
* The explicit foundation for a future per-author undo filter (a local
|
|
1737
|
+
* user's own undo should only ever touch their own ops) — no undo-stack
|
|
1738
|
+
* behavior itself changes this phase.
|
|
1739
|
+
*/
|
|
1740
|
+
clientId?: string;
|
|
828
1741
|
}
|
|
1742
|
+
/**
|
|
1743
|
+
* The one sanctioned seam for real-time multiplayer — implement this against
|
|
1744
|
+
* a Host's own collaboration backend (WebSocket relay, CRDT server, etc.).
|
|
1745
|
+
* `connect()` is called once per controller with the local user's
|
|
1746
|
+
* `identity` and a `receive` callback the adapter invokes with incoming
|
|
1747
|
+
* Ops, presence updates, acks, and connection status; it resolves with a
|
|
1748
|
+
* `CollaborationSession` the controller uses to send local Ops and presence
|
|
1749
|
+
* back out. Passed via `createBoardController({ adapters: { collaboration } })`;
|
|
1750
|
+
* omit it entirely to run single-player.
|
|
1751
|
+
*/
|
|
829
1752
|
interface CollaborationAdapter {
|
|
830
1753
|
connect(options: DocumentContext & {
|
|
831
1754
|
identity: CollaboratorIdentity;
|
|
@@ -836,14 +1759,72 @@ interface CollaboratorIdentity {
|
|
|
836
1759
|
id: string;
|
|
837
1760
|
name: string;
|
|
838
1761
|
color?: string;
|
|
1762
|
+
/** Host-supplied extras (avatar URL, role, etc.) — opaque to Scrawl, forwarded into any resulting `PresenceUser` unread and never interpreted. */
|
|
1763
|
+
metadata?: Record<string, unknown>;
|
|
839
1764
|
}
|
|
840
1765
|
interface CollaborationReceiver {
|
|
841
1766
|
ops(ops: readonly ControllerOp[]): void;
|
|
1767
|
+
/**
|
|
1768
|
+
* The current presence roster (Phase 6, ADR 0015) — always a full
|
|
1769
|
+
* replacement, never a delta, matching `presence.sync`'s existing
|
|
1770
|
+
* semantics exactly (an adapter that aggregates wire deltas into a full
|
|
1771
|
+
* roster before calling this is the adapter's own job, not the
|
|
1772
|
+
* controller's). Required on this interface (not optional) because a
|
|
1773
|
+
* Host only ever *consumes* `CollaborationReceiver` — never implements
|
|
1774
|
+
* it — so adding a required method here cannot break an existing custom
|
|
1775
|
+
* `CollaborationAdapter`. An adapter with no presence support simply
|
|
1776
|
+
* never calls it.
|
|
1777
|
+
*/
|
|
1778
|
+
presence(users: readonly PresenceUser[]): void;
|
|
1779
|
+
/**
|
|
1780
|
+
* The server has confirmed receipt of these op ids (Phase 7) —
|
|
1781
|
+
* distinguishes "sent" from "server accepted," which `sendOps` alone
|
|
1782
|
+
* (fire-and-forget) cannot. Required for the same reason `presence` is:
|
|
1783
|
+
* Hosts only ever consume this interface, never implement it, so this
|
|
1784
|
+
* cannot break an existing custom `CollaborationAdapter`. An adapter with
|
|
1785
|
+
* no ack support simply never calls it — the collaboration pipe still
|
|
1786
|
+
* works exactly as it did before this existed, just without the
|
|
1787
|
+
* bookkeeping/observability this enables.
|
|
1788
|
+
*/
|
|
1789
|
+
acknowledged(opIds: readonly string[]): void;
|
|
842
1790
|
status(state: "online" | "reconnecting" | "offline"): void;
|
|
843
1791
|
error(cause: unknown): void;
|
|
844
1792
|
}
|
|
1793
|
+
/**
|
|
1794
|
+
* Result of `CollaborationSession.requestSync()` (Phase 7). `"ops"` means
|
|
1795
|
+
* the adapter's own live-pipe cache fully covered the gap since the
|
|
1796
|
+
* caller's last known revision — apply `ops` and the client is caught up,
|
|
1797
|
+
* no persistence reload needed. `"unavailable"` means it couldn't (gap too
|
|
1798
|
+
* large, server restarted, or the adapter has no retained history at all)
|
|
1799
|
+
* — the caller must fall back to a persistence-backed reload. This is a
|
|
1800
|
+
* best-effort *liveness* cache, deliberately never a durable source of
|
|
1801
|
+
* truth (ADR 0006's "collaboration is never a second source of document
|
|
1802
|
+
* truth" — see ADR 0015's own extension of that principle to presence,
|
|
1803
|
+
* now extended once more, the same way, to this).
|
|
1804
|
+
*/
|
|
1805
|
+
type CollaborationSyncResult = {
|
|
1806
|
+
state: "ops";
|
|
1807
|
+
ops: readonly ControllerOp[];
|
|
1808
|
+
serverRevision: string;
|
|
1809
|
+
} | {
|
|
1810
|
+
state: "unavailable";
|
|
1811
|
+
};
|
|
845
1812
|
interface CollaborationSession {
|
|
846
1813
|
sendOps(ops: readonly ControllerOp[]): void;
|
|
1814
|
+
/**
|
|
1815
|
+
* Publishes this client's own local presence (Phase 6, ADR 0015) —
|
|
1816
|
+
* best-effort, unordered, never persisted, never an Op. Optional: an
|
|
1817
|
+
* adapter that doesn't support presence simply omits this method, and
|
|
1818
|
+
* `presence.broadcast()` becomes a silent no-op.
|
|
1819
|
+
*/
|
|
1820
|
+
updatePresence?(presence: LocalPresence): void;
|
|
1821
|
+
/**
|
|
1822
|
+
* Requests an incremental catch-up after a reconnect (Phase 7) — optional;
|
|
1823
|
+
* an adapter that doesn't support this simply omits the method, and the
|
|
1824
|
+
* caller (`resyncAfterReconnect`) goes straight to its existing
|
|
1825
|
+
* persistence-backed full reload, unchanged from Phase 6.
|
|
1826
|
+
*/
|
|
1827
|
+
requestSync?(): Promise<CollaborationSyncResult>;
|
|
847
1828
|
close(): Promise<void>;
|
|
848
1829
|
}
|
|
849
1830
|
interface CreateBoardControllerOptions {
|
|
@@ -863,24 +1844,133 @@ interface CreateBoardControllerOptions {
|
|
|
863
1844
|
* Trusted Custom tool/object registrations (ticket #22, design:
|
|
864
1845
|
* docs/research/extension-contracts.md). Validated atomically at
|
|
865
1846
|
* construction; registration failure throws before any controller is
|
|
866
|
-
* returned.
|
|
867
|
-
* internal-only until the reference Extension proves the seam.
|
|
1847
|
+
* returned.
|
|
868
1848
|
*/
|
|
869
1849
|
extensions?: readonly ScrawlExtension[];
|
|
870
1850
|
/**
|
|
871
1851
|
* Optional Host-managed Asset capabilities (ticket #23, design:
|
|
872
1852
|
* docs/research/asset-resolution-resource-policy.md). Without a
|
|
873
1853
|
* resolver, referenced Assets preserve their Document geometry and
|
|
874
|
-
* render an accessible placeholder.
|
|
875
|
-
* package entry point — internal-only until the reference resolver
|
|
876
|
-
* proves the seam, matching how `extensions` is scoped.
|
|
1854
|
+
* render an accessible placeholder.
|
|
877
1855
|
*/
|
|
878
1856
|
assetResolver?: AssetResolver;
|
|
879
1857
|
assetIngestor?: AssetIngestor;
|
|
880
1858
|
/** Clamped to 64–512MiB; defaults to 256MiB. */
|
|
881
1859
|
assetCacheBytes?: number;
|
|
1860
|
+
/**
|
|
1861
|
+
* The rendered board surface's color and reference grid. Defaults to the
|
|
1862
|
+
* light theme preset's values; `<Scrawl>` keeps this current across theme
|
|
1863
|
+
* changes via `boardTheme.set` below — a headless/browser-tier Host that
|
|
1864
|
+
* doesn't use the React theme system can set this directly instead.
|
|
1865
|
+
*/
|
|
1866
|
+
boardTheme?: BoardThemeOptions;
|
|
1867
|
+
/**
|
|
1868
|
+
* Debounced auto-flush of pending persistence Ops after document changes
|
|
1869
|
+
* settle (Phase 5). Enabled by default (1000ms debounce) whenever
|
|
1870
|
+
* `adapters.persistence` is configured — today, without this, a Host must
|
|
1871
|
+
* call `flush()` manually after every edit for anything to persist. Pass
|
|
1872
|
+
* `false` to opt out entirely and drive `flush()` yourself, preserving
|
|
1873
|
+
* prior behavior exactly. Never fires on a per-change basis — rapid edits
|
|
1874
|
+
* coalesce into one flush of their final state (ADR 0006).
|
|
1875
|
+
*/
|
|
1876
|
+
autosave?: boolean | {
|
|
1877
|
+
debounceMs?: number;
|
|
1878
|
+
};
|
|
1879
|
+
/**
|
|
1880
|
+
* Throttle for `presence.broadcast()` (Phase 6, ADR 0015) — the minimum
|
|
1881
|
+
* interval between outgoing presence updates sent via the configured
|
|
1882
|
+
* `CollaborationAdapter`. Defaults to 50ms. A trailing throttle: the
|
|
1883
|
+
* latest value passed to `broadcast()` always eventually sends, even if
|
|
1884
|
+
* calls arrive faster than this interval.
|
|
1885
|
+
*/
|
|
1886
|
+
presenceThrottleMs?: number;
|
|
1887
|
+
/**
|
|
1888
|
+
* Caps how many `ControllerOp`s can sit queued, unsent, for the
|
|
1889
|
+
* persistence pipe (`pendingOps`) or the collaboration pipe
|
|
1890
|
+
* (`pendingCollaborationOps`) at once (Phase 7) — each pipe is capped
|
|
1891
|
+
* independently. Prevents unbounded memory growth from a long-lived
|
|
1892
|
+
* offline session or a stuck adapter. Exceeding it never fails or drops
|
|
1893
|
+
* the local edit itself (the Document already applied it optimistically)
|
|
1894
|
+
* — only queueing for that one pipe is skipped, and a
|
|
1895
|
+
* `{code:"queue-overflow", retryable:false}` error is emitted so a Host
|
|
1896
|
+
* can react. Defaults to 1000 — the Phase 9 collaboration coalescing
|
|
1897
|
+
* above (`collaborationCoalesceMs`) already keeps a busy drag from
|
|
1898
|
+
* approaching this on its own, so hitting it in practice means a pipe
|
|
1899
|
+
* has been offline/stuck for a genuinely long editing session.
|
|
1900
|
+
*
|
|
1901
|
+
* **Recovery** (Phase 9): the dropped op itself is gone from that one
|
|
1902
|
+
* pipe's queue — there is no automatic backfill, and the live
|
|
1903
|
+
* controller keeps running with that pipe now silently missing one
|
|
1904
|
+
* edit. Two things stay true regardless: (1) the in-memory Document is
|
|
1905
|
+
* never affected — a queue-overflow can never corrupt or roll back a
|
|
1906
|
+
* local edit, only skip sending it; (2) staleness is per-object, not
|
|
1907
|
+
* permanent — any *later* edit to that same object produces a brand
|
|
1908
|
+
* new, undropped Op carrying its full current state, which naturally
|
|
1909
|
+
* supersedes the gap (the Op model is already last-write-wins/
|
|
1910
|
+
* idempotent, so a superseding Op doesn't need the earlier one to have
|
|
1911
|
+
* arrived). The real risk is an object that's dropped and never edited
|
|
1912
|
+
* again before the controller is disposed or the page reloads — a Host
|
|
1913
|
+
* that needs strict durability should treat `queue-overflow` as a
|
|
1914
|
+
* signal to check `persistence.state`/`pendingOps` pressure (via
|
|
1915
|
+
* `usePersistenceStatus`/`getSnapshot().connection.persistence`)
|
|
1916
|
+
* before disposing, not assume disposing and reconnecting alone
|
|
1917
|
+
* repairs the gap (a fresh `load()` only returns what the backend
|
|
1918
|
+
* already has, which is exactly what's missing the dropped edit).
|
|
1919
|
+
*/
|
|
1920
|
+
maxPendingOps?: number;
|
|
1921
|
+
/**
|
|
1922
|
+
* Coalescing window (ms) for outgoing collaboration Ops (Phase 9) — same
|
|
1923
|
+
* trailing-throttle shape as `presenceThrottleMs`: the first Op after an
|
|
1924
|
+
* idle period sends immediately, and subsequent Ops for the *same*
|
|
1925
|
+
* object within this window replace each other (latest value wins,
|
|
1926
|
+
* matching the already-idempotent Op model) rather than each triggering
|
|
1927
|
+
* its own send. A multi-second drag that previously sent one full Op per
|
|
1928
|
+
* pointer-move now sends at most one per window per touched object.
|
|
1929
|
+
* Persistence (`pendingOps`) is unaffected — it already debounces via
|
|
1930
|
+
* `autosave`, so this option only changes live collaboration traffic.
|
|
1931
|
+
* Defaults to 50ms.
|
|
1932
|
+
*/
|
|
1933
|
+
collaborationCoalesceMs?: number;
|
|
1934
|
+
/**
|
|
1935
|
+
* Caps how many resolved objects a single `content.copy`/`content.cut`
|
|
1936
|
+
* (or their Cmd/Ctrl+C/X keyboard equivalents) will hold in the
|
|
1937
|
+
* in-memory clipboard at once (Phase 9) — `expandSelection` recursively
|
|
1938
|
+
* expands groups, so an unbounded selection (a huge group, or thousands
|
|
1939
|
+
* of individually selected strokes) could otherwise clone and retain an
|
|
1940
|
+
* arbitrarily large snapshot indefinitely, until the next copy/cut
|
|
1941
|
+
* replaces it. Exceeding it rejects the whole copy/cut (nothing is
|
|
1942
|
+
* cloned, and — for cut — nothing is removed from the Document either,
|
|
1943
|
+
* never a partial copy of an arbitrary subset) and emits a
|
|
1944
|
+
* `{code:"clipboard-overflow", retryable:false}` error. Defaults to
|
|
1945
|
+
* 5000.
|
|
1946
|
+
*/
|
|
1947
|
+
maxClipboardItems?: number;
|
|
882
1948
|
}
|
|
883
1949
|
interface BoardController {
|
|
1950
|
+
readonly notebooks: {
|
|
1951
|
+
/** Create a notebook containing one section and an empty page; select it. */
|
|
1952
|
+
add(name?: string): string;
|
|
1953
|
+
select(id: string): void;
|
|
1954
|
+
rename(id: string, name: string): void;
|
|
1955
|
+
list(): readonly BoardNotebook[];
|
|
1956
|
+
current(): string;
|
|
1957
|
+
};
|
|
1958
|
+
readonly sections: {
|
|
1959
|
+
/** Create a section in the current or specified notebook, with one empty page. */
|
|
1960
|
+
add(name?: string, notebookId?: string): string;
|
|
1961
|
+
select(id: string): void;
|
|
1962
|
+
rename(id: string, name: string): void;
|
|
1963
|
+
list(notebookId?: string): readonly BoardSection[];
|
|
1964
|
+
current(): string;
|
|
1965
|
+
};
|
|
1966
|
+
readonly pages: {
|
|
1967
|
+
/** Add an empty page and switch to it. */
|
|
1968
|
+
add(name?: string, sectionId?: string): string;
|
|
1969
|
+
select(id: string): void;
|
|
1970
|
+
rename(id: string, name: string): void;
|
|
1971
|
+
list(sectionId?: string): readonly BoardPage[];
|
|
1972
|
+
current(): string;
|
|
1973
|
+
};
|
|
884
1974
|
readonly document: ReadonlyBoardDocument;
|
|
885
1975
|
readonly tools: {
|
|
886
1976
|
select(tool: BuiltInTool | (string & {})): void;
|
|
@@ -895,7 +1985,7 @@ interface BoardController {
|
|
|
895
1985
|
noteColor: string;
|
|
896
1986
|
tableRows: number;
|
|
897
1987
|
tableCols: number;
|
|
898
|
-
stampKind:
|
|
1988
|
+
stampKind: StickerKind;
|
|
899
1989
|
timerDurationMs: number;
|
|
900
1990
|
}>): void;
|
|
901
1991
|
current(): BoardStyle;
|
|
@@ -904,10 +1994,35 @@ interface BoardController {
|
|
|
904
1994
|
undo(): void;
|
|
905
1995
|
redo(): void;
|
|
906
1996
|
};
|
|
1997
|
+
readonly boardTheme: {
|
|
1998
|
+
/** Live update of the board surface color/grid/shape-stroke-width — the controller's identity stays fixed across theme changes. */
|
|
1999
|
+
set(theme: BoardThemeOptions): void;
|
|
2000
|
+
};
|
|
907
2001
|
readonly view: {
|
|
908
2002
|
fit(): void;
|
|
2003
|
+
/**
|
|
2004
|
+
* Frame the current selection (Phase 8), the same way `fit()` frames the
|
|
2005
|
+
* whole board. A no-op with nothing selected — deliberately doesn't fall
|
|
2006
|
+
* back to `fit()`'s "frame everything," which would be a surprising
|
|
2007
|
+
* result for an empty selection. On a headless board this can only
|
|
2008
|
+
* re-center the view (no viewport to compute a real zoom-to-fit from),
|
|
2009
|
+
* matching `fit()`'s own headless limitation exactly.
|
|
2010
|
+
*/
|
|
2011
|
+
zoomToSelection(): void;
|
|
909
2012
|
zoomTo(value: number): void;
|
|
910
2013
|
centerOn(point: BoardPoint): void;
|
|
2014
|
+
/**
|
|
2015
|
+
* Tell the canvas an inline text editor is open over a text block (`{ id }`)
|
|
2016
|
+
* or a table cell (`{ id, row, col }`), so it stops drawing that text until
|
|
2017
|
+
* the editor closes (`null`) and the two copies don't overlap. The default
|
|
2018
|
+
* UI's inline editors call this; a Host with its own editors should too.
|
|
2019
|
+
* Presentation only — never touches the Document. A no-op when headless.
|
|
2020
|
+
*/
|
|
2021
|
+
setInlineEditing(target: {
|
|
2022
|
+
id: string;
|
|
2023
|
+
row?: number;
|
|
2024
|
+
col?: number;
|
|
2025
|
+
} | null): void;
|
|
911
2026
|
get(): BoardView;
|
|
912
2027
|
boardToScreen(point: BoardPoint): ScreenPoint;
|
|
913
2028
|
screenToBoard(point: ScreenPoint): BoardPoint;
|
|
@@ -916,6 +2031,75 @@ interface BoardController {
|
|
|
916
2031
|
add(input: BoardObjectInput): string;
|
|
917
2032
|
update(id: string, patch: BoardObjectPatch): void;
|
|
918
2033
|
remove(ids: readonly string[]): void;
|
|
2034
|
+
/**
|
|
2035
|
+
* Clone each given object as a new, unlocked copy offset by a small
|
|
2036
|
+
* fixed cascade (matching the Duplicate affordance's established Host
|
|
2037
|
+
* convention), as one undoable step. Order-preserving: `result[i]` is
|
|
2038
|
+
* the clone of `ids[i]`. Strokes that share a `clusterId` among the
|
|
2039
|
+
* given ids get a single fresh shared `clusterId` in the result, so
|
|
2040
|
+
* duplicating a whole multi-stroke shape (e.g. an arrow's shaft + head)
|
|
2041
|
+
* keeps it one shape — pass every member's id together, not just one.
|
|
2042
|
+
* Unknown ids are silently skipped, matching `remove`'s convention.
|
|
2043
|
+
*/
|
|
2044
|
+
duplicate(ids: readonly string[]): readonly string[];
|
|
2045
|
+
/**
|
|
2046
|
+
* Creates a new Group referencing `ids` as its children and returns its
|
|
2047
|
+
* id, as one undoable step. Unknown ids are silently skipped, matching
|
|
2048
|
+
* `duplicate`/`remove`'s convention. A child id that's itself a group
|
|
2049
|
+
* makes a nested group — expanding nested groups into their leaf
|
|
2050
|
+
* members is always the caller's job, never assumed here (matches the
|
|
2051
|
+
* document-model `GroupObject` itself).
|
|
2052
|
+
*/
|
|
2053
|
+
group(ids: readonly string[]): string;
|
|
2054
|
+
/**
|
|
2055
|
+
* Dissolves one group, returning its immediate children's ids (a nested
|
|
2056
|
+
* subgroup among them stays intact, itself still a group) — the group
|
|
2057
|
+
* record itself is removed, the children are untouched. A no-op
|
|
2058
|
+
* (returns `[]`) if `groupId` isn't a group.
|
|
2059
|
+
*/
|
|
2060
|
+
ungroup(groupId: string): readonly string[];
|
|
2061
|
+
/**
|
|
2062
|
+
* Aligns every given object's matching edge/center to the corresponding
|
|
2063
|
+
* edge/center of their combined bounding box, as one undoable step.
|
|
2064
|
+
* `"top"`/`"bottom"` follow board space's Y-up convention (`"top"` is
|
|
2065
|
+
* the larger Y). Ids that don't resolve, or resolve to a Group (which
|
|
2066
|
+
* has no position of its own), are skipped. A no-op under 2 resolvable
|
|
2067
|
+
* ids — there's nothing to align relative to.
|
|
2068
|
+
*/
|
|
2069
|
+
align(ids: readonly string[], edge: "left" | "right" | "top" | "bottom" | "centerX" | "centerY"): void;
|
|
2070
|
+
/**
|
|
2071
|
+
* Spaces the middle objects' centers evenly between the first and last
|
|
2072
|
+
* (sorted along `axis`), as one undoable step — the two endpoints don't
|
|
2073
|
+
* move. Ids that don't resolve, or resolve to a Group, are skipped. A
|
|
2074
|
+
* no-op under 3 resolvable ids — there's no "middle" to distribute.
|
|
2075
|
+
*/
|
|
2076
|
+
distribute(ids: readonly string[], axis: "x" | "y"): void;
|
|
2077
|
+
/**
|
|
2078
|
+
* Snapshots `ids` (recursively expanded through any group, same as
|
|
2079
|
+
* `duplicate`) into an internal in-memory clipboard — never
|
|
2080
|
+
* `navigator.clipboard`, scoped to this one controller instance and
|
|
2081
|
+
* replaced wholesale by the next `copy`/`cut`. Read-only; works even
|
|
2082
|
+
* on a read-only board.
|
|
2083
|
+
*/
|
|
2084
|
+
copy(ids: readonly string[]): void;
|
|
2085
|
+
/** `copy`, then removes every resolved object (recursively through any group) as one undoable step. */
|
|
2086
|
+
cut(ids: readonly string[]): void;
|
|
2087
|
+
/**
|
|
2088
|
+
* Clones the current clipboard contents onto the board as one undoable
|
|
2089
|
+
* step, offset the same small cascade `duplicate` uses (no cursor
|
|
2090
|
+
* position to paste relative to yet). Returns the new top-level ids —
|
|
2091
|
+
* a pasted group's own id stands for its (also-pasted) children, which
|
|
2092
|
+
* aren't listed separately. `[]` when the clipboard is empty.
|
|
2093
|
+
*/
|
|
2094
|
+
paste(): readonly string[];
|
|
2095
|
+
/**
|
|
2096
|
+
* Select every top-level object (Phase 8) — a group's own id stands for
|
|
2097
|
+
* its children, which aren't selected separately, matching `paste`'s own
|
|
2098
|
+
* "what the user sees" id list. Hidden objects are excluded, consistent
|
|
2099
|
+
* with them already being excluded from marquee selection. Works with
|
|
2100
|
+
* no canvas/engine, same as {@link toggleSelectionVisibility}.
|
|
2101
|
+
*/
|
|
2102
|
+
selectAll(): void;
|
|
919
2103
|
table: {
|
|
920
2104
|
addRow(tableId: string): void;
|
|
921
2105
|
addCol(tableId: string): void;
|
|
@@ -926,6 +2110,29 @@ interface BoardController {
|
|
|
926
2110
|
};
|
|
927
2111
|
select(ids: readonly string[]): void;
|
|
928
2112
|
import(document: SerializedBoardDocument): readonly string[];
|
|
2113
|
+
/**
|
|
2114
|
+
* Toggle lock state for the current selection (or focused note/text/
|
|
2115
|
+
* table/image/timer), matching whatever a single Host lock/unlock
|
|
2116
|
+
* control already does per object type. A no-op with nothing selected,
|
|
2117
|
+
* on a headless board, or when every actionable target is locked by
|
|
2118
|
+
* another collaborator who isn't the current lock holder.
|
|
2119
|
+
*/
|
|
2120
|
+
toggleSelectionLock(): void;
|
|
2121
|
+
/**
|
|
2122
|
+
* Toggle hidden state for the current selection, as one undo entry
|
|
2123
|
+
* (Phase 8). If any selected object is hidden, shows every selected
|
|
2124
|
+
* object; otherwise hides them all — same "any wins" semantics as
|
|
2125
|
+
* {@link toggleSelectionLock}. Hidden objects stay fully present in the
|
|
2126
|
+
* document (they still serialize, persist, sync, undo/redo) — they just
|
|
2127
|
+
* stop rendering and stop being hit-testable/selectable via pointer
|
|
2128
|
+
* interaction. Unlike `toggleSelectionLock`, this works on a headless
|
|
2129
|
+
* board too: it only touches `selection`/the document, no canvas or
|
|
2130
|
+
* engine involved. Custom objects have no visibility concept (no
|
|
2131
|
+
* `Hideable` field) and are silently skipped, matching how
|
|
2132
|
+
* `toggleSelectionLock` already excludes them. A no-op with nothing
|
|
2133
|
+
* selected or when the selection is only custom objects.
|
|
2134
|
+
*/
|
|
2135
|
+
toggleSelectionVisibility(): void;
|
|
929
2136
|
};
|
|
930
2137
|
readonly query: {
|
|
931
2138
|
get(id: string): DeepReadonly<BoardObject> | undefined;
|
|
@@ -954,6 +2161,15 @@ interface BoardController {
|
|
|
954
2161
|
follow(view: PresenceView): void;
|
|
955
2162
|
/** Ease the camera to a peer's view; returns false (no-op) while mid-stroke. */
|
|
956
2163
|
gather(view: PresenceView): boolean;
|
|
2164
|
+
/**
|
|
2165
|
+
* Publishes this client's own cursor/tool/view for other collaborators
|
|
2166
|
+
* (Phase 6, ADR 0015), via the configured `CollaborationAdapter` —
|
|
2167
|
+
* throttled internally (`presenceThrottleMs` option, default 50ms) so
|
|
2168
|
+
* a raw pointermove stream never becomes a message-per-event flood. A
|
|
2169
|
+
* no-op if no collaboration adapter is configured, or if the
|
|
2170
|
+
* configured one doesn't implement `updatePresence`.
|
|
2171
|
+
*/
|
|
2172
|
+
broadcast(local: LocalPresence): void;
|
|
957
2173
|
};
|
|
958
2174
|
readonly export: {
|
|
959
2175
|
svg(): string;
|
|
@@ -979,6 +2195,7 @@ interface BoardController {
|
|
|
979
2195
|
}>;
|
|
980
2196
|
dispose(): Promise<void>;
|
|
981
2197
|
}
|
|
2198
|
+
/** @deprecated Use `BoardController.getSnapshot()`'s return type instead. */
|
|
982
2199
|
type LocalBoardSnapshot = {
|
|
983
2200
|
documentId: string;
|
|
984
2201
|
selectedStrokeId: string | null;
|
|
@@ -987,6 +2204,7 @@ type LocalBoardSnapshot = {
|
|
|
987
2204
|
canRedo: boolean;
|
|
988
2205
|
disposed: boolean;
|
|
989
2206
|
};
|
|
2207
|
+
/** @deprecated Use `BoardController` (from `createBoardController`) instead — this stroke-only, single-tool surface predates the full capability-grouped controller. */
|
|
990
2208
|
type LocalBoard = {
|
|
991
2209
|
drawStroke(stroke: Stroke): void;
|
|
992
2210
|
selectAt(point: BoardPoint): string | null;
|
|
@@ -1000,30 +2218,83 @@ type LocalBoard = {
|
|
|
1000
2218
|
|
|
1001
2219
|
type ScrawlThemePreset = "light" | "dark";
|
|
1002
2220
|
type ScrawlDensity = "comfortable" | "compact";
|
|
2221
|
+
type ScrawlGridMode = "none" | "line" | "dot";
|
|
2222
|
+
type ScrawlSurfaceTexture = "flat" | "textured";
|
|
2223
|
+
/**
|
|
2224
|
+
* Every field is optional — anything you don't set falls back to the
|
|
2225
|
+
* chosen `preset` ("light" or "dark", see {@link resolveScrawlTheme}).
|
|
2226
|
+
* Overrides are semantic, board-local runtime configuration: they never
|
|
2227
|
+
* get written into the Document or into exports, so switching themes is
|
|
2228
|
+
* always non-destructive.
|
|
2229
|
+
*/
|
|
1003
2230
|
interface ScrawlTheme {
|
|
2231
|
+
/** UI chrome background — toolbar/panel base surface. Distinct from `boardSurface` (the canvas itself). */
|
|
1004
2232
|
surface?: string;
|
|
2233
|
+
/** UI chrome background, one step up from `surface` — popovers, dropdowns, elevated panels. */
|
|
1005
2234
|
surfaceRaised?: string;
|
|
2235
|
+
/** UI chrome background, one step down from `surface` — subtle fills, hover states. */
|
|
1006
2236
|
surfaceMuted?: string;
|
|
2237
|
+
/** Primary UI text color. Checked for contrast against `surface`. */
|
|
1007
2238
|
text?: string;
|
|
2239
|
+
/** Secondary/de-emphasized UI text color. Checked for contrast against `surface`. */
|
|
1008
2240
|
textMuted?: string;
|
|
2241
|
+
/** Borders and dividers between UI chrome elements. */
|
|
1009
2242
|
edge?: string;
|
|
2243
|
+
/**
|
|
2244
|
+
* Brand accent color. Drives the active/selected state of toolbar
|
|
2245
|
+
* controls (e.g. the active tool button): its icon/text render in this
|
|
2246
|
+
* color, and its background is automatically derived as a light tint of
|
|
2247
|
+
* it (via `color-mix`) — set this one token and both follow.
|
|
2248
|
+
*/
|
|
2249
|
+
primary?: string;
|
|
2250
|
+
/** Focus ring color. Checked for contrast against `surface`. */
|
|
1010
2251
|
focus?: string;
|
|
2252
|
+
/** Selection highlight color (e.g. selected list items, not board object selection). */
|
|
1011
2253
|
selection?: string;
|
|
2254
|
+
/** Destructive/error state color (delete confirmations, error text). */
|
|
1012
2255
|
danger?: string;
|
|
2256
|
+
/** Warning state color. */
|
|
1013
2257
|
warning?: string;
|
|
2258
|
+
/** Success/confirmation state color. */
|
|
1014
2259
|
success?: string;
|
|
2260
|
+
/** Font stack for UI chrome (toolbar labels, menus, dialogs). */
|
|
1015
2261
|
uiFontFamily?: string;
|
|
2262
|
+
/** Font stack for board content and data (e.g. table cell text). */
|
|
1016
2263
|
dataFontFamily?: string;
|
|
2264
|
+
/** Base UI font size in px. Range: 12–24. */
|
|
1017
2265
|
baseFontSize?: number;
|
|
2266
|
+
/** Regular UI font weight. Range: 300–900. */
|
|
1018
2267
|
regularWeight?: number;
|
|
2268
|
+
/** Emphasized UI font weight (headings, active states). Range: 300–900. */
|
|
1019
2269
|
strongWeight?: number;
|
|
2270
|
+
/** Corner radius for small controls (buttons, inputs) in px. Range: 0–32. */
|
|
1020
2271
|
controlRadius?: number;
|
|
2272
|
+
/** Corner radius for panels/dialogs in px. Range: 0–32. */
|
|
1021
2273
|
panelRadius?: number;
|
|
2274
|
+
/** CSS `box-shadow` value for subtle elevation (e.g. toolbar). */
|
|
1022
2275
|
elevationLow?: string;
|
|
2276
|
+
/** CSS `box-shadow` value for prominent elevation (e.g. modals). */
|
|
1023
2277
|
elevationHigh?: string;
|
|
2278
|
+
/** UI transition duration in ms. Range: 0–500. */
|
|
1024
2279
|
motionDuration?: number;
|
|
2280
|
+
/** CSS easing function for UI transitions. */
|
|
1025
2281
|
motionEasing?: string;
|
|
2282
|
+
/** UI chrome spacing/sizing scale. */
|
|
1026
2283
|
density?: ScrawlDensity;
|
|
2284
|
+
/** The rendered board/canvas surface color — distinct from `surface` (UI chrome panels). */
|
|
2285
|
+
boardSurface?: string;
|
|
2286
|
+
/** `"flat"` (default): a plain, uniform board surface. `"textured"`: a subtle "melamine" micro-noise + satin sheen, like a physical whiteboard. */
|
|
2287
|
+
boardSurfaceTexture?: ScrawlSurfaceTexture;
|
|
2288
|
+
/** `"none"` (default) keeps the board a plain surface; `"line"`/`"dot"` draw a zoom-adaptive reference grid. */
|
|
2289
|
+
gridMode?: ScrawlGridMode;
|
|
2290
|
+
/** Grid line/dot color. Ignored when `gridMode` is `"none"`. */
|
|
2291
|
+
gridColor?: string;
|
|
2292
|
+
/** Grid spacing in board units at 100% zoom. Ignored when `gridMode` is `"none"`. */
|
|
2293
|
+
gridSpacing?: number;
|
|
2294
|
+
/** On-screen grid line/dot width in CSS pixels. Range: 0.5–8. A reference aid, so unlike shape/ink strokes it stays this width at any zoom. */
|
|
2295
|
+
gridLineWidth?: number;
|
|
2296
|
+
/** Shape (rectangle, ellipse, arrow, ...) border width, in board units. Range: 0.01–5. Scales with zoom like ink, since it's part of the drawn content. */
|
|
2297
|
+
shapeStrokeWidth?: number;
|
|
1027
2298
|
}
|
|
1028
2299
|
type ResolvedScrawlTheme = Required<ScrawlTheme>;
|
|
1029
2300
|
interface ScrawlThemeDiagnostic {
|
|
@@ -1040,6 +2311,11 @@ declare const scrawlThemePresets: Readonly<Record<ScrawlThemePreset, Readonly<Re
|
|
|
1040
2311
|
declare function validateScrawlTheme(theme: ScrawlTheme | Record<string, unknown>): ScrawlThemeDiagnostic[];
|
|
1041
2312
|
declare function resolveScrawlTheme(preset?: ScrawlThemePreset, theme?: ScrawlTheme | Record<string, unknown>): ScrawlResolvedTheme;
|
|
1042
2313
|
|
|
2314
|
+
interface CommentAuthor {
|
|
2315
|
+
name: string;
|
|
2316
|
+
color?: string;
|
|
2317
|
+
}
|
|
2318
|
+
|
|
1043
2319
|
interface DefaultBoardChromeProps {
|
|
1044
2320
|
controller: BoardController;
|
|
1045
2321
|
snapshot: BoardSnapshot;
|
|
@@ -1048,8 +2324,22 @@ interface DefaultBoardChromeProps {
|
|
|
1048
2324
|
style?: React.CSSProperties;
|
|
1049
2325
|
regions?: Partial<Record<DefaultUIRegion, boolean>>;
|
|
1050
2326
|
slots?: DefaultUISlots;
|
|
2327
|
+
/**
|
|
2328
|
+
* Override any built-in tool icon. Tools without an override keep the
|
|
2329
|
+
* SDK's outline icon. Accessible labels and shortcut tooltips are preserved.
|
|
2330
|
+
*/
|
|
2331
|
+
icons?: Partial<Record<BuiltInTool, ReactNode>>;
|
|
2332
|
+
/** Name (and pin colour) shown on comments left through the default comment UI. Defaults to "You". */
|
|
2333
|
+
commentAuthor?: CommentAuthor;
|
|
1051
2334
|
}
|
|
1052
|
-
|
|
2335
|
+
|
|
2336
|
+
/**
|
|
2337
|
+
* `comments` is the built-in comment composer/thread, which keeps comments for
|
|
2338
|
+
* the session; `images` is the Image tool's file picker plus drag-and-drop.
|
|
2339
|
+
* Turn either off when the Host handles `comment-draft-request`/
|
|
2340
|
+
* `comment-open-request` or `import-request` itself.
|
|
2341
|
+
*/
|
|
2342
|
+
type DefaultUIRegion = "tools" | "history" | "view" | "style" | "search" | "import" | "export" | "inlineEditing" | "styleShelf" | "focusedItemToolbar" | "emptyState" | "pages" | "comments" | "images";
|
|
1053
2343
|
/** Props for the toolbar/topBar/stylePanel/contextMenu slots. */
|
|
1054
2344
|
interface BoardSlotProps {
|
|
1055
2345
|
controller: BoardController;
|
|
@@ -1077,7 +2367,7 @@ interface DefaultUISlots {
|
|
|
1077
2367
|
*/
|
|
1078
2368
|
contextMenu?: ComponentType<BoardSlotProps> | null;
|
|
1079
2369
|
}
|
|
1080
|
-
declare function DefaultBoardChrome({ controller, snapshot, renderPortal, className, style, regions, slots }: DefaultBoardChromeProps): react.JSX.Element;
|
|
2370
|
+
declare function DefaultBoardChrome({ controller, snapshot, renderPortal, className, style, regions, slots, icons, commentAuthor }: DefaultBoardChromeProps): react.JSX.Element;
|
|
1081
2371
|
|
|
1082
2372
|
interface InlineEditorsProps {
|
|
1083
2373
|
controller: BoardController;
|
|
@@ -1102,12 +2392,68 @@ interface MultiplayerCursorsProps {
|
|
|
1102
2392
|
*/
|
|
1103
2393
|
declare function MultiplayerCursors({ controller, onSelectUser, onJumpToUser }: MultiplayerCursorsProps): react.JSX.Element | null;
|
|
1104
2394
|
|
|
2395
|
+
interface FocusedItemToolbarProps {
|
|
2396
|
+
controller: BoardController;
|
|
2397
|
+
snapshot: BoardSnapshot;
|
|
2398
|
+
}
|
|
2399
|
+
/**
|
|
2400
|
+
* Floating toolbar above the focused note, shape, text block, or semantic
|
|
2401
|
+
* Rectangle/Ellipse — Colour, Size (note/text) or Width (shape), Fill/Stroke
|
|
2402
|
+
* (Rectangle/Ellipse), Lock/Unlock, Duplicate, Delete. Images (photos and
|
|
2403
|
+
* placed emoji/icons) get Lock/Duplicate/Delete only — the native gizmo
|
|
2404
|
+
* already resizes them. Table/timer/custom objects and plain (non-shape) ink
|
|
2405
|
+
* strokes never get a toolbar here — out of scope for this destination.
|
|
2406
|
+
*
|
|
2407
|
+
* Rectangle/Ellipse (Phase 2) are a second, parallel "shape" concept from
|
|
2408
|
+
* the legacy ink-stroke shape below: both draw from the same toolbar
|
|
2409
|
+
* buttons and look the same to a user, but a Rectangle/Ellipse is a real
|
|
2410
|
+
* BoardObject with its own resize handles (independent width/height,
|
|
2411
|
+
* `ShapeResizeHandle`), while a legacy shape-stroke keeps going through the
|
|
2412
|
+
* native selection gizmo. This duality is deliberate and temporary — see
|
|
2413
|
+
* docs/reports/phase-2-document-object-model.md.
|
|
2414
|
+
*
|
|
2415
|
+
* Lock/Unlock carries no per-user ownership gating: nothing else in this
|
|
2416
|
+
* SDK enforces lock ownership either (`content.update` never checks
|
|
2417
|
+
* `locked`, and the engine's own internal unlock check has no way for a
|
|
2418
|
+
* Host to ever supply a real user id) — locking is advisory UI state
|
|
2419
|
+
* throughout, and this toolbar matches that rather than inventing an
|
|
2420
|
+
* enforcement story alone.
|
|
2421
|
+
*
|
|
2422
|
+
* A shape can be several strokes sharing one `clusterId` (e.g. an arrow's
|
|
2423
|
+
* shaft + head) — every action here applies to the whole cluster. Resolved
|
|
2424
|
+
* via `query.all()` + `clusterId`, not `snapshot.selection`: a canvas click
|
|
2425
|
+
* on one member selects every member internally, but the public selection
|
|
2426
|
+
* bridge only ever reports one id (`FocusedItem.id` is singular by design),
|
|
2427
|
+
* so reconstructing the cluster from the document is the reliable path
|
|
2428
|
+
* regardless of how the selection was made.
|
|
2429
|
+
*
|
|
2430
|
+
* A focused note also gets drag-to-resize corner handles — notes have no
|
|
2431
|
+
* gizmo of their own (the native selection gizmo is ink-stroke/shape-only),
|
|
2432
|
+
* so this is that capability's default-ui-owned equivalent. Proportional
|
|
2433
|
+
* (always-square) resize from the note's own centre, clamped to
|
|
2434
|
+
* `[MIN_SIZE, MAX_SIZE]`, independent of which corner is grabbed.
|
|
2435
|
+
*/
|
|
2436
|
+
declare function FocusedItemToolbar({ controller, snapshot }: FocusedItemToolbarProps): react.JSX.Element | null;
|
|
2437
|
+
|
|
1105
2438
|
interface StyleShelfProps {
|
|
1106
2439
|
controller: BoardController;
|
|
1107
2440
|
snapshot: BoardSnapshot;
|
|
2441
|
+
/**
|
|
2442
|
+
* Root-relative point beside the active tool. DefaultBoardChrome supplies
|
|
2443
|
+
* this automatically and keeps the shelf within the board bounds.
|
|
2444
|
+
*/
|
|
2445
|
+
anchor?: {
|
|
2446
|
+
x: number;
|
|
2447
|
+
y: number;
|
|
2448
|
+
};
|
|
2449
|
+
/**
|
|
2450
|
+
* Called after an option is picked (not while typing table dimensions or a
|
|
2451
|
+
* sticker search). DefaultBoardChrome uses it to close the shelf.
|
|
2452
|
+
*/
|
|
2453
|
+
onChoose?(): void;
|
|
1108
2454
|
}
|
|
1109
2455
|
/** Contextual per-tool style controls — visible while a styleable tool is active. */
|
|
1110
|
-
declare function StyleShelf({ controller, snapshot }: StyleShelfProps): react.JSX.Element | null;
|
|
2456
|
+
declare function StyleShelf({ controller, snapshot, anchor, onChoose }: StyleShelfProps): react.JSX.Element | null;
|
|
1111
2457
|
|
|
1112
2458
|
type ThemeStyle = CSSProperties & Record<`--scrawl-${string}`, string | number | undefined>;
|
|
1113
2459
|
interface ScrawlProviderProps {
|
|
@@ -1121,6 +2467,16 @@ interface ScrawlProviderProps {
|
|
|
1121
2467
|
className?: string;
|
|
1122
2468
|
style?: ThemeStyle;
|
|
1123
2469
|
}
|
|
2470
|
+
/**
|
|
2471
|
+
* Wraps a Board `controller` you created yourself (via `createBoardController`)
|
|
2472
|
+
* so its descendants can use `useScrawlController`/`useScrawlSnapshot`/etc.
|
|
2473
|
+
* and render its default UI pieces (`DefaultBoardChrome`, `StyleShelf`, ...).
|
|
2474
|
+
* Prefer `Scrawl` unless you need to construct or own the controller's
|
|
2475
|
+
* lifecycle yourself (e.g. you create it outside React, or need it before
|
|
2476
|
+
* first render). Set `disposeOnUnmount` to have this provider call
|
|
2477
|
+
* `controller.dispose()` on unmount; otherwise disposal remains your own
|
|
2478
|
+
* responsibility.
|
|
2479
|
+
*/
|
|
1124
2480
|
declare function ScrawlProvider({ controller, children, preset, theme, portalContainer: customPortal, disposeOnUnmount, onThemeDiagnostic, className, style }: ScrawlProviderProps): react.JSX.Element;
|
|
1125
2481
|
interface ScrawlProps extends Omit<CreateBoardControllerOptions, "canvas"> {
|
|
1126
2482
|
children?: ReactNode;
|
|
@@ -1134,8 +2490,25 @@ interface ScrawlProps extends Omit<CreateBoardControllerOptions, "canvas"> {
|
|
|
1134
2490
|
onThemeDiagnostic?: (diagnostic: ScrawlThemeDiagnostic) => void;
|
|
1135
2491
|
/** Provide null for a headless Board, or an existing canvas to control its identity. */
|
|
1136
2492
|
canvas?: HTMLCanvasElement | null;
|
|
2493
|
+
/**
|
|
2494
|
+
* Per-tool icon override for the default toolbar and More tools buttons —
|
|
2495
|
+
* only applies when you don't supply `children` (i.e. you're using the
|
|
2496
|
+
* SDK's default UI). See DefaultBoardChromeProps.icons.
|
|
2497
|
+
*/
|
|
2498
|
+
icons?: Partial<Record<BuiltInTool, ReactNode>>;
|
|
1137
2499
|
}
|
|
1138
|
-
|
|
2500
|
+
/**
|
|
2501
|
+
* The fastest path to an embedded Board: creates and owns a
|
|
2502
|
+
* `BoardController` for you (constructed once, disposed on unmount) and
|
|
2503
|
+
* renders it into a canvas. Render with no `children` to get the SDK's
|
|
2504
|
+
* default toolbar/UI chrome, or supply your own `children` (using the
|
|
2505
|
+
* `useScrawlController`/`useScrawlSnapshot` hooks, or the exported
|
|
2506
|
+
* `DefaultBoardChrome`/`StyleShelf`/etc. pieces) to build a custom UI on
|
|
2507
|
+
* top of the same controller. Accepts every `CreateBoardControllerOptions`
|
|
2508
|
+
* field except `canvas` (pass `canvas={null}` for a headless board, or an
|
|
2509
|
+
* existing `<canvas>` element to control its identity yourself).
|
|
2510
|
+
*/
|
|
2511
|
+
declare function Scrawl({ children, preset, theme, portalContainer, className, style, onReady, onError, onThemeDiagnostic, canvas: suppliedCanvas, icons, ...options }: ScrawlProps): react.JSX.Element;
|
|
1139
2512
|
interface ScrawlCanvasProps {
|
|
1140
2513
|
element?: HTMLCanvasElement;
|
|
1141
2514
|
className?: string;
|
|
@@ -1150,14 +2523,50 @@ interface ScrawlDefaultUIProps {
|
|
|
1150
2523
|
regions?: Partial<Record<DefaultUIRegion, boolean>>;
|
|
1151
2524
|
/** Replace (component) or hide (null) a coarse region; omit for the SDK default. */
|
|
1152
2525
|
slots?: DefaultUISlots;
|
|
2526
|
+
/** Per-tool icon override for the toolbar and More tools buttons — see DefaultBoardChromeProps.icons. */
|
|
2527
|
+
icons?: Partial<Record<BuiltInTool, ReactNode>>;
|
|
2528
|
+
/** Author shown on comments left through the default comment UI — see DefaultBoardChromeProps.commentAuthor. */
|
|
2529
|
+
commentAuthor?: CommentAuthor;
|
|
1153
2530
|
}
|
|
1154
|
-
declare function ScrawlDefaultUI({ className, style, regions, slots }: ScrawlDefaultUIProps): react.JSX.Element;
|
|
2531
|
+
declare function ScrawlDefaultUI({ className, style, regions, slots, icons, commentAuthor }: ScrawlDefaultUIProps): react.JSX.Element;
|
|
1155
2532
|
declare function ScrawlPortal({ children }: {
|
|
1156
2533
|
children: ReactNode;
|
|
1157
2534
|
}): react.ReactPortal | null;
|
|
1158
2535
|
declare function useScrawlController(): BoardController;
|
|
1159
2536
|
declare function useScrawlTheme(): ScrawlResolvedTheme;
|
|
1160
2537
|
declare function useScrawlSnapshot(): BoardSnapshot;
|
|
2538
|
+
/**
|
|
2539
|
+
* A thin, purely-derived convenience over
|
|
2540
|
+
* `useScrawlSnapshot().connection.persistence` (Phase 5) — for a Host
|
|
2541
|
+
* component that only cares about save status (e.g. a
|
|
2542
|
+
* "Saving…"/"Saved"/"Offline" indicator) and would otherwise re-derive
|
|
2543
|
+
* this same field access itself. Adds no state and no behavior of its
|
|
2544
|
+
* own: React still owns none of persistence, exactly as before — this
|
|
2545
|
+
* hook only re-reads what the controller already tracks.
|
|
2546
|
+
*
|
|
2547
|
+
* Deliberately does *not* go through `useScrawlSnapshot()` (Phase 9):
|
|
2548
|
+
* `changed()` rebuilds the whole `BoardSnapshot` — including a fresh
|
|
2549
|
+
* `connection.persistence` object — on every document mutation, even ones
|
|
2550
|
+
* that never touch persistence at all, so a component using only this
|
|
2551
|
+
* hook would otherwise re-render on every stroke draw. `useSyncStatus`
|
|
2552
|
+
* below memoizes on the value (`state`/`error`), not just the object
|
|
2553
|
+
* reference, and returns the same cached value across renders where
|
|
2554
|
+
* nothing relevant changed — the same shape `usePresence` already uses
|
|
2555
|
+
* for its own independently-scoped store.
|
|
2556
|
+
*/
|
|
2557
|
+
declare function usePersistenceStatus(): PersistenceSnapshot;
|
|
2558
|
+
/** 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). */
|
|
2559
|
+
declare function useCollaborationStatus(): CollaborationSnapshot;
|
|
2560
|
+
/**
|
|
2561
|
+
* The current presence roster (Phase 6), live-updating — wraps
|
|
2562
|
+
* `controller.presence.list()`/`subscribe` the same way `useScrawlSnapshot`
|
|
2563
|
+
* wraps the controller's main snapshot, via `useSyncExternalStore`. Does
|
|
2564
|
+
* not re-render on every remote pointer move by itself; it re-renders
|
|
2565
|
+
* whenever the roster the controller already tracks changes, at whatever
|
|
2566
|
+
* rate that arrives at (throttled adapter-side, per `presenceThrottleMs`).
|
|
2567
|
+
*/
|
|
2568
|
+
declare function usePresence(): readonly PresenceUser[];
|
|
2569
|
+
/** @deprecated Use `Scrawl` instead — this predates the full capability-grouped `BoardController` and only exposes the stroke-only `LocalBoard` surface. */
|
|
1161
2570
|
type ScrawlBoardProps = {
|
|
1162
2571
|
documentId: string;
|
|
1163
2572
|
initialDocument?: SerializedBoardDocument;
|
|
@@ -1165,7 +2574,8 @@ type ScrawlBoardProps = {
|
|
|
1165
2574
|
className?: string;
|
|
1166
2575
|
style?: CSSProperties;
|
|
1167
2576
|
};
|
|
2577
|
+
/** @deprecated Use `Scrawl` instead — this predates the full capability-grouped `BoardController` and only exposes the stroke-only `LocalBoard` surface. */
|
|
1168
2578
|
declare function ScrawlBoard({ documentId, initialDocument, onReady, className, style }: ScrawlBoardProps): react.JSX.Element;
|
|
1169
2579
|
|
|
1170
|
-
export { DefaultBoardChrome, InlineEditors, MultiplayerCursors, Scrawl, ScrawlBoard, ScrawlCanvas, ScrawlDefaultUI, ScrawlPortal, ScrawlProvider, StyleShelf, resolveScrawlTheme, scrawlThemePresets, useScrawlController, useScrawlSnapshot, useScrawlTheme, validateScrawlTheme };
|
|
1171
|
-
export type { BoardController, BoardSlotProps, BoardSnapshot, BoardStyle, CommentMarker, CreateBoardControllerOptions, DefaultBoardChromeProps, DefaultUIRegion, DefaultUISlot, DefaultUISlots, DialogSlotProps, InlineEditorsProps, LocalBoard, LocalBoardSnapshot, MultiplayerCursorsProps, PresenceCursor, PresenceUser, PresenceView, ScrawlBoardProps, ScrawlCanvasProps, ScrawlDefaultUIProps, ScrawlProps, ScrawlProviderProps, ScrawlResolvedTheme, ScrawlTheme, ScrawlThemeDiagnostic, ScrawlThemePreset, StyleShelfProps };
|
|
2580
|
+
export { AssetResolutionError, DefaultBoardChrome, FocusedItemToolbar, InlineEditors, MultiplayerCursors, SUPPORTED_ASSET_MEDIA_TYPES, Scrawl, ScrawlBoard, ScrawlCanvas, ScrawlDefaultUI, ScrawlPortal, ScrawlProvider, StyleShelf, assetRef, clampAssetCacheBytes, cloneCustomObject, isAssetRef, resolveScrawlTheme, scrawlThemePresets, useCollaborationStatus, usePersistenceStatus, usePresence, useScrawlController, useScrawlSnapshot, useScrawlTheme, validateScrawlTheme };
|
|
2581
|
+
export type { AssetDiagnostic, AssetExportFailure, AssetIngestRequest, AssetIngestResult, AssetIngestor, AssetKind, AssetPurpose, AssetRef, AssetResolutionErrorCode, AssetResolveRequest, AssetResolveResult, AssetResolver, BoardController, BoardKeyInput, BoardNotebook, BoardPage, BoardPointerInput, BoardScene, BoardSection, BoardSlotProps, BoardSnapshot, BoardStyle, BoardThemeOptions, CollaborationSnapshot, CommentAuthor, CommentMarker, CreateBoardControllerOptions, CustomBoardObject, CustomObjectAddInput, CustomObjectDefinition, CustomTool, CustomToolDefinition, DefaultBoardChromeProps, DefaultUIRegion, DefaultUISlot, DefaultUISlots, DialogSlotProps, ExportDocumentSVGOptions, ExportDocumentSVGResult, ExtensionCommand, ExtensionDiagnostic, ExtensionHitResult, ExtensionId, ExtensionRequirement, FocusedItem, FocusedItemToolbarProps, InlineEditorsProps, InputModifiers, JsonObject, JsonValue, LocalBoard, LocalBoardSnapshot, LocalPresence, Mat2x3, MultiplayerCursorsProps, ObjectDescribeContext, ObjectIntent, ObjectType, PresenceCursor, PresenceUser, PresenceView, QueryableBoardObject, ReadonlyCustomObject, SceneEllipse, SceneGroup, SceneImage, ScenePath, SceneRect, SceneText, ScrawlBoardProps, ScrawlCanvasProps, ScrawlDefaultUIProps, ScrawlDensity, ScrawlExtension, ScrawlGridMode, ScrawlProps, ScrawlProviderProps, ScrawlResolvedTheme, ScrawlSurfaceTexture, ScrawlTheme, ScrawlThemeDiagnostic, ScrawlThemePreset, SearchHit, SearchHitKind, SearchableComment, StyleShelfProps, SupportedAssetMediaType, ToolCancelReason, ToolCapabilities, ToolCursor, ToolId };
|