@ai-matrx/canvas 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/dist/controller-oY84BN2c.d.cts +345 -0
- package/dist/controller-oY84BN2c.d.ts +345 -0
- package/dist/index.d.cts +3 -344
- package/dist/index.d.ts +3 -344
- package/dist/react.d.cts +1 -278
- package/dist/react.d.ts +1 -278
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
- Build core and React declarations together so branded pane IDs, stores and controllers retain one type identity across entries.
|
|
6
|
+
- Add packed ESM and CommonJS consumer type checks joining both entry points.
|
|
7
|
+
|
|
8
|
+
### Consumer action
|
|
9
|
+
|
|
10
|
+
- Update to latest before adopting the npm package; no app API changes are required.
|
|
11
|
+
|
|
3
12
|
## 0.1.0
|
|
4
13
|
|
|
5
14
|
- Publish the existing Canvas implementation as an independently installable package with dual ESM/CommonJS entries and packaged styles.
|
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @ai-matrx/canvas — core types.
|
|
3
|
+
*
|
|
4
|
+
* The canvas is ONE docked column on the right edge of an application. It holds
|
|
5
|
+
* panes; panes hold tabs; every tab is one CanvasItem. A CanvasItem is
|
|
6
|
+
* identified by `kind` + `key`, so opening the same thing twice always lands on
|
|
7
|
+
* the existing tab instead of creating a duplicate.
|
|
8
|
+
*
|
|
9
|
+
* Everything in CanvasState is plain JSON: it lives in the host's Redux store
|
|
10
|
+
* (or the package's own standalone store), it is persisted between sessions,
|
|
11
|
+
* and it can be inspected. No functions, no class instances, no React nodes.
|
|
12
|
+
*/
|
|
13
|
+
/** Any value that survives JSON.stringify → JSON.parse unchanged. */
|
|
14
|
+
type CanvasJson = string | number | boolean | null | readonly CanvasJson[] | {
|
|
15
|
+
readonly [key: string]: CanvasJson | undefined;
|
|
16
|
+
};
|
|
17
|
+
declare const itemIdBrand: unique symbol;
|
|
18
|
+
declare const paneIdBrand: unique symbol;
|
|
19
|
+
declare const splitIdBrand: unique symbol;
|
|
20
|
+
/** `${kind}::${key}` — the identity of one thing on the canvas. */
|
|
21
|
+
type CanvasItemId = string & {
|
|
22
|
+
readonly [itemIdBrand]: true;
|
|
23
|
+
};
|
|
24
|
+
type CanvasPaneId = string & {
|
|
25
|
+
readonly [paneIdBrand]: true;
|
|
26
|
+
};
|
|
27
|
+
type CanvasSplitId = string & {
|
|
28
|
+
readonly [splitIdBrand]: true;
|
|
29
|
+
};
|
|
30
|
+
interface CanvasItem {
|
|
31
|
+
readonly id: CanvasItemId;
|
|
32
|
+
/** Registered kind id (see `registerCanvasKind`). */
|
|
33
|
+
readonly kind: string;
|
|
34
|
+
/** Stable key inside the kind — an artifact id, a conversation id, "default". */
|
|
35
|
+
readonly key: string;
|
|
36
|
+
/** Explicit tab title. `null` ⇒ the kind's `title(data)` or its label. */
|
|
37
|
+
readonly title: string | null;
|
|
38
|
+
readonly data: CanvasJson;
|
|
39
|
+
readonly openedAt: number;
|
|
40
|
+
readonly updatedAt: number;
|
|
41
|
+
}
|
|
42
|
+
interface CanvasPane {
|
|
43
|
+
readonly id: CanvasPaneId;
|
|
44
|
+
readonly itemIds: readonly CanvasItemId[];
|
|
45
|
+
readonly activeItemId: CanvasItemId | null;
|
|
46
|
+
}
|
|
47
|
+
/** "horizontal" = children side by side; "vertical" = children stacked. */
|
|
48
|
+
type CanvasOrientation = "horizontal" | "vertical";
|
|
49
|
+
type CanvasLayoutNode = {
|
|
50
|
+
readonly type: "pane";
|
|
51
|
+
readonly paneId: CanvasPaneId;
|
|
52
|
+
} | {
|
|
53
|
+
readonly type: "split";
|
|
54
|
+
readonly id: CanvasSplitId;
|
|
55
|
+
readonly orientation: CanvasOrientation;
|
|
56
|
+
readonly children: readonly CanvasLayoutNode[];
|
|
57
|
+
/** Fractions, one per child, summing to 1. */
|
|
58
|
+
readonly sizes: readonly number[];
|
|
59
|
+
};
|
|
60
|
+
interface CanvasState {
|
|
61
|
+
readonly version: 1;
|
|
62
|
+
readonly isOpen: boolean;
|
|
63
|
+
readonly isFullscreen: boolean;
|
|
64
|
+
/** Column width in CSS pixels (desktop). */
|
|
65
|
+
readonly width: number;
|
|
66
|
+
readonly layout: CanvasLayoutNode;
|
|
67
|
+
readonly panes: {
|
|
68
|
+
readonly [paneId: string]: CanvasPane;
|
|
69
|
+
};
|
|
70
|
+
readonly items: {
|
|
71
|
+
readonly [itemId: string]: CanvasItem;
|
|
72
|
+
};
|
|
73
|
+
readonly focusedPaneId: CanvasPaneId;
|
|
74
|
+
/** Monotonic counter for minting pane/split ids — keeps the reducer pure. */
|
|
75
|
+
readonly seq: number;
|
|
76
|
+
/** True once a persisted snapshot was applied (or there was none). */
|
|
77
|
+
readonly hydrated: boolean;
|
|
78
|
+
}
|
|
79
|
+
/** Where a newly opened item goes. Existing items never move on open. */
|
|
80
|
+
type CanvasOpenTarget = "focused" | "split-right" | "split-down" | {
|
|
81
|
+
readonly paneId: CanvasPaneId;
|
|
82
|
+
};
|
|
83
|
+
interface CanvasOpenInput {
|
|
84
|
+
readonly kind: string;
|
|
85
|
+
readonly key: string;
|
|
86
|
+
readonly title?: string | null | undefined;
|
|
87
|
+
readonly data?: CanvasJson | undefined;
|
|
88
|
+
readonly target?: CanvasOpenTarget | undefined;
|
|
89
|
+
/** Reveal the canvas when it is put away. Default true. */
|
|
90
|
+
readonly reveal?: boolean | undefined;
|
|
91
|
+
/** Make the item the pane's active tab. Default true. */
|
|
92
|
+
readonly activate?: boolean | undefined;
|
|
93
|
+
}
|
|
94
|
+
declare const CANVAS_MIN_WIDTH = 360;
|
|
95
|
+
declare const CANVAS_DEFAULT_WIDTH = 640;
|
|
96
|
+
declare const CANVAS_MIN_SPLIT_FRACTION = 0.12;
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* THE canvas reducer. Plain Redux-compatible reducer + action creators with no
|
|
100
|
+
* Redux Toolkit dependency, so it mounts in a host's RTK store
|
|
101
|
+
* (`canvasHost: canvasReducer`) or runs inside the package's own standalone
|
|
102
|
+
* store with identical behaviour.
|
|
103
|
+
*
|
|
104
|
+
* Identity law: an item is `kind::key`. Opening an item that is already on the
|
|
105
|
+
* canvas never duplicates it — it refreshes its data/title, activates its tab
|
|
106
|
+
* and focuses its pane, wherever that pane is.
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
declare const P = "matrxCanvas/";
|
|
110
|
+
type CanvasAction = {
|
|
111
|
+
type: `${typeof P}open`;
|
|
112
|
+
payload: CanvasOpenInput & {
|
|
113
|
+
now: number;
|
|
114
|
+
};
|
|
115
|
+
} | {
|
|
116
|
+
type: `${typeof P}update`;
|
|
117
|
+
payload: {
|
|
118
|
+
itemId: CanvasItemId;
|
|
119
|
+
data?: CanvasJson | undefined;
|
|
120
|
+
title?: string | null | undefined;
|
|
121
|
+
now: number;
|
|
122
|
+
};
|
|
123
|
+
} | {
|
|
124
|
+
type: `${typeof P}rekey`;
|
|
125
|
+
payload: {
|
|
126
|
+
itemId: CanvasItemId;
|
|
127
|
+
key: string;
|
|
128
|
+
};
|
|
129
|
+
} | {
|
|
130
|
+
type: `${typeof P}closeItem`;
|
|
131
|
+
payload: {
|
|
132
|
+
itemId: CanvasItemId;
|
|
133
|
+
};
|
|
134
|
+
} | {
|
|
135
|
+
type: `${typeof P}closeOthers`;
|
|
136
|
+
payload: {
|
|
137
|
+
itemId: CanvasItemId;
|
|
138
|
+
};
|
|
139
|
+
} | {
|
|
140
|
+
type: `${typeof P}activate`;
|
|
141
|
+
payload: {
|
|
142
|
+
itemId: CanvasItemId;
|
|
143
|
+
};
|
|
144
|
+
} | {
|
|
145
|
+
type: `${typeof P}focusPane`;
|
|
146
|
+
payload: {
|
|
147
|
+
paneId: CanvasPaneId;
|
|
148
|
+
};
|
|
149
|
+
} | {
|
|
150
|
+
type: `${typeof P}moveItem`;
|
|
151
|
+
payload: {
|
|
152
|
+
itemId: CanvasItemId;
|
|
153
|
+
toPaneId: CanvasPaneId;
|
|
154
|
+
index?: number | undefined;
|
|
155
|
+
};
|
|
156
|
+
} | {
|
|
157
|
+
type: `${typeof P}splitPane`;
|
|
158
|
+
payload: {
|
|
159
|
+
paneId: CanvasPaneId;
|
|
160
|
+
orientation: CanvasOrientation;
|
|
161
|
+
moveItemId?: CanvasItemId | undefined;
|
|
162
|
+
};
|
|
163
|
+
} | {
|
|
164
|
+
type: `${typeof P}closePane`;
|
|
165
|
+
payload: {
|
|
166
|
+
paneId: CanvasPaneId;
|
|
167
|
+
};
|
|
168
|
+
} | {
|
|
169
|
+
type: `${typeof P}resizeSplit`;
|
|
170
|
+
payload: {
|
|
171
|
+
splitId: CanvasSplitId;
|
|
172
|
+
sizes: readonly number[];
|
|
173
|
+
};
|
|
174
|
+
} | {
|
|
175
|
+
type: `${typeof P}setOpen`;
|
|
176
|
+
payload: {
|
|
177
|
+
open: boolean;
|
|
178
|
+
};
|
|
179
|
+
} | {
|
|
180
|
+
type: `${typeof P}toggle`;
|
|
181
|
+
} | {
|
|
182
|
+
type: `${typeof P}setFullscreen`;
|
|
183
|
+
payload: {
|
|
184
|
+
fullscreen: boolean;
|
|
185
|
+
};
|
|
186
|
+
} | {
|
|
187
|
+
type: `${typeof P}setWidth`;
|
|
188
|
+
payload: {
|
|
189
|
+
width: number;
|
|
190
|
+
};
|
|
191
|
+
} | {
|
|
192
|
+
type: `${typeof P}hydrate`;
|
|
193
|
+
payload: {
|
|
194
|
+
snapshot: CanvasState | null;
|
|
195
|
+
};
|
|
196
|
+
} | {
|
|
197
|
+
type: `${typeof P}reset`;
|
|
198
|
+
};
|
|
199
|
+
declare const canvasActions: {
|
|
200
|
+
readonly open: (input: CanvasOpenInput) => CanvasAction;
|
|
201
|
+
readonly update: (itemId: CanvasItemId, patch: {
|
|
202
|
+
data?: CanvasJson | undefined;
|
|
203
|
+
title?: string | null | undefined;
|
|
204
|
+
}) => CanvasAction;
|
|
205
|
+
/** Gives an item a new identity in place (e.g. a draft that was just saved and now has an id). */
|
|
206
|
+
readonly rekey: (itemId: CanvasItemId, key: string) => CanvasAction;
|
|
207
|
+
readonly closeItem: (itemId: CanvasItemId) => CanvasAction;
|
|
208
|
+
readonly closeOthers: (itemId: CanvasItemId) => CanvasAction;
|
|
209
|
+
readonly activate: (itemId: CanvasItemId) => CanvasAction;
|
|
210
|
+
readonly focusPane: (paneId: CanvasPaneId) => CanvasAction;
|
|
211
|
+
readonly moveItem: (itemId: CanvasItemId, toPaneId: CanvasPaneId, index?: number) => CanvasAction;
|
|
212
|
+
readonly splitPane: (paneId: CanvasPaneId, orientation: CanvasOrientation, moveItemId?: CanvasItemId) => CanvasAction;
|
|
213
|
+
readonly closePane: (paneId: CanvasPaneId) => CanvasAction;
|
|
214
|
+
readonly resizeSplit: (splitId: CanvasSplitId, sizes: readonly number[]) => CanvasAction;
|
|
215
|
+
readonly setOpen: (open: boolean) => CanvasAction;
|
|
216
|
+
readonly toggle: () => CanvasAction;
|
|
217
|
+
readonly setFullscreen: (fullscreen: boolean) => CanvasAction;
|
|
218
|
+
readonly setWidth: (width: number) => CanvasAction;
|
|
219
|
+
readonly hydrate: (snapshot: CanvasState | null) => CanvasAction;
|
|
220
|
+
readonly reset: () => CanvasAction;
|
|
221
|
+
};
|
|
222
|
+
declare function isCanvasAction(action: unknown): action is CanvasAction;
|
|
223
|
+
declare function createInitialCanvasState(): CanvasState;
|
|
224
|
+
/**
|
|
225
|
+
* Validates a persisted snapshot. Anything malformed is dropped back to the
|
|
226
|
+
* initial state rather than half-applied — a corrupt layout must never crash
|
|
227
|
+
* the shell.
|
|
228
|
+
*/
|
|
229
|
+
declare function sanitizeCanvasSnapshot(raw: unknown): CanvasState | null;
|
|
230
|
+
declare function canvasReducer(state: CanvasState | undefined, action: {
|
|
231
|
+
type: string;
|
|
232
|
+
}): CanvasState;
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* The store seam. The canvas never owns a second copy of its state: it reads
|
|
236
|
+
* and writes through a CanvasStoreBinding.
|
|
237
|
+
*
|
|
238
|
+
* - A host WITH Redux mounts `canvasReducer` in its root reducer and binds it:
|
|
239
|
+
* bindCanvasToReduxStore(store, (root) => root.canvasHost)
|
|
240
|
+
* - A host WITHOUT Redux (a Vite tool, an Electron window) calls
|
|
241
|
+
* createCanvasStore()
|
|
242
|
+
* which runs the very same reducer in a tiny standalone store.
|
|
243
|
+
*/
|
|
244
|
+
|
|
245
|
+
interface CanvasStoreBinding {
|
|
246
|
+
getState(): CanvasState;
|
|
247
|
+
dispatch(action: CanvasAction): void;
|
|
248
|
+
subscribe(listener: () => void): () => void;
|
|
249
|
+
}
|
|
250
|
+
declare function createCanvasStore(initial?: CanvasState): CanvasStoreBinding;
|
|
251
|
+
/** The minimum of a Redux store the canvas needs. */
|
|
252
|
+
interface ReduxStoreLike<TRoot> {
|
|
253
|
+
getState(): TRoot;
|
|
254
|
+
dispatch(action: CanvasAction): unknown;
|
|
255
|
+
subscribe(listener: () => void): () => void;
|
|
256
|
+
}
|
|
257
|
+
declare function bindCanvasToReduxStore<TRoot>(store: ReduxStoreLike<TRoot>, select: (root: TRoot) => CanvasState): CanvasStoreBinding;
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* Remembering the canvas between sessions — a table stake, never optional.
|
|
261
|
+
*
|
|
262
|
+
* The default port writes to localStorage under a versioned key. Items whose
|
|
263
|
+
* kind opts out (`restore: false`, e.g. a live session that cannot come back)
|
|
264
|
+
* are dropped from the snapshot, and panes they leave empty are dropped too.
|
|
265
|
+
*/
|
|
266
|
+
|
|
267
|
+
interface CanvasPersistencePort {
|
|
268
|
+
load(): CanvasState | null | Promise<CanvasState | null>;
|
|
269
|
+
save(snapshot: CanvasState): void | Promise<void>;
|
|
270
|
+
}
|
|
271
|
+
declare const CANVAS_STORAGE_KEY = "ai-matrx.canvas.v1";
|
|
272
|
+
declare function createLocalStorageCanvasPersistence(key?: string, storage?: Pick<Storage, "getItem" | "setItem"> | null): CanvasPersistencePort;
|
|
273
|
+
/** Builds the snapshot that is actually written: only restorable items. */
|
|
274
|
+
declare function toPersistableSnapshot(state: CanvasState, isRestorable: (kind: string) => boolean): CanvasState;
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* The canvas controller: the ONE imperative API every caller uses. It wraps a
|
|
278
|
+
* store binding, validates what callers hand it (so the reducer stays pure and
|
|
279
|
+
* trusting), and owns hydration + autosave.
|
|
280
|
+
*
|
|
281
|
+
* Nothing fails silently: a refused open (non-JSON data, unknown kind) is
|
|
282
|
+
* reported through the error sink and returns false.
|
|
283
|
+
*/
|
|
284
|
+
|
|
285
|
+
interface CanvasErrorReport {
|
|
286
|
+
readonly code: "non-json-data" | "unknown-kind" | "persistence-load" | "persistence-save";
|
|
287
|
+
readonly message: string;
|
|
288
|
+
readonly detail?: unknown;
|
|
289
|
+
}
|
|
290
|
+
type CanvasErrorSink = (report: CanvasErrorReport) => void;
|
|
291
|
+
declare const consoleCanvasErrorSink: CanvasErrorSink;
|
|
292
|
+
interface CanvasControllerOptions {
|
|
293
|
+
readonly store: CanvasStoreBinding;
|
|
294
|
+
readonly persistence?: CanvasPersistencePort | null | undefined;
|
|
295
|
+
/** Kinds that may not come back after a reload. Default: every kind restores. */
|
|
296
|
+
readonly isRestorable?: ((kind: string) => boolean) | undefined;
|
|
297
|
+
/** When given, opens of an unregistered kind are refused and reported. */
|
|
298
|
+
readonly isKnownKind?: ((kind: string) => boolean) | undefined;
|
|
299
|
+
readonly onError?: CanvasErrorSink | undefined;
|
|
300
|
+
readonly saveDelayMs?: number | undefined;
|
|
301
|
+
/**
|
|
302
|
+
* Whether a remembered "open" may be restored right now. A host returns
|
|
303
|
+
* false where an open canvas would take the whole screen (a phone).
|
|
304
|
+
*/
|
|
305
|
+
readonly mayRestoreOpen?: (() => boolean) | undefined;
|
|
306
|
+
}
|
|
307
|
+
interface CanvasController {
|
|
308
|
+
readonly store: CanvasStoreBinding;
|
|
309
|
+
getState(): CanvasState;
|
|
310
|
+
open(input: CanvasOpenInput): CanvasItemId | null;
|
|
311
|
+
update(itemId: CanvasItemId, patch: {
|
|
312
|
+
data?: CanvasJson | undefined;
|
|
313
|
+
title?: string | null | undefined;
|
|
314
|
+
}): boolean;
|
|
315
|
+
/** Gives an item a new identity in place (a draft that was saved and now has a durable id). */
|
|
316
|
+
rekey(itemId: CanvasItemId, key: string): CanvasItemId | null;
|
|
317
|
+
close(itemId: CanvasItemId): void;
|
|
318
|
+
closeOthers(itemId: CanvasItemId): void;
|
|
319
|
+
activate(itemId: CanvasItemId): void;
|
|
320
|
+
focusPane(paneId: CanvasPaneId): void;
|
|
321
|
+
moveItem(itemId: CanvasItemId, toPaneId: CanvasPaneId, index?: number): void;
|
|
322
|
+
splitPane(paneId: CanvasPaneId, orientation: CanvasOrientation, moveItemId?: CanvasItemId): void;
|
|
323
|
+
closePane(paneId: CanvasPaneId): void;
|
|
324
|
+
resizeSplit(splitId: CanvasSplitId, sizes: readonly number[]): void;
|
|
325
|
+
show(): void;
|
|
326
|
+
hide(): void;
|
|
327
|
+
toggle(): void;
|
|
328
|
+
setFullscreen(fullscreen: boolean): void;
|
|
329
|
+
setWidth(width: number): void;
|
|
330
|
+
/** Is this exact thing on the canvas right now? */
|
|
331
|
+
has(kind: string, key: string): boolean;
|
|
332
|
+
/**
|
|
333
|
+
* Is a canvas column on screen? A store can exist in a layout that shows no
|
|
334
|
+
* column (a kiosk, a meeting stage); opening there must be refused aloud.
|
|
335
|
+
*/
|
|
336
|
+
isPresented(): boolean;
|
|
337
|
+
/** Called by a column on mount; returns the unmount callback. */
|
|
338
|
+
registerPresentation(): () => void;
|
|
339
|
+
subscribePresentation(listener: () => void): () => void;
|
|
340
|
+
/** Loads the persisted snapshot and starts autosave. Returns a disposer. */
|
|
341
|
+
start(): () => void;
|
|
342
|
+
}
|
|
343
|
+
declare function createCanvasController(options: CanvasControllerOptions): CanvasController;
|
|
344
|
+
|
|
345
|
+
export { createCanvasStore as A, createInitialCanvasState as B, type CanvasItemId as C, createLocalStorageCanvasPersistence as D, isCanvasAction as E, sanitizeCanvasSnapshot as F, toPersistableSnapshot as G, type ReduxStoreLike as R, type CanvasJson as a, type CanvasLayoutNode as b, type CanvasPaneId as c, type CanvasState as d, type CanvasItem as e, type CanvasPane as f, CANVAS_DEFAULT_WIDTH as g, CANVAS_MIN_SPLIT_FRACTION as h, CANVAS_MIN_WIDTH as i, CANVAS_STORAGE_KEY as j, type CanvasAction as k, type CanvasController as l, type CanvasControllerOptions as m, type CanvasErrorReport as n, type CanvasErrorSink as o, type CanvasOpenInput as p, type CanvasOpenTarget as q, type CanvasOrientation as r, type CanvasPersistencePort as s, type CanvasSplitId as t, type CanvasStoreBinding as u, bindCanvasToReduxStore as v, canvasActions as w, canvasReducer as x, consoleCanvasErrorSink as y, createCanvasController as z };
|