@neta-art/cohub 12.2.0 → 12.4.0

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.
Files changed (93) hide show
  1. package/README.md +87 -3
  2. package/dist/board/editor/context.d.ts +2 -0
  3. package/dist/board/editor/document.d.ts +3 -1
  4. package/dist/board/editor/document.js +8 -6
  5. package/dist/board/editor/editor.d.ts +7 -3
  6. package/dist/board/editor/editor.js +8 -1
  7. package/dist/board/editor/index.d.ts +3 -3
  8. package/dist/board/editor/index.js +2 -2
  9. package/dist/board/editor/interaction.d.ts +23 -2
  10. package/dist/board/editor/interaction.js +99 -5
  11. package/dist/board/editor/query.d.ts +1 -1
  12. package/dist/board/editor/query.js +3 -7
  13. package/dist/board/editor/selection.js +13 -1
  14. package/dist/board/editor/tool.d.ts +4 -1
  15. package/dist/board/editor/tool.js +6 -1
  16. package/dist/board/model/asset-key.d.ts +9 -1
  17. package/dist/board/model/asset-key.js +27 -7
  18. package/dist/board/model/index.d.ts +5 -5
  19. package/dist/board/model/index.js +4 -4
  20. package/dist/board/model/items/create.js +1 -1
  21. package/dist/board/model/items/definition.d.ts +9 -1
  22. package/dist/board/model/items/index.d.ts +2 -2
  23. package/dist/board/model/items/registry.d.ts +2 -1
  24. package/dist/board/model/items/registry.js +1 -0
  25. package/dist/board/model/text-metrics.d.ts +21 -1
  26. package/dist/board/model/text-metrics.js +24 -1
  27. package/dist/board/model/themes.d.ts +49 -42
  28. package/dist/board/model/themes.js +49 -44
  29. package/dist/board/render/index.d.ts +8 -6
  30. package/dist/board/render/index.js +7 -5
  31. package/dist/board/render/labels.d.ts +15 -0
  32. package/dist/board/render/labels.js +14 -0
  33. package/dist/board/render/lod.d.ts +9 -1
  34. package/dist/board/render/lod.js +7 -1
  35. package/dist/board/render/media-interaction.d.ts +5 -2
  36. package/dist/board/render/media-interaction.js +14 -2
  37. package/dist/board/render/renderers/arrow-card-renderer.js +7 -5
  38. package/dist/board/render/renderers/board-renderer-registry.d.ts +12 -1
  39. package/dist/board/render/renderers/board-renderer-registry.js +9 -4
  40. package/dist/board/render/renderers/far-plate.d.ts +4 -1
  41. package/dist/board/render/renderers/far-plate.js +5 -1
  42. package/dist/board/render/renderers/file-card-renderer.js +44 -24
  43. package/dist/board/render/renderers/frame-card-renderer.js +9 -3
  44. package/dist/board/render/renderers/image-card-renderer.js +2 -2
  45. package/dist/board/render/renderers/model-card-renderer.js +2 -2
  46. package/dist/board/render/renderers/task-card-renderer.js +2 -2
  47. package/dist/board/render/renderers/video-card-renderer.js +2 -2
  48. package/dist/board/render/style.d.ts +1 -0
  49. package/dist/board/render/style.js +3 -1
  50. package/dist/board/render/text-edit.d.ts +32 -0
  51. package/dist/board/render/text-edit.js +134 -0
  52. package/dist/board/render/text-fit.d.ts +1 -3
  53. package/dist/board/render/text-fit.js +4 -43
  54. package/dist/board/render/text-measurement.d.ts +12 -2
  55. package/dist/board/render/text-measurement.js +55 -10
  56. package/dist/board/replica/index.d.ts +4 -2
  57. package/dist/board/replica/index.js +34 -21
  58. package/dist/board/replica/indexeddb.d.ts +16 -0
  59. package/dist/board/replica/indexeddb.js +155 -0
  60. package/dist/board/stage/assets.d.ts +16 -4
  61. package/dist/board/stage/assets.js +155 -30
  62. package/dist/board/stage/bench.d.ts +35 -0
  63. package/dist/board/stage/bench.js +153 -0
  64. package/dist/board/stage/device.d.ts +41 -0
  65. package/dist/board/stage/device.js +62 -0
  66. package/dist/board/stage/frame-budget.d.ts +19 -0
  67. package/dist/board/stage/frame-budget.js +69 -0
  68. package/dist/board/stage/idle.d.ts +4 -0
  69. package/dist/board/stage/idle.js +26 -0
  70. package/dist/board/stage/index.d.ts +9 -5
  71. package/dist/board/stage/index.js +5 -2
  72. package/dist/board/stage/presses.d.ts +21 -0
  73. package/dist/board/stage/presses.js +66 -0
  74. package/dist/board/stage/scene.d.ts +4 -0
  75. package/dist/board/stage/scene.js +45 -13
  76. package/dist/board/stage/stage.d.ts +21 -2
  77. package/dist/board/stage/stage.js +309 -89
  78. package/dist/board/stage/texture-pool.d.ts +6 -4
  79. package/dist/board/stage/texture-pool.js +42 -8
  80. package/dist/board/stage/views.d.ts +1 -1
  81. package/dist/board/stage/views.js +2 -2
  82. package/dist/chunks/environment.d.ts +6 -0
  83. package/dist/chunks/http.d.ts +9 -1
  84. package/dist/chunks/http.js +791 -4
  85. package/dist/index.d.ts +52 -1
  86. package/dist/index.js +65 -733
  87. package/dist/protocol/dist/board-model.d.ts +15 -1
  88. package/dist/protocol/dist/board-model.js +12 -2
  89. package/dist/protocol/dist/board-schema.d.ts +2 -1
  90. package/dist/protocol/dist/board-schema.js +4 -1
  91. package/dist/protocol/dist/core/media.js +4 -0
  92. package/dist/protocol/dist/index.d.ts +2 -2
  93. package/package.json +2 -1
package/README.md CHANGED
@@ -112,7 +112,7 @@ const created = await space.boards.create({
112
112
  },
113
113
  });
114
114
 
115
- const board = space.board(created.id);
115
+ const board = space.board(created.id); // or await space.boards.open("boards/plan.board")
116
116
  const { items, version } = await board.get({ only: ["items"] });
117
117
  await board.apply({ items: { goal: { props: { text: "Shipped" } } } }, { baseVersion: version });
118
118
 
