@lupinum/board-core 0.1.0 → 1.0.0-beta.2

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.
@@ -1,9 +1,12 @@
1
1
  export type { InternalBoardCommit, InternalHistoryRoot } from './state/types.js';
2
+ export { readonlyMapView } from './helpers/clone.js';
3
+ export { collectJsonObjectExtras, freezeJsonObject, freezeJsonValue, } from './helpers/json.js';
4
+ export { BOARD_EDITOR_ATTRIBUTE, BOARD_INTERACTIVE_ATTRIBUTE, BOARD_INTERACTIVE_SELECTOR, BOARD_NODE_ID_ATTRIBUTE, BOARD_RESIZE_ATTRIBUTE, BOARD_ROOT_ATTRIBUTE, BOARD_ROOT_SELECTOR, isBoardInteractiveEventTarget, isEventOwnedByBoardRoot, } from './dom-attributes.js';
2
5
  export type { InternalBoardPlugin, InternalBoardPluginDefinition, InternalInteractionAdapter, InternalPluginContext, InternalPluginPersistence, } from './types.js';
3
6
  /** Resolve the unsupported first-party pointer/session surface used by framework adapters. */
4
7
  export declare function getBoardInteractionAdapter(engine: import('./types.js').BoardEngine): InternalInteractionAdapter;
5
8
  import type { BoardEventMap, BoardPlugin, BoardPluginApis, InternalBoardPlugin, InternalBoardPluginDefinition, InternalInteractionAdapter } from './types.js';
6
9
  export declare function defineInternalBoardPlugin<TPluginApis extends BoardPluginApis = BoardPluginApis, TEvents extends {
7
10
  [K in keyof TEvents]: (...args: never[]) => unknown;
8
- } = BoardEventMap>(plugin: InternalBoardPluginDefinition<TPluginApis, TEvents>): InternalBoardPlugin<TPluginApis, TEvents>;
11
+ } = BoardEventMap, TState = unknown>(plugin: InternalBoardPluginDefinition<TPluginApis, TEvents, TState>): InternalBoardPlugin<TPluginApis, TEvents, TState>;
9
12
  export declare function assertInternalBoardPlugin(plugin: BoardPlugin): asserts plugin is InternalBoardPlugin;
package/dist/internal.js CHANGED
@@ -1,10 +1,36 @@
1
1
  import {
2
+ BOARD_EDITOR_ATTRIBUTE,
3
+ BOARD_INTERACTIVE_ATTRIBUTE,
4
+ BOARD_INTERACTIVE_SELECTOR,
5
+ BOARD_NODE_ID_ATTRIBUTE,
6
+ BOARD_RESIZE_ATTRIBUTE,
7
+ BOARD_ROOT_ATTRIBUTE,
8
+ BOARD_ROOT_SELECTOR,
2
9
  assertInternalBoardPlugin,
10
+ collectJsonObjectExtras,
3
11
  defineInternalBoardPlugin,
4
- getBoardInteractionAdapter
5
- } from "./chunk-5JZXHZWR.js";
12
+ freezeJsonObject,
13
+ freezeJsonValue,
14
+ getBoardInteractionAdapter,
15
+ isBoardInteractiveEventTarget,
16
+ isEventOwnedByBoardRoot,
17
+ readonlyMapView
18
+ } from "./chunk-A7SXOVDB.js";
6
19
  export {
20
+ BOARD_EDITOR_ATTRIBUTE,
21
+ BOARD_INTERACTIVE_ATTRIBUTE,
22
+ BOARD_INTERACTIVE_SELECTOR,
23
+ BOARD_NODE_ID_ATTRIBUTE,
24
+ BOARD_RESIZE_ATTRIBUTE,
25
+ BOARD_ROOT_ATTRIBUTE,
26
+ BOARD_ROOT_SELECTOR,
7
27
  assertInternalBoardPlugin,
28
+ collectJsonObjectExtras,
8
29
  defineInternalBoardPlugin,
9
- getBoardInteractionAdapter
30
+ freezeJsonObject,
31
+ freezeJsonValue,
32
+ getBoardInteractionAdapter,
33
+ isBoardInteractiveEventTarget,
34
+ isEventOwnedByBoardRoot,
35
+ readonlyMapView
10
36
  };
@@ -1,4 +1,8 @@
1
- import type { BoardEventMap, BoardNode, Camera, CommandMetadata, GridSettings, InteractionState, NodeConstraints, NodeId, Point, SnapGuide, ZoomSettings } from '../types.js';
1
+ import type { BoardEventMap, BoardNode, Camera, CommandMetadata, GridSettings, InteractionState, JsonObject, NodeConstraints, NodeId, Point, SnapGuide, ZoomSettings } from '../types.js';
2
+ export interface JsonCanvasPassthrough {
3
+ readonly document: JsonObject;
4
+ readonly nodes: ReadonlyMap<NodeId, JsonObject>;
5
+ }
2
6
  export declare const DEFAULT_CAMERA: Camera;
