@scrawl-board/board 0.1.0-beta.0 → 0.1.0-beta.1
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/dist/browser.d.ts +38 -7
- package/dist/browser.js +128 -19
- package/dist/core.d.ts +96 -25
- package/dist/core.js +44 -4
- package/dist/index.d.ts +69 -30
- package/dist/index.js +178 -23
- package/dist/react.d.ts +46 -7
- package/dist/react.js +170 -23
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3,6 +3,8 @@ import { createPortal } from "react-dom";
|
|
|
3
3
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
4
4
|
//#region src/core-internal/assets.ts
|
|
5
5
|
var ASSET_REF_PATTERN = /^asset:[a-z0-9](?:[a-z0-9.-]{0,61}[a-z0-9])?:[A-Za-z0-9._~-]+$/;
|
|
6
|
+
/** Hard ceiling on a reference's own wire length — independent of any resource limit below. */
|
|
7
|
+
var ASSET_REF_MAX_BYTES = 512;
|
|
6
8
|
function isAssetRef(value) {
|
|
7
9
|
return typeof value === "string" && ASSET_REF_PATTERN.test(value) && new TextEncoder().encode(value).length <= 512;
|
|
8
10
|
}
|
|
@@ -20,9 +22,20 @@ var AssetResolutionError = class extends Error {
|
|
|
20
22
|
this.name = "AssetResolutionError";
|
|
21
23
|
}
|
|
22
24
|
};
|
|
25
|
+
var ASSET_MAX_ENCODED_BYTES = 20 * 1024 * 1024;
|
|
26
|
+
var ASSET_MAX_DIMENSION_PX = 8192;
|
|
27
|
+
var ASSET_MAX_DECODED_MEGAPIXELS = 40;
|
|
28
|
+
var ASSET_MAX_CONCURRENT_RESOLUTIONS = 6;
|
|
23
29
|
var ASSET_CACHE_BYTES_DEFAULT = 256 * 1024 * 1024;
|
|
24
30
|
var ASSET_CACHE_BYTES_MIN = 64 * 1024 * 1024;
|
|
25
31
|
var ASSET_CACHE_BYTES_MAX = 512 * 1024 * 1024;
|
|
32
|
+
var ASSET_EXPORT_MAX_ENCODED_BYTES = 100 * 1024 * 1024;
|
|
33
|
+
var ASSET_EXPORT_MAX_DECODED_MEGAPIXELS = 100;
|
|
34
|
+
var SUPPORTED_ASSET_MEDIA_TYPES = [
|
|
35
|
+
"image/png",
|
|
36
|
+
"image/jpeg",
|
|
37
|
+
"image/webp"
|
|
38
|
+
];
|
|
26
39
|
function clampAssetCacheBytes(value) {
|
|
27
40
|
if (value === void 0 || !Number.isFinite(value)) return ASSET_CACHE_BYTES_DEFAULT;
|
|
28
41
|
return Math.min(ASSET_CACHE_BYTES_MAX, Math.max(ASSET_CACHE_BYTES_MIN, Math.floor(value)));
|
|
@@ -466,7 +479,6 @@ function measureTable(table) {
|
|
|
466
479
|
height: table.rowHeights.reduce((sum, h) => sum + h, 0)
|
|
467
480
|
};
|
|
468
481
|
}
|
|
469
|
-
var BOARD_COLOR = "#FFFFFF";
|
|
470
482
|
var FOG_COLOR = "#FFFFFF";
|
|
471
483
|
function cloneImage(img) {
|
|
472
484
|
return { ...img };
|
|
@@ -1963,12 +1975,19 @@ var LIGHT = {
|
|
|
1963
1975
|
var MARGIN = 4;
|
|
1964
1976
|
var NOTE_FONT_RATIO = 44 / 512;
|
|
1965
1977
|
var NOTE_PAD_RATIO = 40 / 512;
|
|
1978
|
+
/** Matches the theme system's light-preset `boardSurface` default (theme.ts) — kept in sync manually, since core-internal stays decoupled from the theme layer. */
|
|
1979
|
+
var DEFAULT_BACKGROUND_COLOR = "#f5f5f3";
|
|
1966
1980
|
/**
|
|
1967
1981
|
* `registry` is optional and only enables rendering Custom objects through
|
|
1968
1982
|
* their own `describe()` — without it (or for an object whose extension
|
|
1969
1983
|
* isn't in it), Custom objects still export via the standard fallback
|
|
1970
1984
|
* placeholder (`fallback.bounds`/`label`), never silently dropped.
|
|
1971
1985
|
*
|
|
1986
|
+
* `backgroundColor` should be the Host's actual resolved `boardTheme.surface`
|
|
1987
|
+
* so an export matches what was on screen; defaults to the theme system's
|
|
1988
|
+
* own light-preset default when the caller doesn't have one on hand. The
|
|
1989
|
+
* board's reference grid, if any, is a screen-only aid and never exported.
|
|
1990
|
+
*
|
|
1972
1991
|
* `resolvedAssets` (ticket #23) maps an Asset reference to an already-
|
|
1973
1992
|
* resolved `data:` URI — see `assetExport.ts`'s `exportDocumentSVGWithAssets`,
|
|
1974
1993
|
* which is the only intended caller that ever passes one. Without it, every
|
|
@@ -1976,7 +1995,7 @@ var NOTE_PAD_RATIO = 40 / 512;
|
|
|
1976
1995
|
* placeholder instead of guessing at a URL; a legacy `src`-backed image is
|
|
1977
1996
|
* unaffected either way.
|
|
1978
1997
|
*/
|
|
1979
|
-
function documentToSVG(doc, now = 0, registry, resolvedAssets) {
|
|
1998
|
+
function documentToSVG(doc, now = 0, registry, backgroundColor = DEFAULT_BACKGROUND_COLOR, resolvedAssets) {
|
|
1980
1999
|
const notes = doc.notes ?? [];
|
|
1981
2000
|
const texts = doc.textBlocks ?? [];
|
|
1982
2001
|
const tables = doc.tables ?? [];
|
|
@@ -1989,7 +2008,7 @@ function documentToSVG(doc, now = 0, registry, resolvedAssets) {
|
|
|
1989
2008
|
const highlights = doc.strokes.filter((s) => s.tool === "highlighter");
|
|
1990
2009
|
const ink = doc.strokes.filter((s) => s.tool !== "highlighter");
|
|
1991
2010
|
return `<svg xmlns="http://www.w3.org/2000/svg" viewBox="${view}">\n<defs>\n${defs}\n</defs>\n${[
|
|
1992
|
-
`<rect x="${fmt(bounds.minX)}" y="${fmt(bounds.minY)}" width="${fmt(bounds.width)}" height="${fmt(bounds.height)}" fill="${
|
|
2011
|
+
`<rect x="${fmt(bounds.minX)}" y="${fmt(bounds.minY)}" width="${fmt(bounds.width)}" height="${fmt(bounds.height)}" fill="${backgroundColor}"/>`,
|
|
1993
2012
|
...images.map((image) => imageToElement(image, resolvedAssets)),
|
|
1994
2013
|
...tables.map(tableToGroup),
|
|
1995
2014
|
...highlights.flatMap(strokeToPaths),
|
|
@@ -2909,7 +2928,7 @@ var SvgAssetExportError = class extends Error {
|
|
|
2909
2928
|
this.name = "SvgAssetExportError";
|
|
2910
2929
|
}
|
|
2911
2930
|
};
|
|
2912
|
-
async function exportDocumentSVGWithAssets(doc, now, registry, assetController, options) {
|
|
2931
|
+
async function exportDocumentSVGWithAssets(doc, now, registry, assetController, backgroundColor, options) {
|
|
2913
2932
|
const refs = collectDocumentAssetRefs(doc, registry);
|
|
2914
2933
|
const resolvedAssets = /* @__PURE__ */ new Map();
|
|
2915
2934
|
const missingAssets = [];
|
|
@@ -2945,7 +2964,7 @@ async function exportDocumentSVGWithAssets(doc, now, registry, assetController,
|
|
|
2945
2964
|
}
|
|
2946
2965
|
if (missingAssets.length > 0 && options?.missingAssets !== "placeholder") throw new SvgAssetExportError(missingAssets);
|
|
2947
2966
|
return {
|
|
2948
|
-
svg: documentToSVG(doc, now, registry, resolvedAssets),
|
|
2967
|
+
svg: documentToSVG(doc, now, registry, backgroundColor, resolvedAssets),
|
|
2949
2968
|
missingAssets
|
|
2950
2969
|
};
|
|
2951
2970
|
}
|
|
@@ -35866,6 +35885,21 @@ var FOG_GLSL = `
|
|
|
35866
35885
|
`;
|
|
35867
35886
|
//#endregion
|
|
35868
35887
|
//#region ../../src/scrawl/engine/boardSurface.ts
|
|
35888
|
+
var GRID_MODE_CODE = {
|
|
35889
|
+
none: 0,
|
|
35890
|
+
line: 1,
|
|
35891
|
+
dot: 2
|
|
35892
|
+
};
|
|
35893
|
+
/** Used when a caller doesn't supply one (e.g. ScrawlEngine constructed without a Host theme). */
|
|
35894
|
+
var DEFAULT_BOARD_SURFACE_THEME = {
|
|
35895
|
+
color: "#f5f5f3",
|
|
35896
|
+
gridMode: "none",
|
|
35897
|
+
gridColor: "#deded8",
|
|
35898
|
+
gridSpacing: 5
|
|
35899
|
+
};
|
|
35900
|
+
/** Grid alpha fades out below this on-screen spacing (px) — otherwise it aliases into a solid wash when zoomed out. */
|
|
35901
|
+
var GRID_FADE_MIN_PX = 3;
|
|
35902
|
+
var GRID_FADE_MAX_PX = 8;
|
|
35869
35903
|
var VERTEX$1 = `
|
|
35870
35904
|
varying vec3 vWorldPos;
|
|
35871
35905
|
void main() {
|
|
@@ -35879,6 +35913,11 @@ var FRAGMENT = `
|
|
|
35879
35913
|
uniform vec3 uFogColor;
|
|
35880
35914
|
uniform float uFogNear;
|
|
35881
35915
|
uniform float uFogFar;
|
|
35916
|
+
uniform float uGridMode;
|
|
35917
|
+
uniform vec3 uGridColor;
|
|
35918
|
+
uniform float uGridSpacing;
|
|
35919
|
+
uniform float uGridLineWorld;
|
|
35920
|
+
uniform float uGridFade;
|
|
35882
35921
|
varying vec3 vWorldPos;
|
|
35883
35922
|
|
|
35884
35923
|
float hash(vec2 p) {
|
|
@@ -35897,6 +35936,22 @@ var FRAGMENT = `
|
|
|
35897
35936
|
|
|
35898
35937
|
${FOG_GLSL}
|
|
35899
35938
|
|
|
35939
|
+
// Distance from p's nearest axis-aligned grid line, in world units.
|
|
35940
|
+
float gridLineAlpha(vec2 p) {
|
|
35941
|
+
vec2 cell = mod(p, uGridSpacing);
|
|
35942
|
+
vec2 distToLine = min(cell, uGridSpacing - cell);
|
|
35943
|
+
vec2 lineAlpha2 = 1.0 - smoothstep(uGridLineWorld, uGridLineWorld * 2.0, distToLine);
|
|
35944
|
+
return max(lineAlpha2.x, lineAlpha2.y);
|
|
35945
|
+
}
|
|
35946
|
+
|
|
35947
|
+
// Distance from p's nearest grid intersection, in world units.
|
|
35948
|
+
float gridDotAlpha(vec2 p) {
|
|
35949
|
+
vec2 cell = mod(p, uGridSpacing) - uGridSpacing * 0.5;
|
|
35950
|
+
float dist = length(cell);
|
|
35951
|
+
float radius = uGridLineWorld * 1.5;
|
|
35952
|
+
return 1.0 - smoothstep(radius, radius * 2.0, dist);
|
|
35953
|
+
}
|
|
35954
|
+
|
|
35900
35955
|
void main() {
|
|
35901
35956
|
vec3 col = uColor;
|
|
35902
35957
|
|
|
@@ -35914,16 +35969,26 @@ var FRAGMENT = `
|
|
|
35914
35969
|
float spec = pow(max(dot(N, H), 0.0), 90.0);
|
|
35915
35970
|
col += (fres * 0.18 + spec * 0.06) * vec3(1.0);
|
|
35916
35971
|
|
|
35972
|
+
if (uGridMode > 0.5 && uGridFade > 0.001) {
|
|
35973
|
+
float gridAlpha = (uGridMode < 1.5 ? gridLineAlpha(vWorldPos.xy) : gridDotAlpha(vWorldPos.xy)) * uGridFade;
|
|
35974
|
+
col = mix(col, uGridColor, gridAlpha);
|
|
35975
|
+
}
|
|
35976
|
+
|
|
35917
35977
|
col = applyBoardFog(col, vWorldPos);
|
|
35918
35978
|
gl_FragColor = vec4(col, 1.0);
|
|
35919
35979
|
}
|
|
35920
35980
|
`;
|
|
35921
|
-
function createBoardSurface(fog) {
|
|
35981
|
+
function createBoardSurface(fog, theme) {
|
|
35922
35982
|
const material = new ShaderMaterial({
|
|
35923
35983
|
vertexShader: VERTEX$1,
|
|
35924
35984
|
fragmentShader: FRAGMENT,
|
|
35925
35985
|
uniforms: {
|
|
35926
|
-
uColor: { value: new Color(
|
|
35986
|
+
uColor: { value: new Color(theme.color) },
|
|
35987
|
+
uGridMode: { value: GRID_MODE_CODE[theme.gridMode] },
|
|
35988
|
+
uGridColor: { value: new Color(theme.gridColor) },
|
|
35989
|
+
uGridSpacing: { value: Math.max(.001, theme.gridSpacing) },
|
|
35990
|
+
uGridLineWorld: { value: .05 },
|
|
35991
|
+
uGridFade: { value: 0 },
|
|
35927
35992
|
...fog
|
|
35928
35993
|
}
|
|
35929
35994
|
});
|
|
@@ -35931,11 +35996,32 @@ function createBoardSurface(fog) {
|
|
|
35931
35996
|
mesh.name = "board-surface";
|
|
35932
35997
|
return mesh;
|
|
35933
35998
|
}
|
|
35934
|
-
/**
|
|
35935
|
-
function
|
|
35999
|
+
/** Applies a theme change (initial or live) — everything except the per-frame fade/line-width. */
|
|
36000
|
+
function setBoardSurfaceTheme(mesh, theme) {
|
|
36001
|
+
const uniforms = mesh.material.uniforms;
|
|
36002
|
+
uniforms.uColor.value.set(theme.color);
|
|
36003
|
+
uniforms.uGridMode.value = GRID_MODE_CODE[theme.gridMode];
|
|
36004
|
+
uniforms.uGridColor.value.set(theme.gridColor);
|
|
36005
|
+
uniforms.uGridSpacing.value = Math.max(.001, theme.gridSpacing);
|
|
36006
|
+
}
|
|
36007
|
+
/** Keep the plane centered under the view, larger than the fog horizon, and the grid's on-screen weight/fade current. */
|
|
36008
|
+
function updateBoardSurface(mesh, centerX, centerY, fogFar, worldPerPixel) {
|
|
35936
36009
|
mesh.position.set(centerX, centerY, 0);
|
|
35937
36010
|
const size = fogFar * 2.5;
|
|
35938
36011
|
mesh.scale.set(size, size, 1);
|
|
36012
|
+
const uniforms = mesh.material.uniforms;
|
|
36013
|
+
uniforms.uGridLineWorld.value = worldPerPixel;
|
|
36014
|
+
const t = (uniforms.uGridSpacing.value / Math.max(1e-6, worldPerPixel) - GRID_FADE_MIN_PX) / (GRID_FADE_MAX_PX - GRID_FADE_MIN_PX);
|
|
36015
|
+
uniforms.uGridFade.value = MathUtils.clamp(t, 0, 1);
|
|
36016
|
+
}
|
|
36017
|
+
/** Temporarily zeroes the grid's contribution (for export snapshots) and returns a restore function. */
|
|
36018
|
+
function suppressBoardSurfaceGrid(mesh) {
|
|
36019
|
+
const uniforms = mesh.material.uniforms;
|
|
36020
|
+
const previous = uniforms.uGridFade.value;
|
|
36021
|
+
uniforms.uGridFade.value = 0;
|
|
36022
|
+
return () => {
|
|
36023
|
+
uniforms.uGridFade.value = previous;
|
|
36024
|
+
};
|
|
35939
36025
|
}
|
|
35940
36026
|
var MIN_HEIGHT = 40;
|
|
35941
36027
|
var MAX_HEIGHT = 1500;
|
|
@@ -36099,7 +36185,7 @@ var CameraRig = class {
|
|
|
36099
36185
|
//#endregion
|
|
36100
36186
|
//#region ../../src/scrawl/engine/scene.ts
|
|
36101
36187
|
var SceneRig = class {
|
|
36102
|
-
constructor(canvas) {
|
|
36188
|
+
constructor(canvas, boardTheme) {
|
|
36103
36189
|
this.renderer = new WebGLRenderer({
|
|
36104
36190
|
canvas,
|
|
36105
36191
|
antialias: true
|
|
@@ -36109,7 +36195,7 @@ var SceneRig = class {
|
|
|
36109
36195
|
this.scene = new Scene();
|
|
36110
36196
|
this.cameraRig = new CameraRig(canvas.clientWidth / Math.max(1, canvas.clientHeight));
|
|
36111
36197
|
this.fog = createFogUniforms(FOG_COLOR);
|
|
36112
|
-
this.board = createBoardSurface(this.fog);
|
|
36198
|
+
this.board = createBoardSurface(this.fog, boardTheme);
|
|
36113
36199
|
this.scene.add(this.board);
|
|
36114
36200
|
this.inkGroup = new Group();
|
|
36115
36201
|
this.inkGroup.name = "ink";
|
|
@@ -36126,9 +36212,17 @@ var SceneRig = class {
|
|
|
36126
36212
|
renderFrame(dtMs) {
|
|
36127
36213
|
this.cameraRig.update(dtMs);
|
|
36128
36214
|
updateFogForCamera(this.fog, this.cameraRig.height);
|
|
36129
|
-
updateBoardSurface(this.board, this.cameraRig.center.x, this.cameraRig.center.y, this.fog.uFogFar.value);
|
|
36215
|
+
updateBoardSurface(this.board, this.cameraRig.center.x, this.cameraRig.center.y, this.fog.uFogFar.value, this.cameraRig.worldPerPixel());
|
|
36130
36216
|
this.renderer.render(this.scene, this.cameraRig.camera);
|
|
36131
36217
|
}
|
|
36218
|
+
/** Live theme update — the board controller's identity stays fixed across theme changes; this is how a new one reaches the render engine. */
|
|
36219
|
+
applyBoardTheme(theme) {
|
|
36220
|
+
setBoardSurfaceTheme(this.board, theme);
|
|
36221
|
+
}
|
|
36222
|
+
/** Zeroes the grid for one render pass (PNG export never bakes it in) and returns a restore function. */
|
|
36223
|
+
suppressGrid() {
|
|
36224
|
+
return suppressBoardSurfaceGrid(this.board);
|
|
36225
|
+
}
|
|
36132
36226
|
dispose() {
|
|
36133
36227
|
this.board.geometry.dispose();
|
|
36134
36228
|
this.board.material.dispose();
|
|
@@ -46577,9 +46671,10 @@ function emptyExtensionRegistry() {
|
|
|
46577
46671
|
* React never reaches past this class.
|
|
46578
46672
|
*/
|
|
46579
46673
|
var ScrawlEngine = class {
|
|
46580
|
-
constructor(canvas, documentId$1, extensionRegistry = emptyExtensionRegistry(), assetController = new AssetController(void 0)) {
|
|
46674
|
+
constructor(canvas, documentId$1, extensionRegistry = emptyExtensionRegistry(), assetController = new AssetController(void 0), boardTheme = DEFAULT_BOARD_SURFACE_THEME) {
|
|
46581
46675
|
this.canvas = canvas;
|
|
46582
46676
|
this.extensionRegistry = extensionRegistry;
|
|
46677
|
+
this.boardTheme = boardTheme;
|
|
46583
46678
|
this.onToolChange = null;
|
|
46584
46679
|
this.onZoomChange = null;
|
|
46585
46680
|
this.onAuditEvent = null;
|
|
@@ -46626,7 +46721,7 @@ var ScrawlEngine = class {
|
|
|
46626
46721
|
this.stampKind = "star";
|
|
46627
46722
|
this.timerDurationMs = TIMER_DEFAULT_DURATION_MS;
|
|
46628
46723
|
this.readOnly = false;
|
|
46629
|
-
this.scene = new SceneRig(canvas);
|
|
46724
|
+
this.scene = new SceneRig(canvas, boardTheme);
|
|
46630
46725
|
this.document = new BoardDocument(documentId(documentId$1));
|
|
46631
46726
|
this.history = new History(this.document);
|
|
46632
46727
|
this.history.onCommand = (command, kind) => {
|
|
@@ -46933,6 +47028,11 @@ var ScrawlEngine = class {
|
|
|
46933
47028
|
height: rig.height
|
|
46934
47029
|
};
|
|
46935
47030
|
}
|
|
47031
|
+
/** Live update of the board surface color/grid — the engine's identity stays fixed across theme changes. */
|
|
47032
|
+
applyBoardTheme(theme) {
|
|
47033
|
+
this.boardTheme = theme;
|
|
47034
|
+
this.scene.applyBoardTheme(theme);
|
|
47035
|
+
}
|
|
46936
47036
|
/**
|
|
46937
47037
|
* Snap to a peer's camera. No-op while drawing so pointer-to-board
|
|
46938
47038
|
* mapping does not warp the in-progress stroke.
|
|
@@ -46951,7 +47051,7 @@ var ScrawlEngine = class {
|
|
|
46951
47051
|
}
|
|
46952
47052
|
/** The saved document as standalone SVG — a pure walk of the wire format. */
|
|
46953
47053
|
exportSVG() {
|
|
46954
|
-
return documentToSVG(this.document.toJSON(), Date.now(), this.extensionRegistry);
|
|
47054
|
+
return documentToSVG(this.document.toJSON(), Date.now(), this.extensionRegistry, this.boardTheme.color);
|
|
46955
47055
|
}
|
|
46956
47056
|
/**
|
|
46957
47057
|
* Content-framed PNG snapshot. Renders once offscreen-style with UI props
|
|
@@ -46987,6 +47087,7 @@ var ScrawlEngine = class {
|
|
|
46987
47087
|
this.notesRenderer.setFocus(null);
|
|
46988
47088
|
this.textsRenderer.setFocus(null);
|
|
46989
47089
|
this.renderer.setGhostsVisible(false);
|
|
47090
|
+
const restoreGrid = this.scene.suppressGrid();
|
|
46990
47091
|
try {
|
|
46991
47092
|
rig.yaw = 0;
|
|
46992
47093
|
rig.pitch = 0;
|
|
@@ -46997,6 +47098,7 @@ var ScrawlEngine = class {
|
|
|
46997
47098
|
const dataUrl = this.scene.renderer.domElement.toDataURL("image/png");
|
|
46998
47099
|
return await (await fetch(dataUrl)).blob();
|
|
46999
47100
|
} finally {
|
|
47101
|
+
restoreGrid();
|
|
47000
47102
|
rig.center.x = saved.x;
|
|
47001
47103
|
rig.center.y = saved.y;
|
|
47002
47104
|
rig.height = saved.height;
|
|
@@ -48006,7 +48108,13 @@ function createBoardControllerWithEngine(options, existingEngine) {
|
|
|
48006
48108
|
}
|
|
48007
48109
|
const extensionRegistry = registryResult.registry;
|
|
48008
48110
|
const assetController = new AssetController(options.assetResolver, { cacheBytes: options.assetCacheBytes });
|
|
48009
|
-
|
|
48111
|
+
let currentBoardTheme = {
|
|
48112
|
+
color: options.boardTheme?.surface ?? DEFAULT_BOARD_SURFACE_THEME.color,
|
|
48113
|
+
gridMode: options.boardTheme?.gridMode ?? DEFAULT_BOARD_SURFACE_THEME.gridMode,
|
|
48114
|
+
gridColor: options.boardTheme?.gridColor ?? DEFAULT_BOARD_SURFACE_THEME.gridColor,
|
|
48115
|
+
gridSpacing: options.boardTheme?.gridSpacing ?? DEFAULT_BOARD_SURFACE_THEME.gridSpacing
|
|
48116
|
+
};
|
|
48117
|
+
const engine = existingEngine ?? (options.canvas ? new ScrawlEngine(options.canvas, options.document.id, extensionRegistry, assetController, currentBoardTheme) : null);
|
|
48010
48118
|
const document = engine?.document ?? new BoardDocument(documentId(options.document.id));
|
|
48011
48119
|
const history = engine?.history ?? new History(document);
|
|
48012
48120
|
const createId = options.createId ?? (() => crypto.randomUUID());
|
|
@@ -48490,6 +48598,15 @@ function createBoardControllerWithEngine(options, existingEngine) {
|
|
|
48490
48598
|
changed();
|
|
48491
48599
|
}
|
|
48492
48600
|
},
|
|
48601
|
+
boardTheme: { set(theme) {
|
|
48602
|
+
currentBoardTheme = {
|
|
48603
|
+
color: theme.surface ?? currentBoardTheme.color,
|
|
48604
|
+
gridMode: theme.gridMode ?? currentBoardTheme.gridMode,
|
|
48605
|
+
gridColor: theme.gridColor ?? currentBoardTheme.gridColor,
|
|
48606
|
+
gridSpacing: theme.gridSpacing ?? currentBoardTheme.gridSpacing
|
|
48607
|
+
};
|
|
48608
|
+
engine?.applyBoardTheme(currentBoardTheme);
|
|
48609
|
+
} },
|
|
48493
48610
|
view: {
|
|
48494
48611
|
fit() {
|
|
48495
48612
|
assertActive();
|
|
@@ -48794,10 +48911,10 @@ function createBoardControllerWithEngine(options, existingEngine) {
|
|
|
48794
48911
|
gather: (nextView) => engine?.applyGatherView(nextView) ?? false
|
|
48795
48912
|
},
|
|
48796
48913
|
export: {
|
|
48797
|
-
svg: () => engine ? engine.exportSVG() : documentToSVG(document.toJSON(), Date.now(), extensionRegistry),
|
|
48914
|
+
svg: () => engine ? engine.exportSVG() : documentToSVG(document.toJSON(), Date.now(), extensionRegistry, currentBoardTheme.color),
|
|
48798
48915
|
png: (options) => engine ? engine.exportPNG(options?.maxSide, { awaitAssets: options?.awaitAssets }) : Promise.resolve(null),
|
|
48799
48916
|
json: () => document.toJSON(),
|
|
48800
|
-
svgAsync: (svgOptions) => exportDocumentSVGWithAssets(document.toJSON(), Date.now(), extensionRegistry, assetController, svgOptions)
|
|
48917
|
+
svgAsync: (svgOptions) => exportDocumentSVGWithAssets(document.toJSON(), Date.now(), extensionRegistry, assetController, currentBoardTheme.color, svgOptions)
|
|
48801
48918
|
},
|
|
48802
48919
|
assets: { async ingest(bytes, mediaType, name, signal) {
|
|
48803
48920
|
assertActive();
|
|
@@ -49284,7 +49401,11 @@ var scrawlThemePresets = Object.freeze({
|
|
|
49284
49401
|
elevationHigh: "0 8px 20px rgba(28, 28, 26, 0.18)",
|
|
49285
49402
|
motionDuration: 140,
|
|
49286
49403
|
motionEasing: "cubic-bezier(0.2, 0, 0, 1)",
|
|
49287
|
-
density: "comfortable"
|
|
49404
|
+
density: "comfortable",
|
|
49405
|
+
boardSurface: "#f5f5f3",
|
|
49406
|
+
gridMode: "none",
|
|
49407
|
+
gridColor: "#deded8",
|
|
49408
|
+
gridSpacing: 5
|
|
49288
49409
|
}),
|
|
49289
49410
|
dark: Object.freeze({
|
|
49290
49411
|
surface: "#171816",
|
|
@@ -49309,7 +49430,11 @@ var scrawlThemePresets = Object.freeze({
|
|
|
49309
49430
|
elevationHigh: "0 8px 20px rgba(0, 0, 0, 0.38)",
|
|
49310
49431
|
motionDuration: 140,
|
|
49311
49432
|
motionEasing: "cubic-bezier(0.2, 0, 0, 1)",
|
|
49312
|
-
density: "comfortable"
|
|
49433
|
+
density: "comfortable",
|
|
49434
|
+
boardSurface: "#232420",
|
|
49435
|
+
gridMode: "none",
|
|
49436
|
+
gridColor: "#33352f",
|
|
49437
|
+
gridSpacing: 5
|
|
49313
49438
|
})
|
|
49314
49439
|
});
|
|
49315
49440
|
var variableNames = {
|
|
@@ -49335,7 +49460,11 @@ var variableNames = {
|
|
|
49335
49460
|
elevationHigh: "--scrawl-elevation-high",
|
|
49336
49461
|
motionDuration: "--scrawl-motion-duration",
|
|
49337
49462
|
motionEasing: "--scrawl-motion-easing",
|
|
49338
|
-
density: "--scrawl-density"
|
|
49463
|
+
density: "--scrawl-density",
|
|
49464
|
+
boardSurface: "--scrawl-board-surface",
|
|
49465
|
+
gridMode: "--scrawl-grid-mode",
|
|
49466
|
+
gridColor: "--scrawl-grid-color",
|
|
49467
|
+
gridSpacing: "--scrawl-grid-spacing"
|
|
49339
49468
|
};
|
|
49340
49469
|
var colorTokens = /* @__PURE__ */ new Set([
|
|
49341
49470
|
"surface",
|
|
@@ -49348,7 +49477,9 @@ var colorTokens = /* @__PURE__ */ new Set([
|
|
|
49348
49477
|
"selection",
|
|
49349
49478
|
"danger",
|
|
49350
49479
|
"warning",
|
|
49351
|
-
"success"
|
|
49480
|
+
"success",
|
|
49481
|
+
"boardSurface",
|
|
49482
|
+
"gridColor"
|
|
49352
49483
|
]);
|
|
49353
49484
|
var knownTokens = new Set(Object.keys(scrawlThemePresets.light));
|
|
49354
49485
|
function validateScrawlTheme(theme) {
|
|
@@ -49385,6 +49516,14 @@ function validateScrawlTheme(theme) {
|
|
|
49385
49516
|
token,
|
|
49386
49517
|
message: "Expected comfortable or compact"
|
|
49387
49518
|
});
|
|
49519
|
+
else if (token === "gridMode" && value !== "none" && value !== "line" && value !== "dot") diagnostics.push({
|
|
49520
|
+
token,
|
|
49521
|
+
message: "Expected none, line, or dot"
|
|
49522
|
+
});
|
|
49523
|
+
else if (token === "gridSpacing" && (!finiteNumber(value) || value < .5 || value > 50)) diagnostics.push({
|
|
49524
|
+
token,
|
|
49525
|
+
message: "Expected spacing 0.5-50 board units"
|
|
49526
|
+
});
|
|
49388
49527
|
else if ((token.endsWith("Family") || token.startsWith("elevation") || token === "motionEasing") && (typeof value !== "string" || !value.trim() || /[;{}]/.test(value))) diagnostics.push({
|
|
49389
49528
|
token,
|
|
49390
49529
|
message: "Expected a safe CSS value"
|
|
@@ -50540,6 +50679,14 @@ function ScrawlProvider({ controller, children, preset = "light", theme, portalC
|
|
|
50540
50679
|
style
|
|
50541
50680
|
]);
|
|
50542
50681
|
useEffect(() => reportThemeDiagnostics(resolved.diagnostics, onThemeDiagnostic), [resolved, onThemeDiagnostic]);
|
|
50682
|
+
useEffect(() => {
|
|
50683
|
+
controller.boardTheme.set({
|
|
50684
|
+
surface: resolved.values.boardSurface,
|
|
50685
|
+
gridMode: resolved.values.gridMode,
|
|
50686
|
+
gridColor: resolved.values.gridColor,
|
|
50687
|
+
gridSpacing: resolved.values.gridSpacing
|
|
50688
|
+
});
|
|
50689
|
+
}, [controller, resolved]);
|
|
50543
50690
|
useDeferredDisposal(controller, disposeOnUnmount);
|
|
50544
50691
|
const value = useMemo(() => ({
|
|
50545
50692
|
controller,
|
|
@@ -50587,8 +50734,16 @@ function Scrawl({ children, preset, theme, portalContainer, className, style, on
|
|
|
50587
50734
|
try {
|
|
50588
50735
|
if (!runtimeRef.current) {
|
|
50589
50736
|
const canvas = suppliedCanvas === void 0 ? ownedCanvasRef.current : suppliedCanvas;
|
|
50737
|
+
const resolved = resolveScrawlTheme(preset, theme);
|
|
50738
|
+
const boardTheme = {
|
|
50739
|
+
surface: resolved.values.boardSurface,
|
|
50740
|
+
gridMode: resolved.values.gridMode,
|
|
50741
|
+
gridColor: resolved.values.gridColor,
|
|
50742
|
+
gridSpacing: resolved.values.gridSpacing
|
|
50743
|
+
};
|
|
50590
50744
|
controller = createBoardController({
|
|
50591
50745
|
...options,
|
|
50746
|
+
boardTheme,
|
|
50592
50747
|
...canvas ? { canvas } : {}
|
|
50593
50748
|
});
|
|
50594
50749
|
runtimeRef.current = {
|
|
@@ -50824,4 +50979,4 @@ function ScrawlBoard({ documentId, initialDocument, onReady, className, style })
|
|
|
50824
50979
|
});
|
|
50825
50980
|
}
|
|
50826
50981
|
//#endregion
|
|
50827
|
-
export { AddImageCommand, AddNoteCommand, AddStrokesCommand, AddTableCommand, AddTextCommand, AddTimerCommand,
|
|
50982
|
+
export { ASSET_CACHE_BYTES_DEFAULT, ASSET_CACHE_BYTES_MAX, ASSET_CACHE_BYTES_MIN, ASSET_EXPORT_MAX_DECODED_MEGAPIXELS, ASSET_EXPORT_MAX_ENCODED_BYTES, ASSET_MAX_CONCURRENT_RESOLUTIONS, ASSET_MAX_DECODED_MEGAPIXELS, ASSET_MAX_DIMENSION_PX, ASSET_MAX_ENCODED_BYTES, ASSET_REF_MAX_BYTES, ASSET_REF_PATTERN, AddImageCommand, AddNoteCommand, AddStrokesCommand, AddTableCommand, AddTextCommand, AddTimerCommand, AssetResolutionError, BEACON_INSET, BoardDocument, CURRENT_DOCUMENT_SCHEMA_VERSION, ClusterStore, DefaultBoardChrome, DeleteImageCommand, DeleteNoteCommand, DeleteStrokesCommand, DeleteTableCommand, DeleteTextCommand, DeleteTimerCommand, DocumentRecoveryError, END_TAPER, ERASE_THRESHOLD, EraseCommand, FOG_COLOR, HIGHLIGHT_COLORS, History, IDENTITY, INK_COLORS, InlineEditors, LockItemsCommand, MIN_WIDTH_FACTOR, MultiplayerCursors, NOTE_COLORS, NOTE_DEFAULT_SIZE, NOTE_DEFAULT_Z, NOTE_MAX_Z, NOTE_MIN_Z, NOTE_PEEL_STEP, SDK_DEVELOPMENT_VERSION, SDK_PACKAGE_NAME, STAMPS, STAMP_SIZE, SUPPORTED_ASSET_MEDIA_TYPES, Scrawl, ScrawlBoard, ScrawlCanvas, ScrawlDefaultUI, ScrawlPortal, ScrawlProvider, SpatialIndex, StyleShelf, TABLE_DEFAULT_CELL_HEIGHT, TABLE_DEFAULT_CELL_WIDTH, TABLE_DEFAULT_FONT_SIZE, TEXT_DEFAULT_SIZE, TIMER_DEFAULT_DURATION_MS, TIMER_DEFAULT_SIZE, TIMER_PRESETS_MS, TransformCommand, UpdateImageCommand, UpdateNoteCommand, UpdateTableCommand, UpdateTextCommand, UpdateTimerCommand, apply, applyItemLock, assetRef, avgScale, canUnlockItem, changeToOps, clampAssetCacheBytes, cloneCustomObject, cloneImage, cloneNote, cloneStroke, cloneTable, cloneText, cloneTimer, createBoardController, createLocalBoard, documentId, documentToSVG, formatTimer, invert, isAssetRef, isIdentity, isStampKind, loadDocumentBytes, measureTable, measureTextBlock, migrateDocument, mul, pauseTimer, placePresenceBeacon, resolveScrawlTheme, ribbonEdges, rotationAbout, scalingAbout, scrawlThemePresets, searchBoard, serializeDocument, serializeLock, serializeStroke, setTimerDuration, stampDataUrl, startTimer, strokeId, timerExpired, timerRemaining, toggleTimer, translation, useScrawlController, useScrawlSnapshot, useScrawlTheme, validateScrawlTheme };
|
package/dist/react.d.ts
CHANGED
|
@@ -8,6 +8,9 @@ type StrokeId = string & {
|
|
|
8
8
|
|
|
9
9
|
/** Wire grammar: `asset:<namespace>:<opaque-id>`. Interpreted only by the Host. */
|
|
10
10
|
type AssetRef = string;
|
|
11
|
+
declare function isAssetRef(value: unknown): value is AssetRef;
|
|
12
|
+
/** Throws on malformed input; use `isAssetRef` where a boolean is wanted instead. */
|
|
13
|
+
declare function assetRef(value: string): AssetRef;
|
|
11
14
|
type AssetKind = "image";
|
|
12
15
|
type AssetPurpose = "render" | "thumbnail" | "export";
|
|
13
16
|
interface AssetResolveRequest {
|
|
@@ -49,6 +52,12 @@ interface AssetIngestor {
|
|
|
49
52
|
ingest(request: AssetIngestRequest): Promise<AssetIngestResult>;
|
|
50
53
|
}
|
|
51
54
|
type AssetResolutionErrorCode = "resolver-unavailable" | "not-found" | "forbidden" | "offline" | "unsupported-type" | "too-large" | "invalid-content" | "decode-failed" | "budget-exceeded" | "aborted" | "unknown";
|
|
55
|
+
declare class AssetResolutionError extends Error {
|
|
56
|
+
readonly code: AssetResolutionErrorCode;
|
|
57
|
+
readonly retryable: boolean;
|
|
58
|
+
readonly ref?: AssetRef | undefined;
|
|
59
|
+
constructor(code: AssetResolutionErrorCode, retryable: boolean, message: string, ref?: AssetRef | undefined);
|
|
60
|
+
}
|
|
52
61
|
/** Runtime event for a resolution/ingestion failure — never carries credentials or a fetchable location. */
|
|
53
62
|
interface AssetDiagnostic {
|
|
54
63
|
code: AssetResolutionErrorCode;
|
|
@@ -57,6 +66,9 @@ interface AssetDiagnostic {
|
|
|
57
66
|
objectKind: "image" | "custom";
|
|
58
67
|
retryable: boolean;
|
|
59
68
|
}
|
|
69
|
+
declare const SUPPORTED_ASSET_MEDIA_TYPES: readonly ["image/png", "image/jpeg", "image/webp"];
|
|
70
|
+
type SupportedAssetMediaType = (typeof SUPPORTED_ASSET_MEDIA_TYPES)[number];
|
|
71
|
+
declare function clampAssetCacheBytes(value: number | undefined): number;
|
|
60
72
|
|
|
61
73
|
type Mat2x3 = [number, number, number, number, number, number];
|
|
62
74
|
|
|
@@ -104,6 +116,7 @@ interface CustomBoardObject {
|
|
|
104
116
|
};
|
|
105
117
|
props: JsonValue;
|
|
106
118
|
}
|
|
119
|
+
declare function cloneCustomObject(object: CustomBoardObject): CustomBoardObject;
|
|
107
120
|
/**
|
|
108
121
|
* The read-only view handed to `describe`. Deep-readonly by construction
|
|
109
122
|
* (not derived via a shallow `Readonly<>`) because `describe` must treat its
|
|
@@ -863,22 +876,31 @@ interface CreateBoardControllerOptions {
|
|
|
863
876
|
* Trusted Custom tool/object registrations (ticket #22, design:
|
|
864
877
|
* docs/research/extension-contracts.md). Validated atomically at
|
|
865
878
|
* construction; registration failure throws before any controller is
|
|
866
|
-
* returned.
|
|
867
|
-
* internal-only until the reference Extension proves the seam.
|
|
879
|
+
* returned.
|
|
868
880
|
*/
|
|
869
881
|
extensions?: readonly ScrawlExtension[];
|
|
870
882
|
/**
|
|
871
883
|
* Optional Host-managed Asset capabilities (ticket #23, design:
|
|
872
884
|
* docs/research/asset-resolution-resource-policy.md). Without a
|
|
873
885
|
* 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.
|
|
886
|
+
* render an accessible placeholder.
|
|
877
887
|
*/
|
|
878
888
|
assetResolver?: AssetResolver;
|
|
879
889
|
assetIngestor?: AssetIngestor;
|
|
880
890
|
/** Clamped to 64–512MiB; defaults to 256MiB. */
|
|
881
891
|
assetCacheBytes?: number;
|
|
892
|
+
/**
|
|
893
|
+
* The rendered board surface's color and reference grid. Defaults to the
|
|
894
|
+
* light theme preset's values; `<Scrawl>` keeps this current across theme
|
|
895
|
+
* changes via `boardTheme.set` below — a headless/browser-tier Host that
|
|
896
|
+
* doesn't use the React theme system can set this directly instead.
|
|
897
|
+
*/
|
|
898
|
+
boardTheme?: {
|
|
899
|
+
surface?: string;
|
|
900
|
+
gridMode?: "none" | "line" | "dot";
|
|
901
|
+
gridColor?: string;
|
|
902
|
+
gridSpacing?: number;
|
|
903
|
+
};
|
|
882
904
|
}
|
|
883
905
|
interface BoardController {
|
|
884
906
|
readonly document: ReadonlyBoardDocument;
|
|
@@ -904,6 +926,15 @@ interface BoardController {
|
|
|
904
926
|
undo(): void;
|
|
905
927
|
redo(): void;
|
|
906
928
|
};
|
|
929
|
+
readonly boardTheme: {
|
|
930
|
+
/** Live update of the board surface color/grid — the controller's identity stays fixed across theme changes. */
|
|
931
|
+
set(theme: {
|
|
932
|
+
surface?: string;
|
|
933
|
+
gridMode?: "none" | "line" | "dot";
|
|
934
|
+
gridColor?: string;
|
|
935
|
+
gridSpacing?: number;
|
|
936
|
+
}): void;
|
|
937
|
+
};
|
|
907
938
|
readonly view: {
|
|
908
939
|
fit(): void;
|
|
909
940
|
zoomTo(value: number): void;
|
|
@@ -1000,6 +1031,7 @@ type LocalBoard = {
|
|
|
1000
1031
|
|
|
1001
1032
|
type ScrawlThemePreset = "light" | "dark";
|
|
1002
1033
|
type ScrawlDensity = "comfortable" | "compact";
|
|
1034
|
+
type ScrawlGridMode = "none" | "line" | "dot";
|
|
1003
1035
|
interface ScrawlTheme {
|
|
1004
1036
|
surface?: string;
|
|
1005
1037
|
surfaceRaised?: string;
|
|
@@ -1024,6 +1056,13 @@ interface ScrawlTheme {
|
|
|
1024
1056
|
motionDuration?: number;
|
|
1025
1057
|
motionEasing?: string;
|
|
1026
1058
|
density?: ScrawlDensity;
|
|
1059
|
+
/** The rendered board/canvas surface color — distinct from `surface` (UI chrome panels). */
|
|
1060
|
+
boardSurface?: string;
|
|
1061
|
+
/** `"none"` (default) keeps the board a plain surface; `"line"`/`"dot"` draw a zoom-adaptive reference grid. */
|
|
1062
|
+
gridMode?: ScrawlGridMode;
|
|
1063
|
+
gridColor?: string;
|
|
1064
|
+
/** Grid spacing in board units at 100% zoom. Ignored when `gridMode` is `"none"`. */
|
|
1065
|
+
gridSpacing?: number;
|
|
1027
1066
|
}
|
|
1028
1067
|
type ResolvedScrawlTheme = Required<ScrawlTheme>;
|
|
1029
1068
|
interface ScrawlThemeDiagnostic {
|
|
@@ -1167,5 +1206,5 @@ type ScrawlBoardProps = {
|
|
|
1167
1206
|
};
|
|
1168
1207
|
declare function ScrawlBoard({ documentId, initialDocument, onReady, className, style }: ScrawlBoardProps): react.JSX.Element;
|
|
1169
1208
|
|
|
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 };
|
|
1209
|
+
export { AssetResolutionError, DefaultBoardChrome, InlineEditors, MultiplayerCursors, SUPPORTED_ASSET_MEDIA_TYPES, Scrawl, ScrawlBoard, ScrawlCanvas, ScrawlDefaultUI, ScrawlPortal, ScrawlProvider, StyleShelf, assetRef, clampAssetCacheBytes, cloneCustomObject, isAssetRef, resolveScrawlTheme, scrawlThemePresets, useScrawlController, useScrawlSnapshot, useScrawlTheme, validateScrawlTheme };
|
|
1210
|
+
export type { AssetDiagnostic, AssetIngestRequest, AssetIngestResult, AssetIngestor, AssetKind, AssetPurpose, AssetRef, AssetResolutionErrorCode, AssetResolveRequest, AssetResolveResult, AssetResolver, BoardController, BoardKeyInput, BoardPointerInput, BoardScene, BoardSlotProps, BoardSnapshot, BoardStyle, CommentMarker, CreateBoardControllerOptions, CustomBoardObject, CustomObjectAddInput, CustomObjectDefinition, CustomTool, CustomToolDefinition, DefaultBoardChromeProps, DefaultUIRegion, DefaultUISlot, DefaultUISlots, DialogSlotProps, ExtensionCommand, ExtensionDiagnostic, ExtensionHitResult, ExtensionId, ExtensionRequirement, InlineEditorsProps, InputModifiers, JsonObject, JsonValue, LocalBoard, LocalBoardSnapshot, 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, ScrawlTheme, ScrawlThemeDiagnostic, ScrawlThemePreset, StyleShelfProps, SupportedAssetMediaType, ToolCancelReason, ToolCapabilities, ToolCursor, ToolId };
|