@@ -159,7 +159,7 @@ change, so they work directly with `useSyncExternalStore`:
159
159
  ```ts
160
160
  import { createBoardRegistry, defineBoardItem, worldPoint } from "@neta-art/cohub/board";
161
161
  import { createBoardEditor } from "@neta-art/cohub/board/editor";
162
- import { createBoardReplica } from "@neta-art/cohub/board/replica";
162
+ import { createBoardReplica, createIndexedDbBoardReplicaStorage } from "@neta-art/cohub/board/replica";
163
163
  import { createBoardAssetManager, mountBoardStage } from "@neta-art/cohub/board/stage";
164
164
  import { z } from "zod";
165
165
 
@@ -172,7 +172,13 @@ const order = defineBoardItem({
172
172
  capabilities: { canRotate: false },
173
173
  });
174
174
 
175
- const replica = createBoardReplica({ remote: space.board(boardId) });
175
+ // Cached documents and unsent writes survive a reload; clear them on sign-out
176
+ // with `clearBoardReplicaStorage({ userKey })`. The replica reads the Board again
177
+ // after a realtime reconnect and resends pending writes when the network returns.
178
+ const replica = createBoardReplica({
179
+ remote: space.board(boardId),
180
+ storage: createIndexedDbBoardReplicaStorage({ userKey, spaceId: space.id, boardId }),
181
+ });
176
182
  await replica.start();
177
183
 
178
184
  const editor = createBoardEditor({
@@ -230,6 +236,84 @@ rewritten by older ones. For a canvas look instead of DOM, pass renderers to
230
236
  `createBoardCardRendererResolver` from `board/render` and hand the result to
231
237
  the stage or to `renderBoardExport` as `renderers`.
232
238
 
239
+ ### Hosting a Board
240
+
241
+ Compose the SDK instead of wrapping it, and each release's loading, level of
242
+ detail and text work reaches your Board without code changes.
243
+
244
+ Pictures your own items show go through `itemAsset`. They share the built-in
245
+ loader: CDN variants sized to the screen, loads ordered by where the view is
246
+ heading, prefetch, compact copies when panned away, device budgets, one cache
247
+ and exports. `src` is a Space path, resolved like an image item's, or a URL;
248
+ `fit: "cover"` sizes pictures cropped to fill their box by the short edge. Call
249
+ `invalidateItemAssets()` when the data it reads changes, and `invalidatePath`
250
+ when a file changes:
251
+
252
+ ```ts
253
+ const assets = createBoardAssetManager({
254
+ resolveFileUrl: source.resolveFileUrl,
255
+ // undefined keeps the built-in rule; null shows no picture.
256
+ itemAsset: (item) => {
257
+ if (item.type !== "acme.order") return undefined;
258
+ const cover = covers.get(item.props.orderId as string); // "orders/o-1.webp"
259
+ return cover ? { src: cover, fit: "cover" } : null;
260
+ },
261
+ });
262
+ ```
263
+
264
+ - **Budgets.** Leave `lod`, `prefetch`, `concurrency` and `lruBudget` unset; they
265
+ follow `defaultBoardDeviceBudget()`. Force a tier with
266
+ `boardDeviceBudget(readBoardDeviceSignals(), "low")`.
267
+ - **Canvas cards.** Declare `lod` (`{ kind: "media" }` for pictures) and
268
+ `renderFar`, drawn with `drawFarPlate(graphics, item.frame, { fill:
269
+ farPictureTone(item, context) ?? paper })`. Wrap card text with
270
+ `layoutTextLines(text, font, width, { whiteSpace: "normal", maxLines: 2 })`
271
+ and apply `context.textResolution` in the `resolution` hook.
272
+ - **Opening cards.** Give a type `activation: "tap"` and handle `onActivate`: a
273
+ quick press opens the card without selecting it, a drag moves it (or pans with
274
+ the hand tool or read-only), and hovering shows a pointer instead of ports. A
275
+ press held in place past `BOARD_TAP_MAX_MS` does nothing: it was a pick-up. A
276
+ press on a selected card reports `selected: true`. `onLongPress` fires for a still
277
+ touch or pen; call `editor.pickUp()` there to pick the card up and drag it. The
278
+ stage drops the click a touch screen sends after a tap that opened something.
279
+ - **Card keys.** A renderer's `hitAt(item, point, context)` returns `{ action, cursor? }`
280
+ for keys it draws; the stage shows the cursor, passes the hovered key to renderers as
281
+ `context.hoveredHit` and sends presses to `onHit` (return `false` to let the editor
282
+ have them). Media cards report `BOARD_PLAY_MEDIA` this way. `handlesHit(item, action)`
283
+ names the keys you act on; the rest show no cursor and go to the editor.
284
+ - **Touch.** A touch on the selected item drags it, never a port. Pass
285
+ `touchRotation: false` to `createBoardEditor` to move and resize by touch and pen
286
+ without a rotation handle.
287
+ - **DOM views.** `viewLayer: "below"` puts them under the canvas, so selection and
288
+ strokes draw over them; views there get no input. Use a theme with `imageBackground`
289
+ and paint the Board background behind the host, or the canvas covers them.
290
+ - **Previews.** Write `preview: { title, excerpt, cover, src }` on your items so
291
+ clients without your renderer, exports and agents show them as cards. Keep it out
292
+ of undo with `editor.commitPatch(patch, { record: false })`.
293
+ - **Text editing.** Place the in-place editor with `boardTextEditLayout`, so its
294
+ caret sits on the canvas text:
295
+
296
+ ```ts
297
+ const layout = boardTextEditLayout(item, { viewport: editor.camera, scene: editor.scene, style: stage.theme.style, draft });
298
+ // A content-box textarea at layout.left/top, sized layout.width × height, padded by
299
+ // layout.padding, with `rotate(${layout.rotation}deg) scale(${layout.scale})` about its center.
300
+ ```
301
+
302
+ - **Themes.** Start from a built-in theme and override what differs:
303
+ `{ key: "neta-pixel", ...boardRenderTheme("neta-pixel") }`. `style.frameLabelSize`
304
+ sets frame titles; the editor's title hit box and text editor follow it.
305
+ - **Fonts.** Board text measures in `"Geist Variable"`; load
306
+ `@fontsource-variable/geist` so lines match the CLI export.
307
+ - **Agent context.** `onViewStateChange` on the editor reports `visibleRect` and
308
+ `selectedNodes`; send them with a prompt as
309
+ `buildViewportContentBlock([{ kind: "board", path, boardId, visibleRect, selectedNodes }])`
310
+ so the agent knows which Board, where and what the user is looking at.
311
+ - **Measuring.** `runBoardStageBench(stage)` drives the stage with wheel input and
312
+ reports frame and settle times; `stage.stats()` shows live, far and texture counts.
313
+
314
+ Release notes for Board changes end with `Hosts:`, naming what an embedding app
315
+ must change, or `Hosts: no action`.
316
+
233
317
  ### Images
234
318
 
235
319
  In the browser, `renderBoardExport(renderer, document, options)` from
@@ -84,6 +84,8 @@ type BoardEditorOptions = {
84
84
  initialTool?: BoardToolId;
85
85
  key?: string;
86
86
  readonly?: boolean;
87
+ /** false: touch and pen move and resize the selection but never rotate it. */
88
+ touchRotation?: boolean;
87
89
  preferences?: Partial<BoardEditorPreferences>;
88
90
  onPreferencesChange?: (preferences: BoardEditorPreferences) => void;
89
91
  onCommit: (patch: BoardPatch) => void | Promise<void>;
@@ -15,7 +15,9 @@ declare function createDocumentModule(ctx: EditorContext): {
15
15
  readonly hasScriptItems: boolean;
16
16
  setScriptItems: (items: ReadonlyMap<string, BoardItem>) => void;
17
17
  animationItems: () => Map<string, BoardItem>;
18
- commitPatch: (patch: BoardPatch) => Promise<void> | undefined;
18
+ commitPatch: (patch: BoardPatch, { record }?: {
19
+ record?: boolean;
20
+ }) => Promise<void> | undefined;
19
21
  rescene: (ids?: Iterable<string>) => void;
20
22
  reset: (next: BoardDocument) => void;
21
23
  setDraftItem: (id: string, item: BoardItem | null) => void;
@@ -206,7 +206,7 @@ function createDocumentModule(ctx) {
206
206
  for (const [id, item] of changes) setDraftItem(id, item);
207
207
  commit({}, record);
208
208
  }
209
- function commitPatch(patch) {
209
+ function commitPatch(patch, { record = true } = {}) {
210
210
  if (options.readonly) throw new Error("Board is read-only.");
211
211
  const before = state.base;
212
212
  const result = applyBoardPatchToDocument(before, patch);
@@ -214,11 +214,13 @@ function createDocumentModule(ctx) {
214
214
  const ids = Object.keys(patch.items ?? {});
215
215
  const redo = diffBoardEdits(before, result.document, ids);
216
216
  if (isEmptyBoardPatch(redo)) return;
217
- state.undoStack = [...state.undoStack.slice(-199), {
218
- undo: diffBoardEdits(result.document, before, ids),
219
- redo
220
- }];
221
- state.redoStack = [];
217
+ if (record) {
218
+ state.undoStack = [...state.undoStack.slice(-199), {
219
+ undo: diffBoardEdits(result.document, before, ids),
220
+ redo
221
+ }];
222
+ state.redoStack = [];
223
+ }
222
224
  adopt(result.document, ids);
223
225
  return send(redo).then((error) => {
224
226
  if (error) throw error;
@@ -11,7 +11,7 @@ import "../model/index.js";
11
11
  import { AlignMode, DistributeAxis } from "./align.js";
12
12
  import { BoardFocusOptions } from "./camera.js";
13
13
  import { BoardSelectionTransform, BoardTransformControl } from "./selection-transform.js";
14
- import { BoardInteraction, BoardPointerEvent, BoardTransformEnd } from "./interaction.js";
14
+ import { BoardActivation, BoardInteraction, BoardPointerEvent, BoardTransformEnd } from "./interaction.js";
15
15
  import { BoardAppMetadata, BoardTaskSource } from "./creation.js";
16
16
  import { BoardClipboardPayload } from "./clipboard.js";
17
17
  import { BoardNaturalSize } from "./snapshots.js";
@@ -136,11 +136,13 @@ declare function createBoardEditor(options: BoardEditorOptions): {
136
136
  setPlayedItems: (items: ReadonlyMap<string, BoardItem>) => void;
137
137
  setScriptItems: (items: ReadonlyMap<string, BoardItem>) => void;
138
138
  animationItems: () => Map<string, BoardItem>;
139
- commitPatch: (patch: BoardPatch) => Promise<void> | undefined;
139
+ commitPatch: (patch: BoardPatch, { record }?: {
140
+ record?: boolean;
141
+ }) => Promise<void> | undefined;
140
142
  consumeRecentlyAdded: () => string[];
141
143
  itemAt: (point: WorldPoint, exclude?: ReadonlySet<string> | undefined, includeTemporary?: boolean | undefined) => BoardSceneItem | null;
142
144
  itemById: (id: string) => BoardSceneItem | null;
143
- labelItemAt: (point: WorldPoint) => BoardSceneItem | null;
145
+ labelItemAt: (point: WorldPoint, frameLabelSize?: number | undefined) => BoardSceneItem | null;
144
146
  idsInRect: (rect: Rect, includeTemporary?: boolean | undefined) => string[];
145
147
  setCamera: (viewport: BoardViewport) => void;
146
148
  setCameraPolicy: (policy: BoardCameraPolicy) => void;
@@ -219,6 +221,8 @@ declare function createBoardEditor(options: BoardEditorOptions): {
219
221
  pointerUp: (event: BoardPointerEvent) => void;
220
222
  pointerLeave: () => void;
221
223
  subscribeTransforms: (listener: (event: BoardTransformEnd) => void) => () => void;
224
+ subscribeActivations: (listener: (event: BoardActivation) => void) => () => void;
225
+ pickUp: () => boolean;
222
226
  cancelPointerInteraction: () => void;
223
227
  destroy(): void;
224
228
  };
@@ -96,6 +96,11 @@ function createBoardEditor(options) {
96
96
  ctx.snapshots = createSnapshotsModule(ctx);
97
97
  ctx.remote = createRemoteModule(ctx);
98
98
  const { document, query, camera, selection, creation, deletion, copy, interaction, animation, snapshots, remote } = ctx;
99
+ const transformChannels = options.touchRotation === false ? [
100
+ "scene",
101
+ "selection",
102
+ "hover"
103
+ ] : ["scene", "selection"];
99
104
  let viewStateQueued = false;
100
105
  function emitViewState() {
101
106
  viewStateQueued = false;
@@ -193,7 +198,7 @@ function createBoardEditor(options) {
193
198
  return read(["scene", "selection"], selection.bounds);
194
199
  },
195
200
  get selectionTransform() {
196
- return read(["scene", "selection"], selection.transform);
201
+ return read(transformChannels, selection.transform);
197
202
  },
198
203
  get camera() {
199
204
  return read("camera", () => state.camera);
@@ -438,6 +443,8 @@ function createBoardEditor(options) {
438
443
  pointerUp: interaction.pointerUp,
439
444
  pointerLeave: interaction.pointerLeave,
440
445
  subscribeTransforms: interaction.subscribeTransforms,
446
+ subscribeActivations: interaction.subscribeActivations,
447
+ pickUp: interaction.pickUp,
441
448
  cancelPointerInteraction: interaction.cancel,
442
449
  destroy() {
443
450
  stopViewState();
@@ -2,13 +2,13 @@ import { BOARD_CLIPBOARD_MIME } from "../../protocol/dist/board.js";
2
2
  import { AlignMode, DistributeAxis, alignFrames, distributeFrames, framesBounds } from "./align.js";
3
3
  import { BoardFocusOptions } from "./camera.js";
4
4
  import { BoardSelectionTransform, BoardTransformControl, resizeCursorForHandle, resolveSelectionTransform, selectionTransformControlAt } from "./selection-transform.js";
5
- import { BoardInteraction, BoardPointerEvent, BoardTransformEnd } from "./interaction.js";
5
+ import { BoardActivation, BoardInteraction, BoardPointerEvent, BoardTransformEnd } from "./interaction.js";
6
6
  import { BoardAppMetadata, BoardTaskSource } from "./creation.js";
7
7
  import { BOARD_CLIPBOARD_VERSION, BoardClipboardPayload, DUPLICATE_OFFSET, MAX_CLIPBOARD_CHARS, MAX_CLIPBOARD_ITEMS, defaultPasteOffset, encodeClipboard, materializeClipboard, parseClipboard, remapItems } from "./clipboard.js";
8
8
  import { BoardNaturalSize } from "./snapshots.js";
9
9
  import { SnapAxis, SnapGuide, SnapOptions, SnapResult, computeSnap } from "./snapping.js";
10
10
  import { BoardEditorChannel, BoardEditorListener } from "./state.js";
11
- import { BOARD_HAND_TAP_SLOP, BoardToolId, canTapSelectWithHand, defaultBoardTool, isContinuousBoardTool, isCreationBoardTool, isWithinHandTapSlop } from "./tool.js";
11
+ import { BOARD_HAND_TAP_SLOP, BOARD_MOUSE_TAP_SLOP, BOARD_TAP_MAX_MS, BoardToolId, boardTapSlop, canTapSelectWithHand, defaultBoardTool, isContinuousBoardTool, isCreationBoardTool, isWithinHandTapSlop } from "./tool.js";
12
12
  import { BoardCameraPolicy, BoardEditorOptions, BoardEditorPreferences, BoardPlayhead, BoardViewState } from "./context.js";
13
13
  import { CONNECTION_PORT_HIT_RADIUS, CONNECTION_PORT_OFFSET, CONNECTION_PORT_RADIUS, CONNECTION_PORT_TOUCH_HIT_RADIUS, CONNECTION_SIDES, ConnectionPort, connectionPortAt, connectionPorts, portHitRadius } from "./connection-ports.js";
14
14
  import { sameJson } from "../model/json.js";
@@ -16,4 +16,4 @@ import { BoardEditPlayhead, diffBoardEdits, isEmptyBoardPatch, readPath, routeBo
16
16
  import { BoardEditor, createBoardEditor } from "./editor.js";
17
17
  import { SpatialEntry, SpatialIndex, createSpatialIndex } from "./spatial.js";
18
18
  import { normalizeWheelDelta, wheelZoomFactor } from "./wheel.js";
19
- export { AlignMode, BOARD_CLIPBOARD_MIME, BOARD_CLIPBOARD_VERSION, BOARD_HAND_TAP_SLOP, type BoardAppMetadata, type BoardCameraPolicy, BoardClipboardPayload, BoardEditPlayhead, type BoardEditor, type BoardEditorChannel, type BoardEditorListener, type BoardEditorOptions, type BoardEditorPreferences, type BoardFocusOptions, type BoardInteraction, type BoardNaturalSize, type BoardPlayhead, type BoardPointerEvent, BoardSelectionTransform, type BoardTaskSource, BoardToolId, BoardTransformControl, type BoardTransformEnd, type BoardViewState, CONNECTION_PORT_HIT_RADIUS, CONNECTION_PORT_OFFSET, CONNECTION_PORT_RADIUS, CONNECTION_PORT_TOUCH_HIT_RADIUS, CONNECTION_SIDES, ConnectionPort, DUPLICATE_OFFSET, DistributeAxis, MAX_CLIPBOARD_CHARS, MAX_CLIPBOARD_ITEMS, SnapAxis, SnapGuide, SnapOptions, SnapResult, SpatialEntry, SpatialIndex, alignFrames, canTapSelectWithHand, computeSnap, connectionPortAt, connectionPorts, createBoardEditor, createSpatialIndex, defaultBoardTool, defaultPasteOffset, diffBoardEdits, distributeFrames, encodeClipboard, framesBounds, isContinuousBoardTool, isCreationBoardTool, isEmptyBoardPatch, isWithinHandTapSlop, materializeClipboard, normalizeWheelDelta, parseClipboard, portHitRadius, readPath, remapItems, resizeCursorForHandle, resolveSelectionTransform, routeBoardEdits, sameJson, selectionTransformControlAt, wheelZoomFactor, writePath };
19
+ export { AlignMode, BOARD_CLIPBOARD_MIME, BOARD_CLIPBOARD_VERSION, BOARD_HAND_TAP_SLOP, BOARD_MOUSE_TAP_SLOP, BOARD_TAP_MAX_MS, type BoardActivation, type BoardAppMetadata, type BoardCameraPolicy, BoardClipboardPayload, BoardEditPlayhead, type BoardEditor, type BoardEditorChannel, type BoardEditorListener, type BoardEditorOptions, type BoardEditorPreferences, type BoardFocusOptions, type BoardInteraction, type BoardNaturalSize, type BoardPlayhead, type BoardPointerEvent, BoardSelectionTransform, type BoardTaskSource, BoardToolId, BoardTransformControl, type BoardTransformEnd, type BoardViewState, CONNECTION_PORT_HIT_RADIUS, CONNECTION_PORT_OFFSET, CONNECTION_PORT_RADIUS, CONNECTION_PORT_TOUCH_HIT_RADIUS, CONNECTION_SIDES, ConnectionPort, DUPLICATE_OFFSET, DistributeAxis, MAX_CLIPBOARD_CHARS, MAX_CLIPBOARD_ITEMS, SnapAxis, SnapGuide, SnapOptions, SnapResult, SpatialEntry, SpatialIndex, alignFrames, boardTapSlop, canTapSelectWithHand, computeSnap, connectionPortAt, connectionPorts, createBoardEditor, createSpatialIndex, defaultBoardTool, defaultPasteOffset, diffBoardEdits, distributeFrames, encodeClipboard, framesBounds, isContinuousBoardTool, isCreationBoardTool, isEmptyBoardPatch, isWithinHandTapSlop, materializeClipboard, normalizeWheelDelta, parseClipboard, portHitRadius, readPath, remapItems, resizeCursorForHandle, resolveSelectionTransform, routeBoardEdits, sameJson, selectionTransformControlAt, wheelZoomFactor, writePath };
@@ -5,9 +5,9 @@ import { diffBoardEdits, isEmptyBoardPatch, readPath, routeBoardEdits, writePath
5
5
  import { normalizeWheelDelta, wheelZoomFactor } from "./wheel.js";
6
6
  import { BOARD_CLIPBOARD_VERSION, DUPLICATE_OFFSET, MAX_CLIPBOARD_CHARS, MAX_CLIPBOARD_ITEMS, defaultPasteOffset, encodeClipboard, materializeClipboard, parseClipboard, remapItems } from "./clipboard.js";
7
7
  import { CONNECTION_PORT_HIT_RADIUS, CONNECTION_PORT_OFFSET, CONNECTION_PORT_RADIUS, CONNECTION_PORT_TOUCH_HIT_RADIUS, CONNECTION_SIDES, connectionPortAt, connectionPorts, portHitRadius } from "./connection-ports.js";
8
- import { BOARD_HAND_TAP_SLOP, canTapSelectWithHand, defaultBoardTool, isContinuousBoardTool, isCreationBoardTool, isWithinHandTapSlop } from "./tool.js";
8
+ import { BOARD_HAND_TAP_SLOP, BOARD_MOUSE_TAP_SLOP, BOARD_TAP_MAX_MS, boardTapSlop, canTapSelectWithHand, defaultBoardTool, isContinuousBoardTool, isCreationBoardTool, isWithinHandTapSlop } from "./tool.js";
9
9
  import { resizeCursorForHandle, resolveSelectionTransform, selectionTransformControlAt } from "./selection-transform.js";
10
10
  import { computeSnap } from "./snapping.js";
11
11
  import { createSpatialIndex } from "./spatial.js";
12
12
  import { createBoardEditor } from "./editor.js";
13
- export { BOARD_CLIPBOARD_MIME, BOARD_CLIPBOARD_VERSION, BOARD_HAND_TAP_SLOP, CONNECTION_PORT_HIT_RADIUS, CONNECTION_PORT_OFFSET, CONNECTION_PORT_RADIUS, CONNECTION_PORT_TOUCH_HIT_RADIUS, CONNECTION_SIDES, DUPLICATE_OFFSET, MAX_CLIPBOARD_CHARS, MAX_CLIPBOARD_ITEMS, alignFrames, canTapSelectWithHand, computeSnap, connectionPortAt, connectionPorts, createBoardEditor, createSpatialIndex, defaultBoardTool, defaultPasteOffset, diffBoardEdits, distributeFrames, encodeClipboard, framesBounds, isContinuousBoardTool, isCreationBoardTool, isEmptyBoardPatch, isWithinHandTapSlop, materializeClipboard, normalizeWheelDelta, parseClipboard, portHitRadius, readPath, remapItems, resizeCursorForHandle, resolveSelectionTransform, routeBoardEdits, sameJson, selectionTransformControlAt, wheelZoomFactor, writePath };
13
+ export { BOARD_CLIPBOARD_MIME, BOARD_CLIPBOARD_VERSION, BOARD_HAND_TAP_SLOP, BOARD_MOUSE_TAP_SLOP, BOARD_TAP_MAX_MS, CONNECTION_PORT_HIT_RADIUS, CONNECTION_PORT_OFFSET, CONNECTION_PORT_RADIUS, CONNECTION_PORT_TOUCH_HIT_RADIUS, CONNECTION_SIDES, DUPLICATE_OFFSET, MAX_CLIPBOARD_CHARS, MAX_CLIPBOARD_ITEMS, alignFrames, boardTapSlop, canTapSelectWithHand, computeSnap, connectionPortAt, connectionPorts, createBoardEditor, createSpatialIndex, defaultBoardTool, defaultPasteOffset, diffBoardEdits, distributeFrames, encodeClipboard, framesBounds, isContinuousBoardTool, isCreationBoardTool, isEmptyBoardPatch, isWithinHandTapSlop, materializeClipboard, normalizeWheelDelta, parseClipboard, portHitRadius, readPath, remapItems, resizeCursorForHandle, resolveSelectionTransform, routeBoardEdits, sameJson, selectionTransformControlAt, wheelZoomFactor, writePath };
@@ -6,9 +6,19 @@ import "../model/index.js";
6
6
  import { BoardTransformControl } from "./selection-transform.js";
7
7
  import { EditorContext } from "./context.js";
8
8
  //#region src/board/editor/interaction.d.ts
9
+ type BoardPress = {
10
+ type: "pressing";
11
+ itemId: string;
12
+ pointerId: number;
13
+ pointerType: string;
14
+ start: ScreenPoint;
15
+ world: WorldPoint;
16
+ camera: BoardViewport;
17
+ at: number;
18
+ };
9
19
  type BoardInteraction = {
10
20
  type: "idle";
11
- } | {
21
+ } | BoardPress | {
12
22
  type: "panning";
13
23
  start: ScreenPoint;
14
24
  origin: BoardViewport;
@@ -22,6 +32,10 @@ type BoardInteraction = {
22
32
  origin: Map<string, BoardFrame>;
23
33
  moved: boolean;
24
34
  duplicate: boolean;
35
+ tap?: {
36
+ itemId: string;
37
+ at: number;
38
+ };
25
39
  } | {
26
40
  type: "resizing";
27
41
  handle: ResizeHandle;
@@ -94,6 +108,11 @@ type BoardPointerEvent = {
94
108
  pressure: number;
95
109
  samples?: readonly BoardDrawInputSample[];
96
110
  };
111
+ type BoardActivation = {
112
+ itemId: string;
113
+ pointerType: string;
114
+ selected: boolean;
115
+ };
97
116
  type BoardTransformEnd = {
98
117
  ids: string[];
99
118
  operation: "move" | "resize" | "rotate" | "arrow";
@@ -117,9 +136,11 @@ declare function createInteractionModule(ctx: EditorContext): {
117
136
  pointerUp: (event: BoardPointerEvent) => void;
118
137
  pointerLeave: () => void;
119
138
  cancel: () => void;
139
+ pickUp: () => boolean;
120
140
  subscribeTransforms(listener: (event: BoardTransformEnd) => void): () => void;
141
+ subscribeActivations(listener: (event: BoardActivation) => void): () => void;
121
142
  destroy(): void;
122
143
  };
123
144
  type InteractionModule = ReturnType<typeof createInteractionModule>;
124
145
  //#endregion
125
- export { BoardInteraction, BoardPointerEvent, BoardTransformEnd, InteractionModule, createInteractionModule };
146
+ export { BoardActivation, BoardInteraction, BoardPointerEvent, BoardTransformEnd, InteractionModule, createInteractionModule };
@@ -3,7 +3,7 @@ import { resolveSceneArrow } from "../model/arrow-geometry.js";
3
3
  import { appendBoardDrawSamples } from "../model/draw-input.js";
4
4
  import { createBoardItemId } from "../model/items/id.js";
5
5
  import { connectionPortAt, connectionPorts } from "./connection-ports.js";
6
- import { canTapSelectWithHand, isCreationBoardTool, isWithinHandTapSlop } from "./tool.js";
6
+ import { boardTapSlop, canTapSelectWithHand, isCreationBoardTool, isWithinHandTapSlop } from "./tool.js";
7
7
  import { selectionTransformControlAt } from "./selection-transform.js";
8
8
  import { computeSnap } from "./snapping.js";
9
9
  import { memo } from "./state.js";
@@ -33,6 +33,15 @@ function createInteractionModule(ctx) {
33
33
  for (const listener of transformListeners) listener(event);
34
34
  }
35
35
  const { state, options, registry } = ctx;
36
+ const activationListeners = /* @__PURE__ */ new Set();
37
+ function activate(itemId, at, pointerType, selected) {
38
+ if (performance.now() - at > 300 || !state.scene.get(itemId)) return;
39
+ for (const listener of activationListeners) listener({
40
+ itemId,
41
+ pointerType,
42
+ selected
43
+ });
44
+ }
36
45
  const activePointers = /* @__PURE__ */ new Map();
37
46
  let pinch = null;
38
47
  const marquee = memo(() => [state.interaction], (interaction) => {
@@ -106,6 +115,7 @@ function createInteractionModule(ctx) {
106
115
  if (!candidateId) return null;
107
116
  const item = scene.get(candidateId);
108
117
  if (!item || ctx.query.isLocked(item) || item.type === "arrow") return null;
118
+ if (!selection.includes(item.id) && registry.activation(item) === "tap") return null;
109
119
  return registry.capabilities(item).canConnect ? item.id : null;
110
120
  }
111
121
  function visiblePorts() {
@@ -160,6 +170,71 @@ function createInteractionModule(ctx) {
160
170
  size: arrow.size
161
171
  };
162
172
  }
173
+ function onSelectedItem(event) {
174
+ if (!canTapSelectWithHand(event.pointerType)) return false;
175
+ const item = ctx.query.itemAt(event.world);
176
+ return item !== null && state.selection.includes(item.id);
177
+ }
178
+ function plainPrimary(event) {
179
+ return event.button === 0 && !event.shiftKey && !event.metaKey && !event.ctrlKey && !event.altKey;
180
+ }
181
+ function press(event) {
182
+ if (!plainPrimary(event)) return false;
183
+ const item = ctx.query.itemAt(event.world);
184
+ if (!item || state.selection.includes(item.id) || registry.activation(item) !== "tap") return false;
185
+ state.interaction = {
186
+ type: "pressing",
187
+ itemId: item.id,
188
+ pointerId: event.pointerId,
189
+ pointerType: event.pointerType,
190
+ start: event.screen,
191
+ world: event.world,
192
+ camera: { ...state.camera },
193
+ at: performance.now()
194
+ };
195
+ return true;
196
+ }
197
+ function releasePress(gesture, event) {
198
+ const dx = event.screen.x - gesture.start.x;
199
+ const dy = event.screen.y - gesture.start.y;
200
+ if (Math.hypot(dx, dy) <= boardTapSlop(gesture.pointerType)) return;
201
+ const item = state.scene.get(gesture.itemId);
202
+ if (!(!options.readonly && state.tool === "select" && item && !ctx.query.isLocked(item) && registry.capabilities(item).canMove)) {
203
+ state.interaction = {
204
+ type: "panning",
205
+ start: gesture.start,
206
+ origin: gesture.camera,
207
+ moved: true,
208
+ tapSelection: null
209
+ };
210
+ ctx.camera.setCamera(panBy(gesture.camera, dx, dy));
211
+ return;
212
+ }
213
+ const translating = {
214
+ type: "translating",
215
+ start: gesture.world,
216
+ origin: ctx.selection.framesFor([gesture.itemId]),
217
+ moved: false,
218
+ duplicate: false
219
+ };
220
+ state.interaction = translating;
221
+ translate(translating, event);
222
+ }
223
+ function pickUp() {
224
+ const gesture = state.interaction;
225
+ if (options.readonly || gesture.type !== "pressing") return false;
226
+ const item = state.scene.get(gesture.itemId);
227
+ if (!item || ctx.query.isLocked(item) || !registry.capabilities(item).canMove) return false;
228
+ state.selection = [item.id];
229
+ state.interaction = {
230
+ type: "translating",
231
+ start: state.hoverPoint ?? gesture.world,
232
+ origin: ctx.selection.framesFor([item.id]),
233
+ moved: false,
234
+ duplicate: false
235
+ };
236
+ return true;
237
+ }
163
238
  function pointerDown(event) {
164
239
  ctx.camera.cancelAnimation();
165
240
  state.hoverPoint = event.world;
@@ -173,6 +248,7 @@ function createInteractionModule(ctx) {
173
248
  }
174
249
  if (activePointers.size > 2) return;
175
250
  const additive = event.shiftKey || event.metaKey || event.ctrlKey;
251
+ if (!spaceHeld && (tool === "hand" || options.readonly) && press(event)) return;
176
252
  if (tool === "hand" || spaceHeld || event.button === 1) {
177
253
  const tapSelection = tool === "hand" && !spaceHeld && event.button !== 1 && canTapSelectWithHand(event.pointerType) ? { targetId: ctx.query.itemAt(event.world)?.id ?? null } : null;
178
254
  state.interaction = {
@@ -225,7 +301,7 @@ function createInteractionModule(ctx) {
225
301
  ctx.creation.beginTextDraft(event.world);
226
302
  return;
227
303
  }
228
- const port = connectionPortAt$1(event.world, event.pointerType);
304
+ const port = onSelectedItem(event) ? null : connectionPortAt$1(event.world, event.pointerType);
229
305
  if (port) {
230
306
  state.interaction = arrowGesture(port.point, event.world, port.itemId, port.side);
231
307
  return;
@@ -278,17 +354,23 @@ function createInteractionModule(ctx) {
278
354
  };
279
355
  return;
280
356
  }
357
+ if (press(event)) return;
281
358
  const item = ctx.query.itemAt(event.world);
282
359
  if (!item) return brush(event, additive);
283
360
  if (additive) ctx.selection.toggle(item.id);
284
361
  else if (!state.selection.includes(item.id)) state.selection = [item.id];
285
362
  const movable = ctx.selection.movable();
363
+ const tap = plainPrimary(event) && state.selection.length === 1 && registry.activation(item) === "tap" ? {
364
+ itemId: item.id,
365
+ at: performance.now()
366
+ } : void 0;
286
367
  state.interaction = movable.length === 0 ? { type: "idle" } : {
287
368
  type: "translating",
288
369
  start: event.world,
289
370
  origin: ctx.selection.framesFor(movable),
290
371
  moved: false,
291
- duplicate: event.altKey
372
+ duplicate: event.altKey,
373
+ ...tap ? { tap } : {}
292
374
  };
293
375
  }
294
376
  function pinchMove() {
@@ -472,6 +554,7 @@ function createInteractionModule(ctx) {
472
554
  ctx.camera.setCamera(panBy(interaction.origin, dx, dy));
473
555
  return;
474
556
  }
557
+ case "pressing": return releasePress(interaction, event);
475
558
  case "translating": return translate(interaction, event);
476
559
  case "resizing": return resize(interaction, event);
477
560
  case "rotating": return rotate(interaction, event);
@@ -532,11 +615,14 @@ function createInteractionModule(ctx) {
532
615
  state.selection = targetId && state.scene.get(targetId) ? [targetId] : [];
533
616
  }
534
617
  break;
618
+ case "pressing":
619
+ activate(gesture.itemId, gesture.at, gesture.pointerType, false);
620
+ break;
535
621
  case "translating":
536
622
  if (gesture.moved) {
537
- for (const [id, item] of reparentDropped(ctx.query.rootsOf(state.selection))) ctx.document.setDraftItem(id, item);
623
+ for (const [id, item] of reparentDropped([...gesture.origin.keys()])) ctx.document.setDraftItem(id, item);
538
624
  ctx.document.commit();
539
- }
625
+ } else if (gesture.tap) activate(gesture.tap.itemId, gesture.tap.at, event.pointerType, true);
540
626
  break;
541
627
  case "resizing":
542
628
  case "rotating":
@@ -610,15 +696,23 @@ function createInteractionModule(ctx) {
610
696
  pointerUp,
611
697
  pointerLeave,
612
698
  cancel,
699
+ pickUp,
613
700
  subscribeTransforms(listener) {
614
701
  transformListeners.add(listener);
615
702
  return () => {
616
703
  transformListeners.delete(listener);
617
704
  };
618
705
  },
706
+ subscribeActivations(listener) {
707
+ activationListeners.add(listener);
708
+ return () => {
709
+ activationListeners.delete(listener);
710
+ };
711
+ },
619
712
  destroy() {
620
713
  activePointers.clear();
621
714
  transformListeners.clear();
715
+ activationListeners.clear();
622
716
  }
623
717
  };
624
718
  }
@@ -18,7 +18,7 @@ declare function createQueryModule(ctx: EditorContext): {
18
18
  idsInRect: (rect: Rect, includeTemporary?: boolean) => string[];
19
19
  frameAt: (point: WorldPoint, exclude?: ReadonlySet<string>) => BoardSceneItem | null;
20
20
  itemAt: (point: WorldPoint, exclude?: ReadonlySet<string>, includeTemporary?: boolean) => BoardSceneItem | null;
21
- labelItemAt: (point: WorldPoint) => BoardSceneItem | null;
21
+ labelItemAt: (point: WorldPoint, frameLabelSize?: number) => BoardSceneItem | null;
22
22
  arrowHandleAt: (point: WorldPoint) => "start" | "end" | "mid" | null;
23
23
  connectTargetAt: (point: WorldPoint, exclude?: ReadonlySet<string>) => string | null;
24
24
  };
@@ -1,6 +1,7 @@
1
1
  import { parseBoardMediaSource } from "../../protocol/dist/board-model.js";
2
2
  import { rectContainsPoint, unionRects } from "../model/geometry.js";
3
3
  import { arrowHitRadius, arrowPathBounds, distanceToArrow, resolveSceneArrow } from "../model/arrow-geometry.js";
4
+ import { frameLabelHitRect } from "../model/text-metrics.js";
4
5
  import { createSpatialIndex } from "./spatial.js";
5
6
  //#region src/board/editor/query.ts
6
7
  const NO_IDS = /* @__PURE__ */ new Set();
@@ -139,7 +140,7 @@ function createQueryModule(ctx) {
139
140
  }
140
141
  return null;
141
142
  }
142
- function labelItemAt(point) {
143
+ function labelItemAt(point, frameLabelSize = 12) {
143
144
  ensureSpatial();
144
145
  const { scene, selection, camera } = state;
145
146
  const nearby = spatial.idsAtPoint(point);
@@ -147,12 +148,7 @@ function createQueryModule(ctx) {
147
148
  for (const id of new Set(candidates)) {
148
149
  const item = scene.get(id);
149
150
  if (!item || !state.base.items[id] || isLocked(item)) continue;
150
- if (item.type === "frame" && rectContainsPoint({
151
- x: item.frame.x,
152
- y: item.frame.y - 22,
153
- width: Math.max(48, item.frame.width * .5),
154
- height: 24
155
- }, point)) return item;
151
+ if (item.type === "frame" && rectContainsPoint(frameLabelHitRect(item.frame, frameLabelSize), point)) return item;
156
152
  if (item.type === "arrow") {
157
153
  const resolved = resolveSceneArrow(item, scene);
158
154
  const radius = Math.max(10, (item.props.fontSize ?? 14) * .9) / Math.min(1, camera.zoom);
@@ -1,6 +1,7 @@
1
1
  import { selectionBounds } from "../model/geometry.js";
2
2
  import { sceneItemToItem } from "../model/scene.js";
3
3
  import { alignFrames, distributeFrames } from "./align.js";
4
+ import { canTapSelectWithHand } from "./tool.js";
4
5
  import { resolveSelectionTransform } from "./selection-transform.js";
5
6
  import { memo } from "./state.js";
6
7
  //#region src/board/editor/selection.ts
@@ -17,7 +18,18 @@ function createSelectionModule(ctx) {
17
18
  return result;
18
19
  });
19
20
  const bounds = memo(() => [items()], (selected) => selectionBounds(selected.map((item) => item.frame)));
20
- const transform = memo(() => [items(), bounds()], (selected, rect) => resolveSelectionTransform(registry, selected, rect));
21
+ const rotatesOnTouch = ctx.options.touchRotation !== false;
22
+ const transform = memo(() => [
23
+ items(),
24
+ bounds(),
25
+ rotatesOnTouch || !canTapSelectWithHand(state.hoverPointerType)
26
+ ], (selected, rect, rotates) => {
27
+ const resolved = resolveSelectionTransform(registry, selected, rect);
28
+ return resolved && !rotates ? {
29
+ ...resolved,
30
+ canRotate: false
31
+ } : resolved;
32
+ });
21
33
  function set(ids) {
22
34
  state.selection = ids.filter((id) => Boolean(state.base.items[id]));
23
35
  }
@@ -1,10 +1,13 @@
1
1
  //#region src/board/editor/tool.d.ts
2
2
  type BoardToolId = "select" | "hand" | "text" | "shape" | "draw" | "arrow" | "frame";
3
3
  declare const BOARD_HAND_TAP_SLOP = 8;
4
+ declare const BOARD_MOUSE_TAP_SLOP = 4;
5
+ declare const BOARD_TAP_MAX_MS = 300;
6
+ declare function boardTapSlop(pointerType: string): number;
4
7
  declare function defaultBoardTool(isMobile: boolean): BoardToolId;
5
8
  declare function canTapSelectWithHand(pointerType: string): boolean;
6
9
  declare function isWithinHandTapSlop(dx: number, dy: number): boolean;
7
10
  declare function isContinuousBoardTool(tool: BoardToolId): boolean;
8
11
  declare function isCreationBoardTool(tool: BoardToolId): boolean;
9
12
  //#endregion
10
- export { BOARD_HAND_TAP_SLOP, BoardToolId, canTapSelectWithHand, defaultBoardTool, isContinuousBoardTool, isCreationBoardTool, isWithinHandTapSlop };
13
+ export { BOARD_HAND_TAP_SLOP, BOARD_MOUSE_TAP_SLOP, BOARD_TAP_MAX_MS, BoardToolId, boardTapSlop, canTapSelectWithHand, defaultBoardTool, isContinuousBoardTool, isCreationBoardTool, isWithinHandTapSlop };
@@ -1,5 +1,10 @@
1
1
  //#region src/board/editor/tool.ts
2
2
  const BOARD_HAND_TAP_SLOP = 8;
3
+ const BOARD_MOUSE_TAP_SLOP = 4;
4
+ const BOARD_TAP_MAX_MS = 300;
5
+ function boardTapSlop(pointerType) {
6
+ return pointerType === "mouse" ? 4 : 8;
7
+ }
3
8
  function defaultBoardTool(isMobile) {
4
9
  return isMobile ? "hand" : "select";
5
10
  }
@@ -16,4 +21,4 @@ function isCreationBoardTool(tool) {
16
21
  return tool === "text" || tool === "shape" || tool === "draw" || tool === "arrow" || tool === "frame";
17
22
  }
18
23
  //#endregion
19
- export { BOARD_HAND_TAP_SLOP, canTapSelectWithHand, defaultBoardTool, isContinuousBoardTool, isCreationBoardTool, isWithinHandTapSlop };
24
+ export { BOARD_HAND_TAP_SLOP, BOARD_MOUSE_TAP_SLOP, BOARD_TAP_MAX_MS, boardTapSlop, canTapSelectWithHand, defaultBoardTool, isContinuousBoardTool, isCreationBoardTool, isWithinHandTapSlop };
@@ -5,7 +5,15 @@ type BoardAssetKeySource = {
5
5
  kind: "file" | "url";
6
6
  media: BoardAssetMedia;
7
7
  value: string;
8
+ fit?: "cover";
8
9
  };
10
+ /** `src` is a Space file path or a URL; `fit: "cover"` sizes an image by its short edge. */
11
+ type BoardAssetRef = {
12
+ src: string;
13
+ media?: "image" | "video";
14
+ fit?: "contain" | "cover";
15
+ };
16
+ declare function boardAssetRefKey({ src, media, fit }: BoardAssetRef): string | null;
9
17
  declare function imageAssetKey(item: BoardSceneItem): string | null;
10
18
  declare function boardAssetKey(item: BoardSceneItem): string | null;
11
19
  declare function boardAssetKeySource(key: string): BoardAssetKeySource | null;
@@ -13,4 +21,4 @@ declare function boardTexturePixels(item: BoardSceneItem, scale: number): number
13
21
  declare const BOARD_THUMBNAIL_EDGE = 64;
14
22
  declare function boardTextureEdge(item: BoardSceneItem, scale: number): number;
15
23
  //#endregion
16
- export { BOARD_THUMBNAIL_EDGE, BoardAssetKeySource, BoardAssetMedia, boardAssetKey, boardAssetKeySource, boardTextureEdge, boardTexturePixels, imageAssetKey };
24
+ export { BOARD_THUMBNAIL_EDGE, BoardAssetKeySource, BoardAssetMedia, BoardAssetRef, boardAssetKey, boardAssetKeySource, boardAssetRefKey, boardTextureEdge, boardTexturePixels, imageAssetKey };