3
7
  export declare const DEFAULT_ZOOM: ZoomSettings;
4
8
  export declare const DEFAULT_GRID: GridSettings;
@@ -11,6 +15,7 @@ export interface MutableBoardState {
11
15
  interaction: InteractionState;
12
16
  snapGuides: SnapGuide[];
13
17
  nextZIndex: number;
18
+ jsonCanvas: JsonCanvasPassthrough;
14
19
  }
15
20
  export type ListenerMap = Map<keyof BoardEventMap, Set<(...args: unknown[]) => void>>;
16
21
  /** Structurally shared persistent state captured by first-party history. */
@@ -19,6 +24,7 @@ export interface InternalHistoryRoot {
19
24
  readonly grid: GridSettings;
20
25
  readonly selection: ReadonlySet<NodeId>;
21
26
  readonly nextZIndex: number;
27
+ readonly jsonCanvas: JsonCanvasPassthrough;
22
28
  readonly pluginSlices: ReadonlyMap<string, unknown>;
23
29
  }
24
30
  /** A successfully published outer command. */
package/dist/types.d.ts CHANGED
@@ -74,6 +74,14 @@ export type JsonCanvasBackgroundStyle = 'cover' | 'ratio' | 'repeat';
74
74
  export type BoardColorPreset = '1' | '2' | '3' | '4' | '5' | '6';
75
75
  /** JSON Canvas color value: a preset id or a concrete hex color. */
76
76
  export type CanvasColor = BoardColorPreset | `#${string}`;
