@scrawl-board/board 0.1.0-beta.2 → 0.1.0-beta.4
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 +7 -2
- package/dist/browser.js +36 -29
- package/dist/core.d.ts +16 -3
- package/dist/core.js +12 -4
- package/dist/index.d.ts +47 -6
- package/dist/index.js +49 -34
- package/dist/react.d.ts +38 -5
- package/dist/react.js +49 -34
- package/dist/styles.css +2 -1
- package/package.json +1 -1
package/dist/browser.d.ts
CHANGED
|
@@ -368,8 +368,13 @@ interface StrokePoint extends BoardPoint {
|
|
|
368
368
|
*/
|
|
369
369
|
erase?: number;
|
|
370
370
|
}
|
|
371
|
-
/**
|
|
372
|
-
|
|
371
|
+
/**
|
|
372
|
+
* Which drawing tool made a stroke; undefined means marker (back-compat).
|
|
373
|
+
* `"shape"` (rect/ellipse/line/arrow/polygon/star/heart) renders at its
|
|
374
|
+
* exact configured width with no pressure variance or end taper — a
|
|
375
|
+
* geometric outline, not an expressive ink mark.
|
|
376
|
+
*/
|
|
377
|
+
type StrokeTool = "marker" | "highlighter" | "shape";
|
|
373
378
|
interface Stroke extends Lockable {
|
|
374
379
|
id: string;
|
|
375
380
|
color: string;
|
package/dist/browser.js
CHANGED
|
@@ -1739,7 +1739,15 @@ function changeToOps(change, restoring = false) {
|
|
|
1739
1739
|
//#region src/core/shapes/ribbonEdges.ts
|
|
1740
1740
|
var MIN_WIDTH_FACTOR = .35;
|
|
1741
1741
|
var END_TAPER = .55;
|
|
1742
|
-
|
|
1742
|
+
/**
|
|
1743
|
+
* @param handDrawn Default `true`: ink's organic feel — width follows
|
|
1744
|
+
* per-point pressure, and both ends taper. Pass `false` for a geometric
|
|
1745
|
+
* shape outline, which has no real pressure signal and isn't a pen stroke
|
|
1746
|
+
* with a lift-off: it renders at the exact `baseWidth`, uniformly, with no
|
|
1747
|
+
* taper at its start/end (which, for a closed shape, is the same point —
|
|
1748
|
+
* tapering it would visibly pinch just that one corner).
|
|
1749
|
+
*/
|
|
1750
|
+
function ribbonEdges(points, baseWidth, handDrawn = true) {
|
|
1743
1751
|
const pts = points.length === 1 ? [points[0], {
|
|
1744
1752
|
...points[0],
|
|
1745
1753
|
x: points[0].x + baseWidth * .05
|
|
@@ -1754,8 +1762,8 @@ function ribbonEdges(points, baseWidth) {
|
|
|
1754
1762
|
const len = Math.hypot(dx, dy) || 1;
|
|
1755
1763
|
dx /= len;
|
|
1756
1764
|
dy /= len;
|
|
1757
|
-
let width = baseWidth * (MIN_WIDTH_FACTOR + (1 - MIN_WIDTH_FACTOR) * pts[i].pressure);
|
|
1758
|
-
if (i === 0 || i === n - 1) width *= END_TAPER;
|
|
1765
|
+
let width = handDrawn ? baseWidth * (MIN_WIDTH_FACTOR + (1 - MIN_WIDTH_FACTOR) * pts[i].pressure) : baseWidth;
|
|
1766
|
+
if (handDrawn && (i === 0 || i === n - 1)) width *= END_TAPER;
|
|
1759
1767
|
const hw = width / 2;
|
|
1760
1768
|
out[i] = {
|
|
1761
1769
|
lx: pts[i].x - dy * hw,
|
|
@@ -2031,7 +2039,7 @@ function strokeToPaths(stroke) {
|
|
|
2031
2039
|
y,
|
|
2032
2040
|
pressure,
|
|
2033
2041
|
...erase > 0 ? { erase } : {}
|
|
2034
|
-
})), stroke.baseWidth);
|
|
2042
|
+
})), stroke.baseWidth, stroke.tool !== "shape");
|
|
2035
2043
|
const transform = stroke.matrix ? ` transform="matrix(${fmt(stroke.matrix[0])} ${fmt(-stroke.matrix[1])} ${fmt(-stroke.matrix[2])} ${fmt(stroke.matrix[3])} ${fmt(stroke.matrix[4])} ${fmt(-stroke.matrix[5])})"` : "";
|
|
2036
2044
|
const baseOpacity = stroke.tool === "highlighter" ? .4 : 1;
|
|
2037
2045
|
const paths = [];
|
|
@@ -35876,12 +35884,16 @@ var FRAGMENT = `
|
|
|
35876
35884
|
return max(lineAlpha2.x, lineAlpha2.y);
|
|
35877
35885
|
}
|
|
35878
35886
|
|
|
35879
|
-
// Distance from p's nearest grid intersection, in world units.
|
|
35880
|
-
//
|
|
35887
|
+
// Distance from p's nearest grid intersection, in world units. A line's
|
|
35888
|
+
// width reads as visible even at 1px because it spans the whole screen;
|
|
35889
|
+
// an isolated dot at that same 1px radius gets almost entirely eaten by
|
|
35890
|
+
// anti-aliasing and all but disappears. Scaling the radius up keeps a
|
|
35891
|
+
// dot's on-screen weight comparable to a line drawn with the same
|
|
35892
|
+
// uGridLineWidthPx, rather than using it literally as a radius.
|
|
35881
35893
|
float gridDotAlpha(vec2 p) {
|
|
35882
35894
|
vec2 cell = mod(p, uGridSpacing) - uGridSpacing * 0.5;
|
|
35883
35895
|
float dist = length(cell);
|
|
35884
|
-
float radius = uGridLineWidthPx * uWorldPerPixel;
|
|
35896
|
+
float radius = uGridLineWidthPx * uWorldPerPixel * 1.75;
|
|
35885
35897
|
float aa = uWorldPerPixel;
|
|
35886
35898
|
return 1.0 - smoothstep(radius - aa * 0.5, radius + aa * 0.5, dist);
|
|
35887
35899
|
}
|
|
@@ -36308,8 +36320,8 @@ var INK_Z = .02;
|
|
|
36308
36320
|
var HIGHLIGHT_Z = .016;
|
|
36309
36321
|
/** Ghosts sit under everything so live strokes always read on top. */
|
|
36310
36322
|
var GHOST_Z = .012;
|
|
36311
|
-
function buildRibbonGeometry(points, baseWidth, z = INK_Z) {
|
|
36312
|
-
const edges = ribbonEdges(points, baseWidth);
|
|
36323
|
+
function buildRibbonGeometry(points, baseWidth, z = INK_Z, handDrawn = true) {
|
|
36324
|
+
const edges = ribbonEdges(points, baseWidth, handDrawn);
|
|
36313
36325
|
const n = edges.length;
|
|
36314
36326
|
const positions = new Float32Array(n * 2 * 3);
|
|
36315
36327
|
const uvs = new Float32Array(n * 2 * 2);
|
|
@@ -36354,6 +36366,8 @@ function buildRibbonGeometry(points, baseWidth, z = INK_Z) {
|
|
|
36354
36366
|
//#endregion
|
|
36355
36367
|
//#region src/renderer/shapes/strokeRenderer.ts
|
|
36356
36368
|
var strokeZ = (stroke) => stroke.tool === "highlighter" ? HIGHLIGHT_Z : INK_Z;
|
|
36369
|
+
/** Shapes (rect/ellipse/line/arrow/...) are geometric outlines, not pressure-sensitive ink. */
|
|
36370
|
+
var handDrawn = (stroke) => stroke.tool !== "shape";
|
|
36357
36371
|
/**
|
|
36358
36372
|
* Derives ink meshes from the document by subscription — the document never
|
|
36359
36373
|
* knows the renderer exists. Also hosts session-only ghosts (decision Q7)
|
|
@@ -36378,12 +36392,12 @@ var StrokeRenderer = class {
|
|
|
36378
36392
|
this.ghostGroup.visible = visible;
|
|
36379
36393
|
}
|
|
36380
36394
|
/** Leave a smeared residue of an erased span (never persisted). */
|
|
36381
|
-
addGhost(points, color, baseWidth) {
|
|
36395
|
+
addGhost(points, color, baseWidth, handDrawn = true) {
|
|
36382
36396
|
if (points.length < 2) return;
|
|
36383
36397
|
const mesh = new Mesh(buildRibbonGeometry(points.map((p) => ({
|
|
36384
36398
|
...p,
|
|
36385
36399
|
erase: 0
|
|
36386
|
-
})), baseWidth * 1.7, GHOST_Z), this.materials.ghost(color));
|
|
36400
|
+
})), baseWidth * 1.7, GHOST_Z, handDrawn), this.materials.ghost(color));
|
|
36387
36401
|
this.ghostGroup.add(mesh);
|
|
36388
36402
|
}
|
|
36389
36403
|
/**
|
|
@@ -36395,7 +36409,7 @@ var StrokeRenderer = class {
|
|
|
36395
36409
|
if (this.meshes.has(stroke.id) || this.retiredLive.has(stroke.id) || stroke.points.length < 2) return;
|
|
36396
36410
|
this.liveUser.set(stroke.id, userId);
|
|
36397
36411
|
const existing = this.liveMeshes.get(stroke.id);
|
|
36398
|
-
const geometry = buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke));
|
|
36412
|
+
const geometry = buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke), handDrawn(stroke));
|
|
36399
36413
|
if (existing) {
|
|
36400
36414
|
existing.geometry.dispose();
|
|
36401
36415
|
existing.geometry = geometry;
|
|
@@ -36455,7 +36469,7 @@ var StrokeRenderer = class {
|
|
|
36455
36469
|
const mesh = this.meshes.get(stroke.id);
|
|
36456
36470
|
if (mesh) {
|
|
36457
36471
|
mesh.geometry.dispose();
|
|
36458
|
-
mesh.geometry = buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke));
|
|
36472
|
+
mesh.geometry = buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke), handDrawn(stroke));
|
|
36459
36473
|
}
|
|
36460
36474
|
}
|
|
36461
36475
|
for (const stroke of change.transformed) {
|
|
@@ -36464,7 +36478,7 @@ var StrokeRenderer = class {
|
|
|
36464
36478
|
}
|
|
36465
36479
|
}
|
|
36466
36480
|
create(stroke) {
|
|
36467
|
-
const mesh = new Mesh(buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke)), stroke.tool === "highlighter" ? this.materials.highlight(stroke.color) : this.materials.get(stroke.color));
|
|
36481
|
+
const mesh = new Mesh(buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke), handDrawn(stroke)), stroke.tool === "highlighter" ? this.materials.highlight(stroke.color) : this.materials.get(stroke.color));
|
|
36468
36482
|
mesh.matrixAutoUpdate = false;
|
|
36469
36483
|
syncMatrix(mesh, stroke);
|
|
36470
36484
|
this.meshes.set(stroke.id, mesh);
|
|
@@ -44603,9 +44617,10 @@ function hitStroke(doc, index, p, slop) {
|
|
|
44603
44617
|
const scale = m ? avgScale(m) : 1;
|
|
44604
44618
|
let prev = null;
|
|
44605
44619
|
let prevWidth = 0;
|
|
44620
|
+
const handDrawn = stroke.tool !== "shape";
|
|
44606
44621
|
for (const point of stroke.points) {
|
|
44607
44622
|
const world = m ? apply(m, point) : point;
|
|
44608
|
-
const width = stroke.baseWidth * (MIN_WIDTH_FACTOR + (1 - MIN_WIDTH_FACTOR) * point.pressure) * scale;
|
|
44623
|
+
const width = (handDrawn ? stroke.baseWidth * (MIN_WIDTH_FACTOR + (1 - MIN_WIDTH_FACTOR) * point.pressure) : stroke.baseWidth) * scale;
|
|
44609
44624
|
if (prev) {
|
|
44610
44625
|
const reach = Math.max(width, prevWidth) / 2 + slop;
|
|
44611
44626
|
if (distToSegmentSq(p, prev, world) <= reach * reach) {
|
|
@@ -44739,7 +44754,7 @@ var EraserTool = class {
|
|
|
44739
44754
|
...p,
|
|
44740
44755
|
...apply(m, p)
|
|
44741
44756
|
})) : run.points;
|
|
44742
|
-
this.ctx.renderer.addGhost(world, current.color, current.baseWidth * (m ? avgScale(m) : 1));
|
|
44757
|
+
this.ctx.renderer.addGhost(world, current.color, current.baseWidth * (m ? avgScale(m) : 1), current.tool !== "shape");
|
|
44743
44758
|
}
|
|
44744
44759
|
}
|
|
44745
44760
|
doc.removeStrokes(before.map((s) => s.id));
|
|
@@ -44972,10 +44987,7 @@ function rect(a, b, step) {
|
|
|
44972
44987
|
a
|
|
44973
44988
|
];
|
|
44974
44989
|
const out = [];
|
|
44975
|
-
for (let i = 0; i < corners.length - 1; i++)
|
|
44976
|
-
const edge = sampleSegment(corners[i], corners[i + 1], step);
|
|
44977
|
-
out.push(...i === 0 ? edge : edge.slice(1));
|
|
44978
|
-
}
|
|
44990
|
+
for (let i = 0; i < corners.length - 1; i++) out.push(...sampleSegment(corners[i], corners[i + 1], step));
|
|
44979
44991
|
return out;
|
|
44980
44992
|
}
|
|
44981
44993
|
function ellipse(a, b, step) {
|
|
@@ -45046,10 +45058,7 @@ function polygon(sides, a, b, step, startAngle = -Math.PI / 2) {
|
|
|
45046
45058
|
}
|
|
45047
45059
|
vertices.push(vertices[0]);
|
|
45048
45060
|
const out = [];
|
|
45049
|
-
for (let i = 0; i < vertices.length - 1; i++)
|
|
45050
|
-
const edge = sampleSegment(vertices[i], vertices[i + 1], step);
|
|
45051
|
-
out.push(...i === 0 ? edge : edge.slice(1));
|
|
45052
|
-
}
|
|
45061
|
+
for (let i = 0; i < vertices.length - 1; i++) out.push(...sampleSegment(vertices[i], vertices[i + 1], step));
|
|
45053
45062
|
return out;
|
|
45054
45063
|
}
|
|
45055
45064
|
function star(points, a, b, step) {
|
|
@@ -45073,10 +45082,7 @@ function star(points, a, b, step) {
|
|
|
45073
45082
|
}
|
|
45074
45083
|
vertices.push(vertices[0]);
|
|
45075
45084
|
const out = [];
|
|
45076
|
-
for (let i = 0; i < vertices.length - 1; i++)
|
|
45077
|
-
const edge = sampleSegment(vertices[i], vertices[i + 1], step);
|
|
45078
|
-
out.push(...i === 0 ? edge : edge.slice(1));
|
|
45079
|
-
}
|
|
45085
|
+
for (let i = 0; i < vertices.length - 1; i++) out.push(...sampleSegment(vertices[i], vertices[i + 1], step));
|
|
45080
45086
|
return out;
|
|
45081
45087
|
}
|
|
45082
45088
|
function heart(a, b, step) {
|
|
@@ -45153,6 +45159,7 @@ var ShapeTool = class {
|
|
|
45153
45159
|
id: crypto.randomUUID(),
|
|
45154
45160
|
color: this.ctx.inkColor(),
|
|
45155
45161
|
baseWidth: this.ctx.shapeStrokeWidth(),
|
|
45162
|
+
tool: "shape",
|
|
45156
45163
|
points
|
|
45157
45164
|
}));
|
|
45158
45165
|
this.ctx.clusters.assign(strokes[0]);
|
|
@@ -45186,7 +45193,7 @@ var ShapeTool = class {
|
|
|
45186
45193
|
this.previews[i].visible = polyline !== void 0;
|
|
45187
45194
|
if (polyline) {
|
|
45188
45195
|
this.previews[i].geometry.dispose();
|
|
45189
|
-
this.previews[i].geometry = buildRibbonGeometry(polyline, this.ctx.shapeStrokeWidth());
|
|
45196
|
+
this.previews[i].geometry = buildRibbonGeometry(polyline, this.ctx.shapeStrokeWidth(), void 0, false);
|
|
45190
45197
|
}
|
|
45191
45198
|
}
|
|
45192
45199
|
}
|
package/dist/core.d.ts
CHANGED
|
@@ -425,8 +425,13 @@ interface StrokePoint extends BoardPoint {
|
|
|
425
425
|
*/
|
|
426
426
|
erase?: number;
|
|
427
427
|
}
|
|
428
|
-
/**
|
|
429
|
-
|
|
428
|
+
/**
|
|
429
|
+
* Which drawing tool made a stroke; undefined means marker (back-compat).
|
|
430
|
+
* `"shape"` (rect/ellipse/line/arrow/polygon/star/heart) renders at its
|
|
431
|
+
* exact configured width with no pressure variance or end taper — a
|
|
432
|
+
* geometric outline, not an expressive ink mark.
|
|
433
|
+
*/
|
|
434
|
+
type StrokeTool = "marker" | "highlighter" | "shape";
|
|
430
435
|
interface Stroke extends Lockable {
|
|
431
436
|
id: string;
|
|
432
437
|
color: string;
|
|
@@ -1061,7 +1066,15 @@ interface RibbonEdgePoint {
|
|
|
1061
1066
|
/** 1 = intact ink, 0 = fully erased (from the erasure channel). */
|
|
1062
1067
|
alpha: number;
|
|
1063
1068
|
}
|
|
1064
|
-
|
|
1069
|
+
/**
|
|
1070
|
+
* @param handDrawn Default `true`: ink's organic feel — width follows
|
|
1071
|
+
* per-point pressure, and both ends taper. Pass `false` for a geometric
|
|
1072
|
+
* shape outline, which has no real pressure signal and isn't a pen stroke
|
|
1073
|
+
* with a lift-off: it renders at the exact `baseWidth`, uniformly, with no
|
|
1074
|
+
* taper at its start/end (which, for a closed shape, is the same point —
|
|
1075
|
+
* tapering it would visibly pinch just that one corner).
|
|
1076
|
+
*/
|
|
1077
|
+
declare function ribbonEdges(points: StrokePoint[], baseWidth: number, handDrawn?: boolean): RibbonEdgePoint[];
|
|
1065
1078
|
|
|
1066
1079
|
declare class SpatialIndex {
|
|
1067
1080
|
private readonly doc;
|
package/dist/core.js
CHANGED
|
@@ -1732,7 +1732,15 @@ function changeToOps(change, restoring = false) {
|
|
|
1732
1732
|
//#region src/core/shapes/ribbonEdges.ts
|
|
1733
1733
|
var MIN_WIDTH_FACTOR = .35;
|
|
1734
1734
|
var END_TAPER = .55;
|
|
1735
|
-
|
|
1735
|
+
/**
|
|
1736
|
+
* @param handDrawn Default `true`: ink's organic feel — width follows
|
|
1737
|
+
* per-point pressure, and both ends taper. Pass `false` for a geometric
|
|
1738
|
+
* shape outline, which has no real pressure signal and isn't a pen stroke
|
|
1739
|
+
* with a lift-off: it renders at the exact `baseWidth`, uniformly, with no
|
|
1740
|
+
* taper at its start/end (which, for a closed shape, is the same point —
|
|
1741
|
+
* tapering it would visibly pinch just that one corner).
|
|
1742
|
+
*/
|
|
1743
|
+
function ribbonEdges(points, baseWidth, handDrawn = true) {
|
|
1736
1744
|
const pts = points.length === 1 ? [points[0], {
|
|
1737
1745
|
...points[0],
|
|
1738
1746
|
x: points[0].x + baseWidth * .05
|
|
@@ -1747,8 +1755,8 @@ function ribbonEdges(points, baseWidth) {
|
|
|
1747
1755
|
const len = Math.hypot(dx, dy) || 1;
|
|
1748
1756
|
dx /= len;
|
|
1749
1757
|
dy /= len;
|
|
1750
|
-
let width = baseWidth * (MIN_WIDTH_FACTOR + (1 - MIN_WIDTH_FACTOR) * pts[i].pressure);
|
|
1751
|
-
if (i === 0 || i === n - 1) width *= END_TAPER;
|
|
1758
|
+
let width = handDrawn ? baseWidth * (MIN_WIDTH_FACTOR + (1 - MIN_WIDTH_FACTOR) * pts[i].pressure) : baseWidth;
|
|
1759
|
+
if (handDrawn && (i === 0 || i === n - 1)) width *= END_TAPER;
|
|
1752
1760
|
const hw = width / 2;
|
|
1753
1761
|
out[i] = {
|
|
1754
1762
|
lx: pts[i].x - dy * hw,
|
|
@@ -2061,7 +2069,7 @@ function strokeToPaths(stroke) {
|
|
|
2061
2069
|
y,
|
|
2062
2070
|
pressure,
|
|
2063
2071
|
...erase > 0 ? { erase } : {}
|
|
2064
|
-
})), stroke.baseWidth);
|
|
2072
|
+
})), stroke.baseWidth, stroke.tool !== "shape");
|
|
2065
2073
|
const transform = stroke.matrix ? ` transform="matrix(${fmt(stroke.matrix[0])} ${fmt(-stroke.matrix[1])} ${fmt(-stroke.matrix[2])} ${fmt(stroke.matrix[3])} ${fmt(stroke.matrix[4])} ${fmt(-stroke.matrix[5])})"` : "";
|
|
2066
2074
|
const baseOpacity = stroke.tool === "highlighter" ? .4 : 1;
|
|
2067
2075
|
const paths = [];
|
package/dist/index.d.ts
CHANGED
|
@@ -428,8 +428,13 @@ interface StrokePoint extends BoardPoint {
|
|
|
428
428
|
*/
|
|
429
429
|
erase?: number;
|
|
430
430
|
}
|
|
431
|
-
/**
|
|
432
|
-
|
|
431
|
+
/**
|
|
432
|
+
* Which drawing tool made a stroke; undefined means marker (back-compat).
|
|
433
|
+
* `"shape"` (rect/ellipse/line/arrow/polygon/star/heart) renders at its
|
|
434
|
+
* exact configured width with no pressure variance or end taper — a
|
|
435
|
+
* geometric outline, not an expressive ink mark.
|
|
436
|
+
*/
|
|
437
|
+
type StrokeTool = "marker" | "highlighter" | "shape";
|
|
433
438
|
interface Stroke extends Lockable {
|
|
434
439
|
id: string;
|
|
435
440
|
color: string;
|
|
@@ -1064,7 +1069,15 @@ interface RibbonEdgePoint {
|
|
|
1064
1069
|
/** 1 = intact ink, 0 = fully erased (from the erasure channel). */
|
|
1065
1070
|
alpha: number;
|
|
1066
1071
|
}
|
|
1067
|
-
|
|
1072
|
+
/**
|
|
1073
|
+
* @param handDrawn Default `true`: ink's organic feel — width follows
|
|
1074
|
+
* per-point pressure, and both ends taper. Pass `false` for a geometric
|
|
1075
|
+
* shape outline, which has no real pressure signal and isn't a pen stroke
|
|
1076
|
+
* with a lift-off: it renders at the exact `baseWidth`, uniformly, with no
|
|
1077
|
+
* taper at its start/end (which, for a closed shape, is the same point —
|
|
1078
|
+
* tapering it would visibly pinch just that one corner).
|
|
1079
|
+
*/
|
|
1080
|
+
declare function ribbonEdges(points: StrokePoint[], baseWidth: number, handDrawn?: boolean): RibbonEdgePoint[];
|
|
1068
1081
|
|
|
1069
1082
|
declare class SpatialIndex {
|
|
1070
1083
|
private readonly doc;
|
|
@@ -1690,6 +1703,13 @@ interface ScrawlTheme {
|
|
|
1690
1703
|
textMuted?: string;
|
|
1691
1704
|
/** Borders and dividers between UI chrome elements. */
|
|
1692
1705
|
edge?: string;
|
|
1706
|
+
/**
|
|
1707
|
+
* Brand accent color. Drives the active/selected state of toolbar
|
|
1708
|
+
* controls (e.g. the active tool button): its icon/text render in this
|
|
1709
|
+
* color, and its background is automatically derived as a light tint of
|
|
1710
|
+
* it (via `color-mix`) — set this one token and both follow.
|
|
1711
|
+
*/
|
|
1712
|
+
primary?: string;
|
|
1693
1713
|
/** Focus ring color. Checked for contrast against `surface`. */
|
|
1694
1714
|
focus?: string;
|
|
1695
1715
|
/** Selection highlight color (e.g. selected list items, not board object selection). */
|
|
@@ -1762,6 +1782,19 @@ interface DefaultBoardChromeProps {
|
|
|
1762
1782
|
style?: React.CSSProperties;
|
|
1763
1783
|
regions?: Partial<Record<DefaultUIRegion, boolean>>;
|
|
1764
1784
|
slots?: DefaultUISlots;
|
|
1785
|
+
/**
|
|
1786
|
+
* Swap in your own icon for a tool's primary toolbar button — pass any
|
|
1787
|
+
* icon from any library (`<Pencil />`, `<Icon icon="..." />`, an inline
|
|
1788
|
+
* `<svg>`, whatever). A tool with no entry here keeps its default text
|
|
1789
|
+
* label, so this is opt-in per tool, not all-or-nothing. Aria labeling
|
|
1790
|
+
* and the keyboard-shortcut tooltip stay text-based regardless, so the
|
|
1791
|
+
* board remains fully accessible even with icon-only buttons.
|
|
1792
|
+
*
|
|
1793
|
+
* Only affects the six primary toolbar buttons (select/marker/
|
|
1794
|
+
* highlighter/eraser/note/text) — the "more tools" overflow menu is a
|
|
1795
|
+
* native `<select>`, which can only render plain text `<option>`s.
|
|
1796
|
+
*/
|
|
1797
|
+
icons?: Partial<Record<BuiltInTool, ReactNode>>;
|
|
1765
1798
|
}
|
|
1766
1799
|
type DefaultUIRegion = "tools" | "history" | "view" | "style" | "search" | "import" | "export" | "inlineEditing" | "styleShelf";
|
|
1767
1800
|
/** Props for the toolbar/topBar/stylePanel/contextMenu slots. */
|
|
@@ -1791,7 +1824,7 @@ interface DefaultUISlots {
|
|
|
1791
1824
|
*/
|
|
1792
1825
|
contextMenu?: ComponentType<BoardSlotProps> | null;
|
|
1793
1826
|
}
|
|
1794
|
-
declare function DefaultBoardChrome({ controller, snapshot, renderPortal, className, style, regions, slots }: DefaultBoardChromeProps): react.JSX.Element;
|
|
1827
|
+
declare function DefaultBoardChrome({ controller, snapshot, renderPortal, className, style, regions, slots, icons }: DefaultBoardChromeProps): react.JSX.Element;
|
|
1795
1828
|
|
|
1796
1829
|
interface InlineEditorsProps {
|
|
1797
1830
|
controller: BoardController;
|
|
@@ -1848,8 +1881,14 @@ interface ScrawlProps extends Omit<CreateBoardControllerOptions, "canvas"> {
|
|
|
1848
1881
|
onThemeDiagnostic?: (diagnostic: ScrawlThemeDiagnostic) => void;
|
|
1849
1882
|
/** Provide null for a headless Board, or an existing canvas to control its identity. */
|
|
1850
1883
|
canvas?: HTMLCanvasElement | null;
|
|
1884
|
+
/**
|
|
1885
|
+
* Per-tool icon override for the default toolbar's primary buttons —
|
|
1886
|
+
* only applies when you don't supply `children` (i.e. you're using the
|
|
1887
|
+
* SDK's default UI). See DefaultBoardChromeProps.icons.
|
|
1888
|
+
*/
|
|
1889
|
+
icons?: Partial<Record<BuiltInTool, ReactNode>>;
|
|
1851
1890
|
}
|
|
1852
|
-
declare function Scrawl({ children, preset, theme, portalContainer, className, style, onReady, onError, onThemeDiagnostic, canvas: suppliedCanvas, ...options }: ScrawlProps): react.JSX.Element;
|
|
1891
|
+
declare function Scrawl({ children, preset, theme, portalContainer, className, style, onReady, onError, onThemeDiagnostic, canvas: suppliedCanvas, icons, ...options }: ScrawlProps): react.JSX.Element;
|
|
1853
1892
|
interface ScrawlCanvasProps {
|
|
1854
1893
|
element?: HTMLCanvasElement;
|
|
1855
1894
|
className?: string;
|
|
@@ -1864,8 +1903,10 @@ interface ScrawlDefaultUIProps {
|
|
|
1864
1903
|
regions?: Partial<Record<DefaultUIRegion, boolean>>;
|
|
1865
1904
|
/** Replace (component) or hide (null) a coarse region; omit for the SDK default. */
|
|
1866
1905
|
slots?: DefaultUISlots;
|
|
1906
|
+
/** Per-tool icon override for the primary toolbar buttons — see DefaultBoardChromeProps.icons. */
|
|
1907
|
+
icons?: Partial<Record<BuiltInTool, ReactNode>>;
|
|
1867
1908
|
}
|
|
1868
|
-
declare function ScrawlDefaultUI({ className, style, regions, slots }: ScrawlDefaultUIProps): react.JSX.Element;
|
|
1909
|
+
declare function ScrawlDefaultUI({ className, style, regions, slots, icons }: ScrawlDefaultUIProps): react.JSX.Element;
|
|
1869
1910
|
declare function ScrawlPortal({ children }: {
|
|
1870
1911
|
children: ReactNode;
|
|
1871
1912
|
}): react.ReactPortal | null;
|
package/dist/index.js
CHANGED
|
@@ -1778,7 +1778,15 @@ function changeToOps(change, restoring = false) {
|
|
|
1778
1778
|
//#region src/core/shapes/ribbonEdges.ts
|
|
1779
1779
|
var MIN_WIDTH_FACTOR = .35;
|
|
1780
1780
|
var END_TAPER = .55;
|
|
1781
|
-
|
|
1781
|
+
/**
|
|
1782
|
+
* @param handDrawn Default `true`: ink's organic feel — width follows
|
|
1783
|
+
* per-point pressure, and both ends taper. Pass `false` for a geometric
|
|
1784
|
+
* shape outline, which has no real pressure signal and isn't a pen stroke
|
|
1785
|
+
* with a lift-off: it renders at the exact `baseWidth`, uniformly, with no
|
|
1786
|
+
* taper at its start/end (which, for a closed shape, is the same point —
|
|
1787
|
+
* tapering it would visibly pinch just that one corner).
|
|
1788
|
+
*/
|
|
1789
|
+
function ribbonEdges(points, baseWidth, handDrawn = true) {
|
|
1782
1790
|
const pts = points.length === 1 ? [points[0], {
|
|
1783
1791
|
...points[0],
|
|
1784
1792
|
x: points[0].x + baseWidth * .05
|
|
@@ -1793,8 +1801,8 @@ function ribbonEdges(points, baseWidth) {
|
|
|
1793
1801
|
const len = Math.hypot(dx, dy) || 1;
|
|
1794
1802
|
dx /= len;
|
|
1795
1803
|
dy /= len;
|
|
1796
|
-
let width = baseWidth * (MIN_WIDTH_FACTOR + (1 - MIN_WIDTH_FACTOR) * pts[i].pressure);
|
|
1797
|
-
if (i === 0 || i === n - 1) width *= END_TAPER;
|
|
1804
|
+
let width = handDrawn ? baseWidth * (MIN_WIDTH_FACTOR + (1 - MIN_WIDTH_FACTOR) * pts[i].pressure) : baseWidth;
|
|
1805
|
+
if (handDrawn && (i === 0 || i === n - 1)) width *= END_TAPER;
|
|
1798
1806
|
const hw = width / 2;
|
|
1799
1807
|
out[i] = {
|
|
1800
1808
|
lx: pts[i].x - dy * hw,
|
|
@@ -2107,7 +2115,7 @@ function strokeToPaths(stroke) {
|
|
|
2107
2115
|
y,
|
|
2108
2116
|
pressure,
|
|
2109
2117
|
...erase > 0 ? { erase } : {}
|
|
2110
|
-
})), stroke.baseWidth);
|
|
2118
|
+
})), stroke.baseWidth, stroke.tool !== "shape");
|
|
2111
2119
|
const transform = stroke.matrix ? ` transform="matrix(${fmt(stroke.matrix[0])} ${fmt(-stroke.matrix[1])} ${fmt(-stroke.matrix[2])} ${fmt(stroke.matrix[3])} ${fmt(stroke.matrix[4])} ${fmt(-stroke.matrix[5])})"` : "";
|
|
2112
2120
|
const baseOpacity = stroke.tool === "highlighter" ? .4 : 1;
|
|
2113
2121
|
const paths = [];
|
|
@@ -35956,12 +35964,16 @@ var FRAGMENT = `
|
|
|
35956
35964
|
return max(lineAlpha2.x, lineAlpha2.y);
|
|
35957
35965
|
}
|
|
35958
35966
|
|
|
35959
|
-
// Distance from p's nearest grid intersection, in world units.
|
|
35960
|
-
//
|
|
35967
|
+
// Distance from p's nearest grid intersection, in world units. A line's
|
|
35968
|
+
// width reads as visible even at 1px because it spans the whole screen;
|
|
35969
|
+
// an isolated dot at that same 1px radius gets almost entirely eaten by
|
|
35970
|
+
// anti-aliasing and all but disappears. Scaling the radius up keeps a
|
|
35971
|
+
// dot's on-screen weight comparable to a line drawn with the same
|
|
35972
|
+
// uGridLineWidthPx, rather than using it literally as a radius.
|
|
35961
35973
|
float gridDotAlpha(vec2 p) {
|
|
35962
35974
|
vec2 cell = mod(p, uGridSpacing) - uGridSpacing * 0.5;
|
|
35963
35975
|
float dist = length(cell);
|
|
35964
|
-
float radius = uGridLineWidthPx * uWorldPerPixel;
|
|
35976
|
+
float radius = uGridLineWidthPx * uWorldPerPixel * 1.75;
|
|
35965
35977
|
float aa = uWorldPerPixel;
|
|
35966
35978
|
return 1.0 - smoothstep(radius - aa * 0.5, radius + aa * 0.5, dist);
|
|
35967
35979
|
}
|
|
@@ -36388,8 +36400,8 @@ var INK_Z = .02;
|
|
|
36388
36400
|
var HIGHLIGHT_Z = .016;
|
|
36389
36401
|
/** Ghosts sit under everything so live strokes always read on top. */
|
|
36390
36402
|
var GHOST_Z = .012;
|
|
36391
|
-
function buildRibbonGeometry(points, baseWidth, z = INK_Z) {
|
|
36392
|
-
const edges = ribbonEdges(points, baseWidth);
|
|
36403
|
+
function buildRibbonGeometry(points, baseWidth, z = INK_Z, handDrawn = true) {
|
|
36404
|
+
const edges = ribbonEdges(points, baseWidth, handDrawn);
|
|
36393
36405
|
const n = edges.length;
|
|
36394
36406
|
const positions = new Float32Array(n * 2 * 3);
|
|
36395
36407
|
const uvs = new Float32Array(n * 2 * 2);
|
|
@@ -36434,6 +36446,8 @@ function buildRibbonGeometry(points, baseWidth, z = INK_Z) {
|
|
|
36434
36446
|
//#endregion
|
|
36435
36447
|
//#region src/renderer/shapes/strokeRenderer.ts
|
|
36436
36448
|
var strokeZ = (stroke) => stroke.tool === "highlighter" ? HIGHLIGHT_Z : INK_Z;
|
|
36449
|
+
/** Shapes (rect/ellipse/line/arrow/...) are geometric outlines, not pressure-sensitive ink. */
|
|
36450
|
+
var handDrawn = (stroke) => stroke.tool !== "shape";
|
|
36437
36451
|
/**
|
|
36438
36452
|
* Derives ink meshes from the document by subscription — the document never
|
|
36439
36453
|
* knows the renderer exists. Also hosts session-only ghosts (decision Q7)
|
|
@@ -36458,12 +36472,12 @@ var StrokeRenderer = class {
|
|
|
36458
36472
|
this.ghostGroup.visible = visible;
|
|
36459
36473
|
}
|
|
36460
36474
|
/** Leave a smeared residue of an erased span (never persisted). */
|
|
36461
|
-
addGhost(points, color, baseWidth) {
|
|
36475
|
+
addGhost(points, color, baseWidth, handDrawn = true) {
|
|
36462
36476
|
if (points.length < 2) return;
|
|
36463
36477
|
const mesh = new Mesh(buildRibbonGeometry(points.map((p) => ({
|
|
36464
36478
|
...p,
|
|
36465
36479
|
erase: 0
|
|
36466
|
-
})), baseWidth * 1.7, GHOST_Z), this.materials.ghost(color));
|
|
36480
|
+
})), baseWidth * 1.7, GHOST_Z, handDrawn), this.materials.ghost(color));
|
|
36467
36481
|
this.ghostGroup.add(mesh);
|
|
36468
36482
|
}
|
|
36469
36483
|
/**
|
|
@@ -36475,7 +36489,7 @@ var StrokeRenderer = class {
|
|
|
36475
36489
|
if (this.meshes.has(stroke.id) || this.retiredLive.has(stroke.id) || stroke.points.length < 2) return;
|
|
36476
36490
|
this.liveUser.set(stroke.id, userId);
|
|
36477
36491
|
const existing = this.liveMeshes.get(stroke.id);
|
|
36478
|
-
const geometry = buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke));
|
|
36492
|
+
const geometry = buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke), handDrawn(stroke));
|
|
36479
36493
|
if (existing) {
|
|
36480
36494
|
existing.geometry.dispose();
|
|
36481
36495
|
existing.geometry = geometry;
|
|
@@ -36535,7 +36549,7 @@ var StrokeRenderer = class {
|
|
|
36535
36549
|
const mesh = this.meshes.get(stroke.id);
|
|
36536
36550
|
if (mesh) {
|
|
36537
36551
|
mesh.geometry.dispose();
|
|
36538
|
-
mesh.geometry = buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke));
|
|
36552
|
+
mesh.geometry = buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke), handDrawn(stroke));
|
|
36539
36553
|
}
|
|
36540
36554
|
}
|
|
36541
36555
|
for (const stroke of change.transformed) {
|
|
@@ -36544,7 +36558,7 @@ var StrokeRenderer = class {
|
|
|
36544
36558
|
}
|
|
36545
36559
|
}
|
|
36546
36560
|
create(stroke) {
|
|
36547
|
-
const mesh = new Mesh(buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke)), stroke.tool === "highlighter" ? this.materials.highlight(stroke.color) : this.materials.get(stroke.color));
|
|
36561
|
+
const mesh = new Mesh(buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke), handDrawn(stroke)), stroke.tool === "highlighter" ? this.materials.highlight(stroke.color) : this.materials.get(stroke.color));
|
|
36548
36562
|
mesh.matrixAutoUpdate = false;
|
|
36549
36563
|
syncMatrix(mesh, stroke);
|
|
36550
36564
|
this.meshes.set(stroke.id, mesh);
|
|
@@ -44683,9 +44697,10 @@ function hitStroke(doc, index, p, slop) {
|
|
|
44683
44697
|
const scale = m ? avgScale(m) : 1;
|
|
44684
44698
|
let prev = null;
|
|
44685
44699
|
let prevWidth = 0;
|
|
44700
|
+
const handDrawn = stroke.tool !== "shape";
|
|
44686
44701
|
for (const point of stroke.points) {
|
|
44687
44702
|
const world = m ? apply(m, point) : point;
|
|
44688
|
-
const width = stroke.baseWidth * (MIN_WIDTH_FACTOR + (1 - MIN_WIDTH_FACTOR) * point.pressure) * scale;
|
|
44703
|
+
const width = (handDrawn ? stroke.baseWidth * (MIN_WIDTH_FACTOR + (1 - MIN_WIDTH_FACTOR) * point.pressure) : stroke.baseWidth) * scale;
|
|
44689
44704
|
if (prev) {
|
|
44690
44705
|
const reach = Math.max(width, prevWidth) / 2 + slop;
|
|
44691
44706
|
if (distToSegmentSq(p, prev, world) <= reach * reach) {
|
|
@@ -44819,7 +44834,7 @@ var EraserTool = class {
|
|
|
44819
44834
|
...p,
|
|
44820
44835
|
...apply(m, p)
|
|
44821
44836
|
})) : run.points;
|
|
44822
|
-
this.ctx.renderer.addGhost(world, current.color, current.baseWidth * (m ? avgScale(m) : 1));
|
|
44837
|
+
this.ctx.renderer.addGhost(world, current.color, current.baseWidth * (m ? avgScale(m) : 1), current.tool !== "shape");
|
|
44823
44838
|
}
|
|
44824
44839
|
}
|
|
44825
44840
|
doc.removeStrokes(before.map((s) => s.id));
|
|
@@ -45052,10 +45067,7 @@ function rect(a, b, step) {
|
|
|
45052
45067
|
a
|
|
45053
45068
|
];
|
|
45054
45069
|
const out = [];
|
|
45055
|
-
for (let i = 0; i < corners.length - 1; i++)
|
|
45056
|
-
const edge = sampleSegment(corners[i], corners[i + 1], step);
|
|
45057
|
-
out.push(...i === 0 ? edge : edge.slice(1));
|
|
45058
|
-
}
|
|
45070
|
+
for (let i = 0; i < corners.length - 1; i++) out.push(...sampleSegment(corners[i], corners[i + 1], step));
|
|
45059
45071
|
return out;
|
|
45060
45072
|
}
|
|
45061
45073
|
function ellipse(a, b, step) {
|
|
@@ -45126,10 +45138,7 @@ function polygon(sides, a, b, step, startAngle = -Math.PI / 2) {
|
|
|
45126
45138
|
}
|
|
45127
45139
|
vertices.push(vertices[0]);
|
|
45128
45140
|
const out = [];
|
|
45129
|
-
for (let i = 0; i < vertices.length - 1; i++)
|
|
45130
|
-
const edge = sampleSegment(vertices[i], vertices[i + 1], step);
|
|
45131
|
-
out.push(...i === 0 ? edge : edge.slice(1));
|
|
45132
|
-
}
|
|
45141
|
+
for (let i = 0; i < vertices.length - 1; i++) out.push(...sampleSegment(vertices[i], vertices[i + 1], step));
|
|
45133
45142
|
return out;
|
|
45134
45143
|
}
|
|
45135
45144
|
function star(points, a, b, step) {
|
|
@@ -45153,10 +45162,7 @@ function star(points, a, b, step) {
|
|
|
45153
45162
|
}
|
|
45154
45163
|
vertices.push(vertices[0]);
|
|
45155
45164
|
const out = [];
|
|
45156
|
-
for (let i = 0; i < vertices.length - 1; i++)
|
|
45157
|
-
const edge = sampleSegment(vertices[i], vertices[i + 1], step);
|
|
45158
|
-
out.push(...i === 0 ? edge : edge.slice(1));
|
|
45159
|
-
}
|
|
45165
|
+
for (let i = 0; i < vertices.length - 1; i++) out.push(...sampleSegment(vertices[i], vertices[i + 1], step));
|
|
45160
45166
|
return out;
|
|
45161
45167
|
}
|
|
45162
45168
|
function heart(a, b, step) {
|
|
@@ -45233,6 +45239,7 @@ var ShapeTool = class {
|
|
|
45233
45239
|
id: crypto.randomUUID(),
|
|
45234
45240
|
color: this.ctx.inkColor(),
|
|
45235
45241
|
baseWidth: this.ctx.shapeStrokeWidth(),
|
|
45242
|
+
tool: "shape",
|
|
45236
45243
|
points
|
|
45237
45244
|
}));
|
|
45238
45245
|
this.ctx.clusters.assign(strokes[0]);
|
|
@@ -45266,7 +45273,7 @@ var ShapeTool = class {
|
|
|
45266
45273
|
this.previews[i].visible = polyline !== void 0;
|
|
45267
45274
|
if (polyline) {
|
|
45268
45275
|
this.previews[i].geometry.dispose();
|
|
45269
|
-
this.previews[i].geometry = buildRibbonGeometry(polyline, this.ctx.shapeStrokeWidth());
|
|
45276
|
+
this.previews[i].geometry = buildRibbonGeometry(polyline, this.ctx.shapeStrokeWidth(), void 0, false);
|
|
45270
45277
|
}
|
|
45271
45278
|
}
|
|
45272
45279
|
}
|
|
@@ -49427,6 +49434,7 @@ var scrawlThemePresets = Object.freeze({
|
|
|
49427
49434
|
text: "#1c1c1a",
|
|
49428
49435
|
textMuted: "#62625c",
|
|
49429
49436
|
edge: "#d4d4cc",
|
|
49437
|
+
primary: "#1856a3",
|
|
49430
49438
|
focus: "#1856a3",
|
|
49431
49439
|
selection: "#d9e8f7",
|
|
49432
49440
|
danger: "#a3302b",
|
|
@@ -49459,6 +49467,7 @@ var scrawlThemePresets = Object.freeze({
|
|
|
49459
49467
|
text: "#f4f4ee",
|
|
49460
49468
|
textMuted: "#b7b8ae",
|
|
49461
49469
|
edge: "#484a42",
|
|
49470
|
+
primary: "#9dc8ef",
|
|
49462
49471
|
focus: "#9dc8ef",
|
|
49463
49472
|
selection: "#314b63",
|
|
49464
49473
|
danger: "#ffaaa3",
|
|
@@ -49492,6 +49501,7 @@ var variableNames = {
|
|
|
49492
49501
|
text: "--scrawl-color-text",
|
|
49493
49502
|
textMuted: "--scrawl-color-text-muted",
|
|
49494
49503
|
edge: "--scrawl-color-edge",
|
|
49504
|
+
primary: "--scrawl-color-primary",
|
|
49495
49505
|
focus: "--scrawl-color-focus",
|
|
49496
49506
|
selection: "--scrawl-color-selection",
|
|
49497
49507
|
danger: "--scrawl-color-danger",
|
|
@@ -49524,6 +49534,7 @@ var colorTokens = /* @__PURE__ */ new Set([
|
|
|
49524
49534
|
"text",
|
|
49525
49535
|
"textMuted",
|
|
49526
49536
|
"edge",
|
|
49537
|
+
"primary",
|
|
49527
49538
|
"focus",
|
|
49528
49539
|
"selection",
|
|
49529
49540
|
"danger",
|
|
@@ -50165,7 +50176,7 @@ var INKS = [
|
|
|
50165
50176
|
["Forest", "#315d46"],
|
|
50166
50177
|
["Oxblood", "#773f3b"]
|
|
50167
50178
|
];
|
|
50168
|
-
function DefaultBoardChrome({ controller, snapshot, renderPortal, className, style, regions, slots }) {
|
|
50179
|
+
function DefaultBoardChrome({ controller, snapshot, renderPortal, className, style, regions, slots, icons }) {
|
|
50169
50180
|
const [panel, setPanel] = useState(null);
|
|
50170
50181
|
const [query, setQuery] = useState("");
|
|
50171
50182
|
const [announcement, setAnnouncement] = useState("Board controls ready");
|
|
@@ -50319,7 +50330,10 @@ function DefaultBoardChrome({ controller, snapshot, renderPortal, className, sty
|
|
|
50319
50330
|
onClick: () => chooseTool(tool),
|
|
50320
50331
|
title: `${label} (${key})`,
|
|
50321
50332
|
type: "button",
|
|
50322
|
-
children:
|
|
50333
|
+
children: icons?.[tool] ? /* @__PURE__ */ jsx("span", {
|
|
50334
|
+
"aria-hidden": "true",
|
|
50335
|
+
children: icons[tool]
|
|
50336
|
+
}) : label
|
|
50323
50337
|
}, tool)), /* @__PURE__ */ jsxs("label", {
|
|
50324
50338
|
className: "scrawl-board__tool-select",
|
|
50325
50339
|
children: [/* @__PURE__ */ jsx("span", { children: "More tools" }), /* @__PURE__ */ jsxs("select", {
|
|
@@ -50782,7 +50796,7 @@ function ScrawlProvider({ controller, children, preset = "light", theme, portalC
|
|
|
50782
50796
|
})
|
|
50783
50797
|
});
|
|
50784
50798
|
}
|
|
50785
|
-
function Scrawl({ children, preset, theme, portalContainer, className, style, onReady, onError, onThemeDiagnostic, canvas: suppliedCanvas, ...options }) {
|
|
50799
|
+
function Scrawl({ children, preset, theme, portalContainer, className, style, onReady, onError, onThemeDiagnostic, canvas: suppliedCanvas, icons, ...options }) {
|
|
50786
50800
|
const [runtime, setRuntime] = useState(null);
|
|
50787
50801
|
const runtimeRef = useRef(null);
|
|
50788
50802
|
const ownedCanvasRef = useRef(null);
|
|
@@ -50878,7 +50892,7 @@ function Scrawl({ children, preset, theme, portalContainer, className, style, on
|
|
|
50878
50892
|
onThemeDiagnostic,
|
|
50879
50893
|
className,
|
|
50880
50894
|
style,
|
|
50881
|
-
children: [runtime.canvas && /* @__PURE__ */ jsx(ScrawlCanvas, { element: runtime.canvas }), children ?? /* @__PURE__ */ jsx(ScrawlDefaultUI, {})]
|
|
50895
|
+
children: [runtime.canvas && /* @__PURE__ */ jsx(ScrawlCanvas, { element: runtime.canvas }), children ?? /* @__PURE__ */ jsx(ScrawlDefaultUI, { icons })]
|
|
50882
50896
|
});
|
|
50883
50897
|
}
|
|
50884
50898
|
function ScrawlCanvas({ element, className, style, "aria-label": ariaLabel = "Scrawl Board" }) {
|
|
@@ -50914,10 +50928,11 @@ function ScrawlCanvas({ element, className, style, "aria-label": ariaLabel = "Sc
|
|
|
50914
50928
|
style
|
|
50915
50929
|
});
|
|
50916
50930
|
}
|
|
50917
|
-
function ScrawlDefaultUI({ className, style, regions, slots }) {
|
|
50931
|
+
function ScrawlDefaultUI({ className, style, regions, slots, icons }) {
|
|
50918
50932
|
return /* @__PURE__ */ jsx(DefaultBoardChrome, {
|
|
50919
50933
|
className,
|
|
50920
50934
|
controller: useScrawlController(),
|
|
50935
|
+
icons,
|
|
50921
50936
|
regions,
|
|
50922
50937
|
renderPortal: (children) => /* @__PURE__ */ jsx(ScrawlPortal, { children }),
|
|
50923
50938
|
slots,
|
package/dist/react.d.ts
CHANGED
|
@@ -371,8 +371,13 @@ interface StrokePoint extends BoardPoint {
|
|
|
371
371
|
*/
|
|
372
372
|
erase?: number;
|
|
373
373
|
}
|
|
374
|
-
/**
|
|
375
|
-
|
|
374
|
+
/**
|
|
375
|
+
* Which drawing tool made a stroke; undefined means marker (back-compat).
|
|
376
|
+
* `"shape"` (rect/ellipse/line/arrow/polygon/star/heart) renders at its
|
|
377
|
+
* exact configured width with no pressure variance or end taper — a
|
|
378
|
+
* geometric outline, not an expressive ink mark.
|
|
379
|
+
*/
|
|
380
|
+
type StrokeTool = "marker" | "highlighter" | "shape";
|
|
376
381
|
interface Stroke extends Lockable {
|
|
377
382
|
id: string;
|
|
378
383
|
color: string;
|
|
@@ -1064,6 +1069,13 @@ interface ScrawlTheme {
|
|
|
1064
1069
|
textMuted?: string;
|
|
1065
1070
|
/** Borders and dividers between UI chrome elements. */
|
|
1066
1071
|
edge?: string;
|
|
1072
|
+
/**
|
|
1073
|
+
* Brand accent color. Drives the active/selected state of toolbar
|
|
1074
|
+
* controls (e.g. the active tool button): its icon/text render in this
|
|
1075
|
+
* color, and its background is automatically derived as a light tint of
|
|
1076
|
+
* it (via `color-mix`) — set this one token and both follow.
|
|
1077
|
+
*/
|
|
1078
|
+
primary?: string;
|
|
1067
1079
|
/** Focus ring color. Checked for contrast against `surface`. */
|
|
1068
1080
|
focus?: string;
|
|
1069
1081
|
/** Selection highlight color (e.g. selected list items, not board object selection). */
|
|
@@ -1136,6 +1148,19 @@ interface DefaultBoardChromeProps {
|
|
|
1136
1148
|
style?: React.CSSProperties;
|
|
1137
1149
|
regions?: Partial<Record<DefaultUIRegion, boolean>>;
|
|
1138
1150
|
slots?: DefaultUISlots;
|
|
1151
|
+
/**
|
|
1152
|
+
* Swap in your own icon for a tool's primary toolbar button — pass any
|
|
1153
|
+
* icon from any library (`<Pencil />`, `<Icon icon="..." />`, an inline
|
|
1154
|
+
* `<svg>`, whatever). A tool with no entry here keeps its default text
|
|
1155
|
+
* label, so this is opt-in per tool, not all-or-nothing. Aria labeling
|
|
1156
|
+
* and the keyboard-shortcut tooltip stay text-based regardless, so the
|
|
1157
|
+
* board remains fully accessible even with icon-only buttons.
|
|
1158
|
+
*
|
|
1159
|
+
* Only affects the six primary toolbar buttons (select/marker/
|
|
1160
|
+
* highlighter/eraser/note/text) — the "more tools" overflow menu is a
|
|
1161
|
+
* native `<select>`, which can only render plain text `<option>`s.
|
|
1162
|
+
*/
|
|
1163
|
+
icons?: Partial<Record<BuiltInTool, ReactNode>>;
|
|
1139
1164
|
}
|
|
1140
1165
|
type DefaultUIRegion = "tools" | "history" | "view" | "style" | "search" | "import" | "export" | "inlineEditing" | "styleShelf";
|
|
1141
1166
|
/** Props for the toolbar/topBar/stylePanel/contextMenu slots. */
|
|
@@ -1165,7 +1190,7 @@ interface DefaultUISlots {
|
|
|
1165
1190
|
*/
|
|
1166
1191
|
contextMenu?: ComponentType<BoardSlotProps> | null;
|
|
1167
1192
|
}
|
|
1168
|
-
declare function DefaultBoardChrome({ controller, snapshot, renderPortal, className, style, regions, slots }: DefaultBoardChromeProps): react.JSX.Element;
|
|
1193
|
+
declare function DefaultBoardChrome({ controller, snapshot, renderPortal, className, style, regions, slots, icons }: DefaultBoardChromeProps): react.JSX.Element;
|
|
1169
1194
|
|
|
1170
1195
|
interface InlineEditorsProps {
|
|
1171
1196
|
controller: BoardController;
|
|
@@ -1222,8 +1247,14 @@ interface ScrawlProps extends Omit<CreateBoardControllerOptions, "canvas"> {
|
|
|
1222
1247
|
onThemeDiagnostic?: (diagnostic: ScrawlThemeDiagnostic) => void;
|
|
1223
1248
|
/** Provide null for a headless Board, or an existing canvas to control its identity. */
|
|
1224
1249
|
canvas?: HTMLCanvasElement | null;
|
|
1250
|
+
/**
|
|
1251
|
+
* Per-tool icon override for the default toolbar's primary buttons —
|
|
1252
|
+
* only applies when you don't supply `children` (i.e. you're using the
|
|
1253
|
+
* SDK's default UI). See DefaultBoardChromeProps.icons.
|
|
1254
|
+
*/
|
|
1255
|
+
icons?: Partial<Record<BuiltInTool, ReactNode>>;
|
|
1225
1256
|
}
|
|
1226
|
-
declare function Scrawl({ children, preset, theme, portalContainer, className, style, onReady, onError, onThemeDiagnostic, canvas: suppliedCanvas, ...options }: ScrawlProps): react.JSX.Element;
|
|
1257
|
+
declare function Scrawl({ children, preset, theme, portalContainer, className, style, onReady, onError, onThemeDiagnostic, canvas: suppliedCanvas, icons, ...options }: ScrawlProps): react.JSX.Element;
|
|
1227
1258
|
interface ScrawlCanvasProps {
|
|
1228
1259
|
element?: HTMLCanvasElement;
|
|
1229
1260
|
className?: string;
|
|
@@ -1238,8 +1269,10 @@ interface ScrawlDefaultUIProps {
|
|
|
1238
1269
|
regions?: Partial<Record<DefaultUIRegion, boolean>>;
|
|
1239
1270
|
/** Replace (component) or hide (null) a coarse region; omit for the SDK default. */
|
|
1240
1271
|
slots?: DefaultUISlots;
|
|
1272
|
+
/** Per-tool icon override for the primary toolbar buttons — see DefaultBoardChromeProps.icons. */
|
|
1273
|
+
icons?: Partial<Record<BuiltInTool, ReactNode>>;
|
|
1241
1274
|
}
|
|
1242
|
-
declare function ScrawlDefaultUI({ className, style, regions, slots }: ScrawlDefaultUIProps): react.JSX.Element;
|
|
1275
|
+
declare function ScrawlDefaultUI({ className, style, regions, slots, icons }: ScrawlDefaultUIProps): react.JSX.Element;
|
|
1243
1276
|
declare function ScrawlPortal({ children }: {
|
|
1244
1277
|
children: ReactNode;
|
|
1245
1278
|
}): react.ReactPortal | null;
|
package/dist/react.js
CHANGED
|
@@ -1748,7 +1748,15 @@ function changeToOps(change, restoring = false) {
|
|
|
1748
1748
|
//#region src/core/shapes/ribbonEdges.ts
|
|
1749
1749
|
var MIN_WIDTH_FACTOR = .35;
|
|
1750
1750
|
var END_TAPER = .55;
|
|
1751
|
-
|
|
1751
|
+
/**
|
|
1752
|
+
* @param handDrawn Default `true`: ink's organic feel — width follows
|
|
1753
|
+
* per-point pressure, and both ends taper. Pass `false` for a geometric
|
|
1754
|
+
* shape outline, which has no real pressure signal and isn't a pen stroke
|
|
1755
|
+
* with a lift-off: it renders at the exact `baseWidth`, uniformly, with no
|
|
1756
|
+
* taper at its start/end (which, for a closed shape, is the same point —
|
|
1757
|
+
* tapering it would visibly pinch just that one corner).
|
|
1758
|
+
*/
|
|
1759
|
+
function ribbonEdges(points, baseWidth, handDrawn = true) {
|
|
1752
1760
|
const pts = points.length === 1 ? [points[0], {
|
|
1753
1761
|
...points[0],
|
|
1754
1762
|
x: points[0].x + baseWidth * .05
|
|
@@ -1763,8 +1771,8 @@ function ribbonEdges(points, baseWidth) {
|
|
|
1763
1771
|
const len = Math.hypot(dx, dy) || 1;
|
|
1764
1772
|
dx /= len;
|
|
1765
1773
|
dy /= len;
|
|
1766
|
-
let width = baseWidth * (MIN_WIDTH_FACTOR + (1 - MIN_WIDTH_FACTOR) * pts[i].pressure);
|
|
1767
|
-
if (i === 0 || i === n - 1) width *= END_TAPER;
|
|
1774
|
+
let width = handDrawn ? baseWidth * (MIN_WIDTH_FACTOR + (1 - MIN_WIDTH_FACTOR) * pts[i].pressure) : baseWidth;
|
|
1775
|
+
if (handDrawn && (i === 0 || i === n - 1)) width *= END_TAPER;
|
|
1768
1776
|
const hw = width / 2;
|
|
1769
1777
|
out[i] = {
|
|
1770
1778
|
lx: pts[i].x - dy * hw,
|
|
@@ -2073,7 +2081,7 @@ function strokeToPaths(stroke) {
|
|
|
2073
2081
|
y,
|
|
2074
2082
|
pressure,
|
|
2075
2083
|
...erase > 0 ? { erase } : {}
|
|
2076
|
-
})), stroke.baseWidth);
|
|
2084
|
+
})), stroke.baseWidth, stroke.tool !== "shape");
|
|
2077
2085
|
const transform = stroke.matrix ? ` transform="matrix(${fmt(stroke.matrix[0])} ${fmt(-stroke.matrix[1])} ${fmt(-stroke.matrix[2])} ${fmt(stroke.matrix[3])} ${fmt(stroke.matrix[4])} ${fmt(-stroke.matrix[5])})"` : "";
|
|
2078
2086
|
const baseOpacity = stroke.tool === "highlighter" ? .4 : 1;
|
|
2079
2087
|
const paths = [];
|
|
@@ -35918,12 +35926,16 @@ var FRAGMENT = `
|
|
|
35918
35926
|
return max(lineAlpha2.x, lineAlpha2.y);
|
|
35919
35927
|
}
|
|
35920
35928
|
|
|
35921
|
-
// Distance from p's nearest grid intersection, in world units.
|
|
35922
|
-
//
|
|
35929
|
+
// Distance from p's nearest grid intersection, in world units. A line's
|
|
35930
|
+
// width reads as visible even at 1px because it spans the whole screen;
|
|
35931
|
+
// an isolated dot at that same 1px radius gets almost entirely eaten by
|
|
35932
|
+
// anti-aliasing and all but disappears. Scaling the radius up keeps a
|
|
35933
|
+
// dot's on-screen weight comparable to a line drawn with the same
|
|
35934
|
+
// uGridLineWidthPx, rather than using it literally as a radius.
|
|
35923
35935
|
float gridDotAlpha(vec2 p) {
|
|
35924
35936
|
vec2 cell = mod(p, uGridSpacing) - uGridSpacing * 0.5;
|
|
35925
35937
|
float dist = length(cell);
|
|
35926
|
-
float radius = uGridLineWidthPx * uWorldPerPixel;
|
|
35938
|
+
float radius = uGridLineWidthPx * uWorldPerPixel * 1.75;
|
|
35927
35939
|
float aa = uWorldPerPixel;
|
|
35928
35940
|
return 1.0 - smoothstep(radius - aa * 0.5, radius + aa * 0.5, dist);
|
|
35929
35941
|
}
|
|
@@ -36350,8 +36362,8 @@ var INK_Z = .02;
|
|
|
36350
36362
|
var HIGHLIGHT_Z = .016;
|
|
36351
36363
|
/** Ghosts sit under everything so live strokes always read on top. */
|
|
36352
36364
|
var GHOST_Z = .012;
|
|
36353
|
-
function buildRibbonGeometry(points, baseWidth, z = INK_Z) {
|
|
36354
|
-
const edges = ribbonEdges(points, baseWidth);
|
|
36365
|
+
function buildRibbonGeometry(points, baseWidth, z = INK_Z, handDrawn = true) {
|
|
36366
|
+
const edges = ribbonEdges(points, baseWidth, handDrawn);
|
|
36355
36367
|
const n = edges.length;
|
|
36356
36368
|
const positions = new Float32Array(n * 2 * 3);
|
|
36357
36369
|
const uvs = new Float32Array(n * 2 * 2);
|
|
@@ -36396,6 +36408,8 @@ function buildRibbonGeometry(points, baseWidth, z = INK_Z) {
|
|
|
36396
36408
|
//#endregion
|
|
36397
36409
|
//#region src/renderer/shapes/strokeRenderer.ts
|
|
36398
36410
|
var strokeZ = (stroke) => stroke.tool === "highlighter" ? HIGHLIGHT_Z : INK_Z;
|
|
36411
|
+
/** Shapes (rect/ellipse/line/arrow/...) are geometric outlines, not pressure-sensitive ink. */
|
|
36412
|
+
var handDrawn = (stroke) => stroke.tool !== "shape";
|
|
36399
36413
|
/**
|
|
36400
36414
|
* Derives ink meshes from the document by subscription — the document never
|
|
36401
36415
|
* knows the renderer exists. Also hosts session-only ghosts (decision Q7)
|
|
@@ -36420,12 +36434,12 @@ var StrokeRenderer = class {
|
|
|
36420
36434
|
this.ghostGroup.visible = visible;
|
|
36421
36435
|
}
|
|
36422
36436
|
/** Leave a smeared residue of an erased span (never persisted). */
|
|
36423
|
-
addGhost(points, color, baseWidth) {
|
|
36437
|
+
addGhost(points, color, baseWidth, handDrawn = true) {
|
|
36424
36438
|
if (points.length < 2) return;
|
|
36425
36439
|
const mesh = new Mesh(buildRibbonGeometry(points.map((p) => ({
|
|
36426
36440
|
...p,
|
|
36427
36441
|
erase: 0
|
|
36428
|
-
})), baseWidth * 1.7, GHOST_Z), this.materials.ghost(color));
|
|
36442
|
+
})), baseWidth * 1.7, GHOST_Z, handDrawn), this.materials.ghost(color));
|
|
36429
36443
|
this.ghostGroup.add(mesh);
|
|
36430
36444
|
}
|
|
36431
36445
|
/**
|
|
@@ -36437,7 +36451,7 @@ var StrokeRenderer = class {
|
|
|
36437
36451
|
if (this.meshes.has(stroke.id) || this.retiredLive.has(stroke.id) || stroke.points.length < 2) return;
|
|
36438
36452
|
this.liveUser.set(stroke.id, userId);
|
|
36439
36453
|
const existing = this.liveMeshes.get(stroke.id);
|
|
36440
|
-
const geometry = buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke));
|
|
36454
|
+
const geometry = buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke), handDrawn(stroke));
|
|
36441
36455
|
if (existing) {
|
|
36442
36456
|
existing.geometry.dispose();
|
|
36443
36457
|
existing.geometry = geometry;
|
|
@@ -36497,7 +36511,7 @@ var StrokeRenderer = class {
|
|
|
36497
36511
|
const mesh = this.meshes.get(stroke.id);
|
|
36498
36512
|
if (mesh) {
|
|
36499
36513
|
mesh.geometry.dispose();
|
|
36500
|
-
mesh.geometry = buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke));
|
|
36514
|
+
mesh.geometry = buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke), handDrawn(stroke));
|
|
36501
36515
|
}
|
|
36502
36516
|
}
|
|
36503
36517
|
for (const stroke of change.transformed) {
|
|
@@ -36506,7 +36520,7 @@ var StrokeRenderer = class {
|
|
|
36506
36520
|
}
|
|
36507
36521
|
}
|
|
36508
36522
|
create(stroke) {
|
|
36509
|
-
const mesh = new Mesh(buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke)), stroke.tool === "highlighter" ? this.materials.highlight(stroke.color) : this.materials.get(stroke.color));
|
|
36523
|
+
const mesh = new Mesh(buildRibbonGeometry(stroke.points, stroke.baseWidth, strokeZ(stroke), handDrawn(stroke)), stroke.tool === "highlighter" ? this.materials.highlight(stroke.color) : this.materials.get(stroke.color));
|
|
36510
36524
|
mesh.matrixAutoUpdate = false;
|
|
36511
36525
|
syncMatrix(mesh, stroke);
|
|
36512
36526
|
this.meshes.set(stroke.id, mesh);
|
|
@@ -44645,9 +44659,10 @@ function hitStroke(doc, index, p, slop) {
|
|
|
44645
44659
|
const scale = m ? avgScale(m) : 1;
|
|
44646
44660
|
let prev = null;
|
|
44647
44661
|
let prevWidth = 0;
|
|
44662
|
+
const handDrawn = stroke.tool !== "shape";
|
|
44648
44663
|
for (const point of stroke.points) {
|
|
44649
44664
|
const world = m ? apply(m, point) : point;
|
|
44650
|
-
const width = stroke.baseWidth * (MIN_WIDTH_FACTOR + (1 - MIN_WIDTH_FACTOR) * point.pressure) * scale;
|
|
44665
|
+
const width = (handDrawn ? stroke.baseWidth * (MIN_WIDTH_FACTOR + (1 - MIN_WIDTH_FACTOR) * point.pressure) : stroke.baseWidth) * scale;
|
|
44651
44666
|
if (prev) {
|
|
44652
44667
|
const reach = Math.max(width, prevWidth) / 2 + slop;
|
|
44653
44668
|
if (distToSegmentSq(p, prev, world) <= reach * reach) {
|
|
@@ -44781,7 +44796,7 @@ var EraserTool = class {
|
|
|
44781
44796
|
...p,
|
|
44782
44797
|
...apply(m, p)
|
|
44783
44798
|
})) : run.points;
|
|
44784
|
-
this.ctx.renderer.addGhost(world, current.color, current.baseWidth * (m ? avgScale(m) : 1));
|
|
44799
|
+
this.ctx.renderer.addGhost(world, current.color, current.baseWidth * (m ? avgScale(m) : 1), current.tool !== "shape");
|
|
44785
44800
|
}
|
|
44786
44801
|
}
|
|
44787
44802
|
doc.removeStrokes(before.map((s) => s.id));
|
|
@@ -45014,10 +45029,7 @@ function rect(a, b, step) {
|
|
|
45014
45029
|
a
|
|
45015
45030
|
];
|
|
45016
45031
|
const out = [];
|
|
45017
|
-
for (let i = 0; i < corners.length - 1; i++)
|
|
45018
|
-
const edge = sampleSegment(corners[i], corners[i + 1], step);
|
|
45019
|
-
out.push(...i === 0 ? edge : edge.slice(1));
|
|
45020
|
-
}
|
|
45032
|
+
for (let i = 0; i < corners.length - 1; i++) out.push(...sampleSegment(corners[i], corners[i + 1], step));
|
|
45021
45033
|
return out;
|
|
45022
45034
|
}
|
|
45023
45035
|
function ellipse(a, b, step) {
|
|
@@ -45088,10 +45100,7 @@ function polygon(sides, a, b, step, startAngle = -Math.PI / 2) {
|
|
|
45088
45100
|
}
|
|
45089
45101
|
vertices.push(vertices[0]);
|
|
45090
45102
|
const out = [];
|
|
45091
|
-
for (let i = 0; i < vertices.length - 1; i++)
|
|
45092
|
-
const edge = sampleSegment(vertices[i], vertices[i + 1], step);
|
|
45093
|
-
out.push(...i === 0 ? edge : edge.slice(1));
|
|
45094
|
-
}
|
|
45103
|
+
for (let i = 0; i < vertices.length - 1; i++) out.push(...sampleSegment(vertices[i], vertices[i + 1], step));
|
|
45095
45104
|
return out;
|
|
45096
45105
|
}
|
|
45097
45106
|
function star(points, a, b, step) {
|
|
@@ -45115,10 +45124,7 @@ function star(points, a, b, step) {
|
|
|
45115
45124
|
}
|
|
45116
45125
|
vertices.push(vertices[0]);
|
|
45117
45126
|
const out = [];
|
|
45118
|
-
for (let i = 0; i < vertices.length - 1; i++)
|
|
45119
|
-
const edge = sampleSegment(vertices[i], vertices[i + 1], step);
|
|
45120
|
-
out.push(...i === 0 ? edge : edge.slice(1));
|
|
45121
|
-
}
|
|
45127
|
+
for (let i = 0; i < vertices.length - 1; i++) out.push(...sampleSegment(vertices[i], vertices[i + 1], step));
|
|
45122
45128
|
return out;
|
|
45123
45129
|
}
|
|
45124
45130
|
function heart(a, b, step) {
|
|
@@ -45195,6 +45201,7 @@ var ShapeTool = class {
|
|
|
45195
45201
|
id: crypto.randomUUID(),
|
|
45196
45202
|
color: this.ctx.inkColor(),
|
|
45197
45203
|
baseWidth: this.ctx.shapeStrokeWidth(),
|
|
45204
|
+
tool: "shape",
|
|
45198
45205
|
points
|
|
45199
45206
|
}));
|
|
45200
45207
|
this.ctx.clusters.assign(strokes[0]);
|
|
@@ -45228,7 +45235,7 @@ var ShapeTool = class {
|
|
|
45228
45235
|
this.previews[i].visible = polyline !== void 0;
|
|
45229
45236
|
if (polyline) {
|
|
45230
45237
|
this.previews[i].geometry.dispose();
|
|
45231
|
-
this.previews[i].geometry = buildRibbonGeometry(polyline, this.ctx.shapeStrokeWidth());
|
|
45238
|
+
this.previews[i].geometry = buildRibbonGeometry(polyline, this.ctx.shapeStrokeWidth(), void 0, false);
|
|
45232
45239
|
}
|
|
45233
45240
|
}
|
|
45234
45241
|
}
|
|
@@ -49389,6 +49396,7 @@ var scrawlThemePresets = Object.freeze({
|
|
|
49389
49396
|
text: "#1c1c1a",
|
|
49390
49397
|
textMuted: "#62625c",
|
|
49391
49398
|
edge: "#d4d4cc",
|
|
49399
|
+
primary: "#1856a3",
|
|
49392
49400
|
focus: "#1856a3",
|
|
49393
49401
|
selection: "#d9e8f7",
|
|
49394
49402
|
danger: "#a3302b",
|
|
@@ -49421,6 +49429,7 @@ var scrawlThemePresets = Object.freeze({
|
|
|
49421
49429
|
text: "#f4f4ee",
|
|
49422
49430
|
textMuted: "#b7b8ae",
|
|
49423
49431
|
edge: "#484a42",
|
|
49432
|
+
primary: "#9dc8ef",
|
|
49424
49433
|
focus: "#9dc8ef",
|
|
49425
49434
|
selection: "#314b63",
|
|
49426
49435
|
danger: "#ffaaa3",
|
|
@@ -49454,6 +49463,7 @@ var variableNames = {
|
|
|
49454
49463
|
text: "--scrawl-color-text",
|
|
49455
49464
|
textMuted: "--scrawl-color-text-muted",
|
|
49456
49465
|
edge: "--scrawl-color-edge",
|
|
49466
|
+
primary: "--scrawl-color-primary",
|
|
49457
49467
|
focus: "--scrawl-color-focus",
|
|
49458
49468
|
selection: "--scrawl-color-selection",
|
|
49459
49469
|
danger: "--scrawl-color-danger",
|
|
@@ -49486,6 +49496,7 @@ var colorTokens = /* @__PURE__ */ new Set([
|
|
|
49486
49496
|
"text",
|
|
49487
49497
|
"textMuted",
|
|
49488
49498
|
"edge",
|
|
49499
|
+
"primary",
|
|
49489
49500
|
"focus",
|
|
49490
49501
|
"selection",
|
|
49491
49502
|
"danger",
|
|
@@ -50127,7 +50138,7 @@ var INKS = [
|
|
|
50127
50138
|
["Forest", "#315d46"],
|
|
50128
50139
|
["Oxblood", "#773f3b"]
|
|
50129
50140
|
];
|
|
50130
|
-
function DefaultBoardChrome({ controller, snapshot, renderPortal, className, style, regions, slots }) {
|
|
50141
|
+
function DefaultBoardChrome({ controller, snapshot, renderPortal, className, style, regions, slots, icons }) {
|
|
50131
50142
|
const [panel, setPanel] = useState(null);
|
|
50132
50143
|
const [query, setQuery] = useState("");
|
|
50133
50144
|
const [announcement, setAnnouncement] = useState("Board controls ready");
|
|
@@ -50281,7 +50292,10 @@ function DefaultBoardChrome({ controller, snapshot, renderPortal, className, sty
|
|
|
50281
50292
|
onClick: () => chooseTool(tool),
|
|
50282
50293
|
title: `${label} (${key})`,
|
|
50283
50294
|
type: "button",
|
|
50284
|
-
children:
|
|
50295
|
+
children: icons?.[tool] ? /* @__PURE__ */ jsx("span", {
|
|
50296
|
+
"aria-hidden": "true",
|
|
50297
|
+
children: icons[tool]
|
|
50298
|
+
}) : label
|
|
50285
50299
|
}, tool)), /* @__PURE__ */ jsxs("label", {
|
|
50286
50300
|
className: "scrawl-board__tool-select",
|
|
50287
50301
|
children: [/* @__PURE__ */ jsx("span", { children: "More tools" }), /* @__PURE__ */ jsxs("select", {
|
|
@@ -50744,7 +50758,7 @@ function ScrawlProvider({ controller, children, preset = "light", theme, portalC
|
|
|
50744
50758
|
})
|
|
50745
50759
|
});
|
|
50746
50760
|
}
|
|
50747
|
-
function Scrawl({ children, preset, theme, portalContainer, className, style, onReady, onError, onThemeDiagnostic, canvas: suppliedCanvas, ...options }) {
|
|
50761
|
+
function Scrawl({ children, preset, theme, portalContainer, className, style, onReady, onError, onThemeDiagnostic, canvas: suppliedCanvas, icons, ...options }) {
|
|
50748
50762
|
const [runtime, setRuntime] = useState(null);
|
|
50749
50763
|
const runtimeRef = useRef(null);
|
|
50750
50764
|
const ownedCanvasRef = useRef(null);
|
|
@@ -50840,7 +50854,7 @@ function Scrawl({ children, preset, theme, portalContainer, className, style, on
|
|
|
50840
50854
|
onThemeDiagnostic,
|
|
50841
50855
|
className,
|
|
50842
50856
|
style,
|
|
50843
|
-
children: [runtime.canvas && /* @__PURE__ */ jsx(ScrawlCanvas, { element: runtime.canvas }), children ?? /* @__PURE__ */ jsx(ScrawlDefaultUI, {})]
|
|
50857
|
+
children: [runtime.canvas && /* @__PURE__ */ jsx(ScrawlCanvas, { element: runtime.canvas }), children ?? /* @__PURE__ */ jsx(ScrawlDefaultUI, { icons })]
|
|
50844
50858
|
});
|
|
50845
50859
|
}
|
|
50846
50860
|
function ScrawlCanvas({ element, className, style, "aria-label": ariaLabel = "Scrawl Board" }) {
|
|
@@ -50876,10 +50890,11 @@ function ScrawlCanvas({ element, className, style, "aria-label": ariaLabel = "Sc
|
|
|
50876
50890
|
style
|
|
50877
50891
|
});
|
|
50878
50892
|
}
|
|
50879
|
-
function ScrawlDefaultUI({ className, style, regions, slots }) {
|
|
50893
|
+
function ScrawlDefaultUI({ className, style, regions, slots, icons }) {
|
|
50880
50894
|
return /* @__PURE__ */ jsx(DefaultBoardChrome, {
|
|
50881
50895
|
className,
|
|
50882
50896
|
controller: useScrawlController(),
|
|
50897
|
+
icons,
|
|
50883
50898
|
regions,
|
|
50884
50899
|
renderPortal: (children) => /* @__PURE__ */ jsx(ScrawlPortal, { children }),
|
|
50885
50900
|
slots,
|
package/dist/styles.css
CHANGED
|
@@ -151,7 +151,8 @@
|
|
|
151
151
|
|
|
152
152
|
[data-scrawl-root] [data-scrawl-default-ui] button[aria-pressed="true"],
|
|
153
153
|
[data-scrawl-portal] .scrawl-board__panel button[aria-pressed="true"] {
|
|
154
|
-
background: var(--scrawl-color-
|
|
154
|
+
background: color-mix(in srgb, var(--scrawl-color-primary), var(--scrawl-color-surface) 82%);
|
|
155
|
+
color: var(--scrawl-color-primary);
|
|
155
156
|
font-weight: var(--scrawl-font-weight-strong);
|
|
156
157
|
}
|
|
157
158
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@scrawl-board/board",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.4",
|
|
4
4
|
"description": "The Scrawl Board SDK: an embeddable, collaborative infinite-canvas whiteboard for React and vanilla JS/TS apps.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|