77
+ /** A value that can be persisted without changing its JSON representation. */
78
+ export type JsonValue = null | boolean | number | string | readonly JsonValue[] | {
79
+ readonly [key: string]: JsonValue;
80
+ };
81
+ /** A JSON object used for extension-owned persisted data. */
82
+ export type JsonObject = {
83
+ readonly [key: string]: JsonValue;
84
+ };
77
85
  interface JsonCanvasNodeBase<TType extends JsonCanvasNodeType> {
78
86
  readonly id: string;
79
87
  readonly type: TType;
@@ -112,30 +120,30 @@ export interface JsonCanvasEdge {
112
120
  readonly color?: CanvasColor;
113
121
  readonly label?: string;
114
122
  }
115
- export interface VueBoardNodeMetadata {
123
+ export interface BoardNodeMetadata {
116
124
  readonly zIndex?: number;
117
125
  readonly locked?: boolean;
118
126
  readonly visible?: boolean;
119
127
  readonly parentId?: string;
120
128
  }
121
- export interface VueBoardEdgeMetadata {
129
+ export interface BoardEdgeMetadata {
122
130
  readonly zIndex?: number;
123
- readonly data?: Record<string, unknown>;
131
+ readonly data?: JsonObject;
124
132
  }
125
133
  /** Nuxt Board metadata for engine state that JSON Canvas 1.0 does not define. */
126
- export interface VueBoardDocumentMetadata {
134
+ export interface BoardDocumentMetadata {
127
135
  readonly camera?: Camera;
128
136
  readonly grid?: GridSettings;
129
137
  readonly selection?: readonly string[];
130
138
  readonly nextZIndex?: number;
131
- readonly nodes?: Readonly<Record<string, VueBoardNodeMetadata>>;
132
- readonly edges?: Readonly<Record<string, VueBoardEdgeMetadata>>;
139
+ readonly nodes?: Readonly<Record<string, BoardNodeMetadata>>;
140
+ readonly edges?: Readonly<Record<string, BoardEdgeMetadata>>;
133
141
  }
134
142
  /** Canonical persisted board document. */
135
143
  export interface JsonCanvasDocument {
136
144
  readonly nodes: readonly JsonCanvasNode[];
137
145
  readonly edges?: readonly JsonCanvasEdge[];
138
- readonly 'x-vue-board'?: VueBoardDocumentMetadata;
146
+ readonly 'x-lupinum-board'?: BoardDocumentMetadata;
139
147
  }
140
148
  interface BoardNodeBase {
141
149
  readonly id: NodeId;
@@ -260,6 +268,10 @@ export interface DuplicateNodesResult {
260
268
  readonly nodes: readonly BoardNode[];
261
269
  readonly idMap: ReadonlyMap<NodeId, NodeId>;
262
270
  }
271
+ /** Clipboard hook used to translate host payloads into board nodes. */
272
+ export interface BoardClipboardHooks {
273
+ deserialize(payload: unknown): NodeInput[] | null;
274
+ }
263
275
  /** Options controlling how a validated document enters the current board. */
264
276
  export interface DocumentLoadOptions {
265
277
  mode?: 'replace' | 'merge';
@@ -373,6 +385,7 @@ export interface BoardEngineOptions<TPlugins extends readonly BoardPlugin[] = re
373
385
  nodes?: Partial<NodeConstraints>;
374
386
  boxSelect?: Partial<BoxSelectSettings>;
375
387
  plugins?: TPlugins;
388
+ clipboard?: BoardClipboardHooks;
376
389
  diagnostics?: boolean | {
377
390
  traceLimit?: number;
378
391
  };
@@ -491,6 +504,8 @@ export interface BoardEngine<TPluginApis extends BoardPluginApis = BoardPluginAp
491
504
  duplicateNodes(ids: NodeId[], offset?: Point): DuplicateNodesResult;
492
505
  copySelected(): BoardNode[];
493
506
  pasteClipboard(offset?: Point): BoardNode[];
507
+ /** Paste a host payload, or return `null` when the hook does not recognize it. */
508
+ pasteData(payload: unknown, offset?: Point): BoardNode[] | null;
494
509
  select(ids: NodeId | NodeId[], mode?: SelectionMode): void;
495
510
  selectAll(): void;
496
511
  clearSelection(): void;
@@ -523,7 +538,7 @@ export interface InternalInteractionAdapter {
523
538
  */
524
539
  export interface InternalPluginContext<TPluginApis extends BoardPluginApis = BoardPluginApis, TEvents extends {
525
540
  [K in keyof TEvents]: (...args: never[]) => unknown;
526
- } = BoardEventMap> extends Omit<BoardEngine, 'plugins'> {
541
+ } = BoardEventMap, TState = unknown> extends Omit<BoardEngine, 'plugins'> {
527
542
  readonly plugins: TPluginApis;
528
543
  /** Assert that the owning engine has not been destroyed. */
529
544
  assertActive(): void;
@@ -539,38 +554,40 @@ export interface InternalPluginContext<TPluginApis extends BoardPluginApis = Boa
539
554
  */
540
555
  runCommand<T>(name: string, args: unknown[], fn: () => T, metadata: CommandMetadata): T;
541
556
  /** Read the immutable persistent slice owned by the current plugin. */
542
- getPluginState<S>(): S;
557
+ getPluginState(): TState;
543
558
  /** Replace the current plugin's persistent slice inside the active command. */
544
- updatePluginState<S>(update: (current: S) => S): S;
559
+ updatePluginState(update: (current: TState) => TState): TState;
560
+ /** Project persistent plugin state through the canonical subscribable lifecycle. */
561
+ createCommitSubscribable<T>(select: () => T, channel: string): Subscribable<T>;
545
562
  /** Prepare final bookkeeping/event publication for a validated outer commit. The effect cannot mutate or destroy the board. */
546
563
  projectCommit(projector: (commit: import('./state/types.js').InternalBoardCommit) => () => void): Unsubscribe;
547
564
  /** Atomically restore a persistent root without recording another history frame. */
548
565
  restoreHistoryRoot(root: import('./state/types.js').InternalHistoryRoot): void;
549
566
  }
550
567
  /** Persistent state owned by an internal plugin. */
551
- interface InternalPluginSlice {
552
- initial: unknown;
568
+ interface InternalPluginSlice<TState> {
569
+ initial: TState;
553
570
  }
554
571
  /** Optional internal hook for persisted JSON Canvas document data. */
555
572
  export interface InternalPluginPersistence<TPluginApis extends BoardPluginApis = BoardPluginApis, TEvents extends {
556
573
  [K in keyof TEvents]: (...args: never[]) => unknown;
557
- } = BoardEventMap> {
558
- exportDocument?(engine: InternalPluginContext<TPluginApis, TEvents>): Partial<JsonCanvasDocument> | void;
559
- loadDocument?(engine: InternalPluginContext<TPluginApis, TEvents>, document: JsonCanvasDocument, mode: 'replace' | 'merge', idMap: ReadonlyMap<NodeId, NodeId>): void;
574
+ } = BoardEventMap, TState = unknown> {
575
+ exportDocument?(engine: InternalPluginContext<TPluginApis, TEvents, TState>): Partial<JsonCanvasDocument> | void;
576
+ loadDocument?(engine: InternalPluginContext<TPluginApis, TEvents, TState>, document: JsonCanvasDocument, mode: 'replace' | 'merge', idMap: ReadonlyMap<NodeId, NodeId>): void;
560
577
  }
561
578
  /** Internal plugin contract for state, commands, and side effects. */
562
579
  export interface InternalBoardPlugin<TPluginApis extends BoardPluginApis = BoardPluginApis, TEvents extends {
563
580
  [K in keyof TEvents]: (...args: never[]) => unknown;
564
- } = BoardEventMap> extends BoardPlugin<TPluginApis, TEvents> {
581
+ } = BoardEventMap, TState = unknown> extends BoardPlugin<TPluginApis, TEvents> {
565
582
  name: string;
566
- slice?: InternalPluginSlice;
567
- persistence?: InternalPluginPersistence<TPluginApis, TEvents>;
568
- nodeDeleted?(engine: InternalPluginContext<TPluginApis, TEvents>, nodeId: NodeId): void;
569
- install(engine: InternalPluginContext<TPluginApis, TEvents>, options?: Record<string, unknown>): void | PluginCleanup;
583
+ slice?: InternalPluginSlice<TState>;
584
+ persistence?: InternalPluginPersistence<TPluginApis, TEvents, TState>;
585
+ nodeDeleted?(engine: InternalPluginContext<TPluginApis, TEvents, TState>, nodeId: NodeId): void;
586
+ install(engine: InternalPluginContext<TPluginApis, TEvents, TState>, options?: Record<string, unknown>): void | PluginCleanup;
570
587
  }
571
588
  export type InternalBoardPluginDefinition<TPluginApis extends BoardPluginApis = BoardPluginApis, TEvents extends {
572
589
  [K in keyof TEvents]: (...args: never[]) => unknown;
573
- } = BoardEventMap> = Omit<InternalBoardPlugin<TPluginApis, TEvents>, keyof BoardPlugin> & {
590
+ } = BoardEventMap, TState = unknown> = Omit<InternalBoardPlugin<TPluginApis, TEvents, TState>, keyof BoardPlugin> & {
574
591
  readonly name: string;
575
592
  };
576
593
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lupinum/board-core",
3
- "version": "0.1.0",
3
+ "version": "1.0.0-beta.2",
4
4
  "description": "Headless node-based board engine for spatial editors, diagramming tools, and whiteboard-style interfaces.",
5
5
  "license": "MIT",
6
6
  "author": "Lupinum OG <info@lupinum.com> (https://lupinum.com)",
@@ -1,68 +0,0 @@
1
- // src/errors.ts
2
- var BoardError = class extends Error {
3
- constructor(message) {
4
- super(message);
5
- this.name = new.target.name;
6
- }
7
- };
8
- var BoardInputError = class extends BoardError {
9
- };
10
- var BoardNotFoundError = class extends BoardError {
11
- };
12
- var BoardConflictError = class extends BoardError {
13
- };
14
- var BoardDestroyedError = class extends BoardError {
15
- constructor() {
16
- super("Board engine has been destroyed.");
17
- }
18
- };
19
-
20
- // src/engine/interaction-adapter.ts
21
- var adapterKey = /* @__PURE__ */ Symbol.for("@lupinum/board-core/interaction-adapter");
22
- function registerBoardInteractionAdapter(engine, adapter) {
23
- Object.defineProperty(engine, adapterKey, {
24
- configurable: true,
25
- value: adapter,
26
- enumerable: false
27
- });
28
- }
29
- function getRegisteredBoardInteractionAdapter(engine) {
30
- const adapter = Reflect.get(engine, adapterKey);
31
- if (!adapter) {
32
- throw new BoardInputError(
33
- "The supplied engine was not created by createBoardEngine()."
34
- );
35
- }
36
- return adapter;
37
- }
38
-
39
- // src/internal.ts
40
- function getBoardInteractionAdapter(engine) {
41
- return getRegisteredBoardInteractionAdapter(engine);
42
- }
43
- function defineInternalBoardPlugin(plugin) {
44
- return plugin;
45
- }
46
- function assertInternalBoardPlugin(plugin) {
47
- const candidate = plugin;
48
- if (typeof candidate.name !== "string" || candidate.name.length === 0) {
49
- throw new Error("Invalid board plugin: expected a named plugin token.");
50
- }
51
- if (typeof candidate.install !== "function") {
52
- throw new Error(
53
- `Invalid board plugin "${candidate.name}": expected a token created by a first-party plugin factory.`
54
- );
55
- }
56
- }
57
-
58
- export {
59
- BoardError,
60
- BoardInputError,
61
- BoardNotFoundError,
62
- BoardConflictError,
63
- BoardDestroyedError,
64
- registerBoardInteractionAdapter,
65
- getBoardInteractionAdapter,
66
- defineInternalBoardPlugin,
67
- assertInternalBoardPlugin
68
- };