@calca/canvas-base 0.7.3

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/README.md ADDED
@@ -0,0 +1,35 @@
1
+ # @calca/canvas-base
2
+
3
+ Zero-dependency, runtime-agnostic document core for the Calca canvas. Web Standards only (ESM + `EventTarget`) — runs on Node ≥20.19, Bun, Deno, and workers. Contains no React or `@xyflow/*`; the React Flow projection lives in `@calca/canvas-flow`.
4
+
5
+ ## Model
6
+
7
+ - **Records** — `ShapeRecord` (id, type, `parentId`, fractional `index`, `props`, `meta`) and `BindingRecord` (`fromId`/`toId`), held in flat maps on `CanvasStore`.
8
+ - **Ordering** — `keys.ts` fractional indices (`generateKeyBetween`, `generateNKeysBetween`) for stable, O(1) reordering under user/agent/remote writes.
9
+ - **Commands** — `create-shape` | `create-binding` | `update` | `delete` | `reparent`. All mutations go through `store.apply(commands, { origin, history })`, which computes inverse commands, updates history, and emits a `change` event (`CustomEvent<CanvasChange>`) with `{ origin, commands, inverse, added, updated, removed }`.
10
+ - **History** — `transact(fn)` groups `apply` calls into one undo entry; `history: false` for remote/derived changes. `undo()` / `redo()`.
11
+ - **Persistence** — `snapshot()` / `load()` on a versioned `CanvasSnapshot`; ordered `Migration`s run on load and future versions are rejected.
12
+ - **Types** — `ShapeDef<P>` carries an optional [Standard Schema](https://standardschema.dev) `schema` (type-only dev dependency; bring your own zod/valibot/arktype) plus `defaultProps`. Validation runs inside `apply`.
13
+
14
+ ```ts
15
+ import { createCanvasStore, generateKeyBetween } from "@calca/canvas-base";
16
+
17
+ const store = createCanvasStore();
18
+ store.addEventListener("change", (e) => console.log(e.detail));
19
+
20
+ store.apply(
21
+ [
22
+ {
23
+ op: "create-shape",
24
+ record: {
25
+ id: "frame-1",
26
+ type: "frame",
27
+ parentId: null,
28
+ index: generateKeyBetween(null, null),
29
+ props: { x: 0, y: 0, w: 640, h: 480 },
30
+ },
31
+ },
32
+ ],
33
+ { origin: "agent" },
34
+ );
35
+ ```
@@ -0,0 +1,254 @@
1
+ //#region src/records.d.ts
2
+ type RecordId = string;
3
+ interface Point {
4
+ x: number;
5
+ y: number;
6
+ }
7
+ interface Bounds {
8
+ x: number;
9
+ y: number;
10
+ w: number;
11
+ h: number;
12
+ }
13
+ interface ShapeRecord<P = Record<string, unknown>> {
14
+ id: RecordId;
15
+ /** Shape type registered via ShapeDef (e.g. "frame", "image", "note"). */
16
+ type: string;
17
+ /** Parent frame/group id, or null for root-level shapes. */
18
+ parentId: RecordId | null;
19
+ /** Fractional index ordering siblings within a parent. */
20
+ index: string;
21
+ /** Shape-specific payload (position, size, content refs…). */
22
+ props: P;
23
+ meta?: Record<string, unknown>;
24
+ }
25
+ interface BindingRecord<P = Record<string, unknown>> {
26
+ id: RecordId;
27
+ /** Binding type (e.g. "arrow", "edge"). */
28
+ type: string;
29
+ fromId: RecordId;
30
+ toId: RecordId;
31
+ index: string;
32
+ props: P;
33
+ meta?: Record<string, unknown>;
34
+ }
35
+ type CanvasRecord = ShapeRecord | BindingRecord;
36
+ export declare const isShapeRecord: (record: CanvasRecord) => record is ShapeRecord;
37
+ export declare const isBindingRecord: (record: CanvasRecord) => record is BindingRecord;
38
+ //#endregion
39
+ //#region src/commands.d.ts
40
+ interface CreateShapeCommand {
41
+ op: "create-shape";
42
+ record: ShapeRecord;
43
+ }
44
+ interface CreateBindingCommand {
45
+ op: "create-binding";
46
+ record: BindingRecord;
47
+ }
48
+ interface UpdateRecordCommand {
49
+ op: "update";
50
+ id: RecordId;
51
+ /** Shallow-merged into the record's props. */
52
+ props: Record<string, unknown>;
53
+ }
54
+ interface DeleteRecordCommand {
55
+ op: "delete";
56
+ id: RecordId;
57
+ }
58
+ interface ReparentCommand {
59
+ op: "reparent";
60
+ id: RecordId;
61
+ parentId: RecordId | null;
62
+ /** New sibling index; when omitted a key after the last sibling is generated. */
63
+ index?: string;
64
+ }
65
+ type Command = CreateShapeCommand | CreateBindingCommand | UpdateRecordCommand | DeleteRecordCommand | ReparentCommand;
66
+ export declare const isCommand: (value: unknown) => value is Command;
67
+ export declare const commandTargets: (command: Command) => RecordId;
68
+ export declare const createdRecord: (command: Command) => CanvasRecord | null;
69
+ //#endregion
70
+ //#region src/migrations.d.ts
71
+ interface Migration {
72
+ version: number;
73
+ up(snapshot: CanvasSnapshot): CanvasSnapshot;
74
+ }
75
+ /**
76
+ * Applies pending migrations in ascending version order. A snapshot whose
77
+ * `version` is ahead of the newest known migration is rejected rather than
78
+ * silently truncated.
79
+ */
80
+ export declare const migrate: (snapshot: CanvasSnapshot, migrations: Migration[]) => CanvasSnapshot;
81
+ //#endregion
82
+ //#region ../../node_modules/.bun/@standard-schema+spec@1.1.0/node_modules/@standard-schema/spec/dist/index.d.ts
83
+ /** The Standard Typed interface. This is a base type extended by other specs. */
84
+ interface StandardTypedV1<Input = unknown, Output = Input> {
85
+ /** The Standard properties. */
86
+ readonly "~standard": StandardTypedV1.Props<Input, Output>;
87
+ }
88
+ declare namespace StandardTypedV1 {
89
+ /** The Standard Typed properties interface. */
90
+ interface Props<Input = unknown, Output = Input> {
91
+ /** The version number of the standard. */
92
+ readonly version: 1;
93
+ /** The vendor name of the schema library. */
94
+ readonly vendor: string;
95
+ /** Inferred types associated with the schema. */
96
+ readonly types?: Types<Input, Output> | undefined;
97
+ }
98
+ /** The Standard Typed types interface. */
99
+ interface Types<Input = unknown, Output = Input> {
100
+ /** The input type of the schema. */
101
+ readonly input: Input;
102
+ /** The output type of the schema. */
103
+ readonly output: Output;
104
+ }
105
+ /** Infers the input type of a Standard Typed. */
106
+ type InferInput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["input"];
107
+ /** Infers the output type of a Standard Typed. */
108
+ type InferOutput<Schema extends StandardTypedV1> = NonNullable<Schema["~standard"]["types"]>["output"];
109
+ }
110
+ /** The Standard Schema interface. */
111
+ interface StandardSchemaV1<Input = unknown, Output = Input> {
112
+ /** The Standard Schema properties. */
113
+ readonly "~standard": StandardSchemaV1.Props<Input, Output>;
114
+ }
115
+ declare namespace StandardSchemaV1 {
116
+ /** The Standard Schema properties interface. */
117
+ interface Props<Input = unknown, Output = Input> extends StandardTypedV1.Props<Input, Output> {
118
+ /** Validates unknown input values. */
119
+ readonly validate: (value: unknown, options?: StandardSchemaV1.Options | undefined) => Result<Output> | Promise<Result<Output>>;
120
+ }
121
+ /** The result interface of the validate function. */
122
+ type Result<Output> = SuccessResult<Output> | FailureResult;
123
+ /** The result interface if validation succeeds. */
124
+ interface SuccessResult<Output> {
125
+ /** The typed output value. */
126
+ readonly value: Output;
127
+ /** A falsy value for `issues` indicates success. */
128
+ readonly issues?: undefined;
129
+ }
130
+ interface Options {
131
+ /** Explicit support for additional vendor-specific parameters, if needed. */
132
+ readonly libraryOptions?: Record<string, unknown> | undefined;
133
+ }
134
+ /** The result interface if validation fails. */
135
+ interface FailureResult {
136
+ /** The issues of failed validation. */
137
+ readonly issues: ReadonlyArray<Issue>;
138
+ }
139
+ /** The issue interface of the failure output. */
140
+ interface Issue {
141
+ /** The error message of the issue. */
142
+ readonly message: string;
143
+ /** The path of the issue, if any. */
144
+ readonly path?: ReadonlyArray<PropertyKey | PathSegment> | undefined;
145
+ }
146
+ /** The path segment interface of the issue. */
147
+ interface PathSegment {
148
+ /** The key representing a path segment. */
149
+ readonly key: PropertyKey;
150
+ }
151
+ /** The Standard types interface. */
152
+ interface Types<Input = unknown, Output = Input> extends StandardTypedV1.Types<Input, Output> {}
153
+ /** Infers the input type of a Standard. */
154
+ type InferInput<Schema extends StandardTypedV1> = StandardTypedV1.InferInput<Schema>;
155
+ /** Infers the output type of a Standard. */
156
+ type InferOutput<Schema extends StandardTypedV1> = StandardTypedV1.InferOutput<Schema>;
157
+ }
158
+ //#endregion
159
+ //#region src/shapes.d.ts
160
+ /**
161
+ * Shape-type definition. `schema` is a type-only Standard Schema slot —
162
+ * validation runs through `~standard.validate`, so this package stays
163
+ * dependency-free while any schema library (zod, valibot, arktype) can
164
+ * supply the contract.
165
+ */
166
+ interface ShapeDef<P = Record<string, unknown>> {
167
+ type: string;
168
+ schema?: StandardSchemaV1<P, P>;
169
+ defaultProps?: () => P;
170
+ }
171
+ interface ShapeRegistry {
172
+ register(def: ShapeDef): void;
173
+ get(type: string): ShapeDef | undefined;
174
+ has(type: string): boolean;
175
+ }
176
+ export declare const createShapeRegistry: (defs?: ShapeDef[]) => ShapeRegistry;
177
+ //#endregion
178
+ //#region src/store.d.ts
179
+ type ChangeOrigin = "user" | "agent" | "remote";
180
+ interface ApplyOptions {
181
+ origin: ChangeOrigin;
182
+ /** Record an undo entry (default true; remote sync applies pass history:false). */
183
+ history?: boolean;
184
+ }
185
+ interface CanvasChange {
186
+ origin: ChangeOrigin;
187
+ commands: readonly Command[];
188
+ /** Commands that restore the previous state — applied by undo(). */
189
+ inverse: readonly Command[];
190
+ added: readonly RecordId[];
191
+ updated: readonly RecordId[];
192
+ removed: readonly RecordId[];
193
+ }
194
+ export declare const CANVAS_CHANGE_EVENT = "change";
195
+ interface CanvasSnapshot {
196
+ version: number;
197
+ shapes: ShapeRecord[];
198
+ bindings: BindingRecord[];
199
+ }
200
+ export declare const SNAPSHOT_VERSION = 1;
201
+ interface CanvasStoreOptions {
202
+ defs?: ShapeDef[];
203
+ migrations?: Migration[];
204
+ snapshot?: CanvasSnapshot;
205
+ }
206
+ export declare class CanvasStore extends EventTarget {
207
+ #private;
208
+ constructor(options?: CanvasStoreOptions);
209
+ get registry(): ShapeRegistry;
210
+ get(id: RecordId): CanvasRecord | undefined;
211
+ getShape(id: RecordId): ShapeRecord | undefined;
212
+ all(): CanvasRecord[];
213
+ shapes(): ShapeRecord[];
214
+ bindings(): BindingRecord[];
215
+ /** Direct children of a parent (null = root), in index order. */
216
+ children(parentId: RecordId | null): ShapeRecord[];
217
+ /** Index key for a new shape appended at the end of a parent's children. */
218
+ nextIndex(parentId: RecordId | null): string;
219
+ get canUndo(): boolean;
220
+ get canRedo(): boolean;
221
+ apply(commands: readonly Command[], options: ApplyOptions): CanvasChange;
222
+ /** Batch several applies into a single undo entry. */
223
+ transact(fn: () => void): void;
224
+ undo(): void;
225
+ redo(): void;
226
+ snapshot(): CanvasSnapshot;
227
+ load(snapshot: CanvasSnapshot): void;
228
+ }
229
+ export declare const createCanvasStore: (options?: CanvasStoreOptions) => CanvasStore;
230
+ //#endregion
231
+ //#region src/keys.d.ts
232
+ export declare const isValidIndex: (key: string) => boolean;
233
+ /** Key strictly between `a` and `b`; either may be `null` (list edge). */
234
+ export declare const generateKeyBetween: (a: string | null, b: string | null) => string;
235
+ export declare const generateNKeysBetween: (a: string | null, b: string | null, n: number) => string[];
236
+ //#endregion
237
+ //#region src/geometry.d.ts
238
+ export declare const boundsContains: (outer: Bounds, inner: Bounds) => boolean;
239
+ export declare const boundsOverlap: (a: Bounds, b: Bounds) => boolean;
240
+ export declare const pointInBounds: (point: Point, bounds: Bounds) => boolean;
241
+ export declare const unionBounds: (bounds: readonly Bounds[]) => Bounds | null;
242
+ export declare const centerOf: (bounds: Bounds) => Point;
243
+ export declare const translate: (bounds: Bounds, delta: Point) => Bounds;
244
+ interface Camera {
245
+ x: number;
246
+ y: number;
247
+ /** Zoom scale; 1 = 100%. */
248
+ z: number;
249
+ }
250
+ export declare const screenToCanvas: (point: Point, camera: Camera) => Point;
251
+ export declare const canvasToScreen: (point: Point, camera: Camera) => Point;
252
+ //#endregion
253
+ export type { ApplyOptions, BindingRecord, Bounds, Camera, CanvasChange, CanvasRecord, CanvasSnapshot, CanvasStoreOptions, ChangeOrigin, Command, CreateBindingCommand, CreateShapeCommand, DeleteRecordCommand, Migration, Point, RecordId, ReparentCommand, ShapeDef, ShapeRecord, ShapeRegistry, UpdateRecordCommand };
254
+ //# sourceMappingURL=index.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/records.ts","../src/commands.ts","../src/migrations.ts","../../../node_modules/.bun/@standard-schema+spec@1.1.0/node_modules/@standard-schema/spec/dist/index.d.ts","../src/shapes.ts","../src/store.ts","../src/keys.ts","../src/geometry.ts"],"ignoreList":[3],"mappings":"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"}
package/dist/index.mjs ADDED
@@ -0,0 +1,359 @@
1
+ //#region src/commands.ts
2
+ const isCommand = (value) => typeof value === "object" && value !== null && "op" in value && [
3
+ "create-shape",
4
+ "create-binding",
5
+ "update",
6
+ "delete",
7
+ "reparent"
8
+ ].includes(value.op);
9
+ const commandTargets = (command) => {
10
+ switch (command.op) {
11
+ case "create-shape":
12
+ case "create-binding": return command.record.id;
13
+ default: return command.id;
14
+ }
15
+ };
16
+ const commandCreates = (command) => command.op === "create-shape" || command.op === "create-binding";
17
+ const createdRecord = (command) => commandCreates(command) ? command.record : null;
18
+
19
+ //#endregion
20
+ //#region src/keys.ts
21
+ /**
22
+ * Ordered index keys: a base-62 "fraction" string between two implicit
23
+ * endpoints "" (the very start) and a top sentinel. Lexicographic string
24
+ * comparison defines record order, so keys may only be compared as strings.
25
+ *
26
+ * Invariant: a key never ends in the smallest digit ("0") — otherwise
27
+ * `"a"` and `"a0"` would be numerically equal but compare differently.
28
+ */
29
+ const DIGITS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
30
+ const isValidIndex = (key) => key.length > 0 && !key.endsWith(DIGITS[0]) && [...key].every((c) => DIGITS.includes(c));
31
+ /** Smallest index strictly between `a` and `b` ("" = unbounded side). */
32
+ const midpoint = (a, b) => {
33
+ let i = 0;
34
+ while (i < a.length && i < b.length && a[i] === b[i]) i++;
35
+ const digA = i < a.length ? DIGITS.indexOf(a[i]) : 0;
36
+ const digB = i < b.length ? DIGITS.indexOf(b[i]) : 62;
37
+ if (digB - digA > 1) return a.slice(0, i) + DIGITS[Math.floor((digA + digB) / 2)];
38
+ const takeFromA = i < a.length;
39
+ const headChar = takeFromA ? a[i] : DIGITS[0];
40
+ const aTail = takeFromA ? a.slice(i + 1) : "";
41
+ const bTail = takeFromA ? "" : b.slice(i + 1);
42
+ return a.slice(0, i) + headChar + midpoint(aTail, bTail);
43
+ };
44
+ /** Key strictly between `a` and `b`; either may be `null` (list edge). */
45
+ const generateKeyBetween = (a, b) => {
46
+ if (a !== null && !isValidIndex(a)) throw new Error(`invalid index key: ${a}`);
47
+ if (b !== null && !isValidIndex(b)) throw new Error(`invalid index key: ${b}`);
48
+ if (a !== null && b !== null && a >= b) throw new Error("generateKeyBetween: a must be < b");
49
+ return midpoint(a ?? "", b ?? "");
50
+ };
51
+ const generateNKeysBetween = (a, b, n) => {
52
+ if (n === 0) return [];
53
+ const mid = generateKeyBetween(a, b);
54
+ const lo = generateNKeysBetween(a, mid, Math.floor(n / 2));
55
+ const hi = generateNKeysBetween(mid, b, n - Math.floor(n / 2) - 1);
56
+ return [
57
+ ...lo,
58
+ mid,
59
+ ...hi
60
+ ];
61
+ };
62
+
63
+ //#endregion
64
+ //#region src/migrations.ts
65
+ /**
66
+ * Applies pending migrations in ascending version order. A snapshot whose
67
+ * `version` is ahead of the newest known migration is rejected rather than
68
+ * silently truncated.
69
+ */
70
+ const migrate = (snapshot, migrations) => {
71
+ const ordered = [...migrations].sort((a, b) => a.version - b.version);
72
+ const latest = ordered.at(-1)?.version ?? snapshot.version;
73
+ if (snapshot.version > latest) throw new Error(`snapshot version ${snapshot.version} is newer than supported version ${latest}`);
74
+ return ordered.filter((m) => m.version > snapshot.version).reduce((snap, m) => m.up(snap), snapshot);
75
+ };
76
+
77
+ //#endregion
78
+ //#region src/records.ts
79
+ const isShapeRecord = (record) => "parentId" in record;
80
+ const isBindingRecord = (record) => "fromId" in record;
81
+
82
+ //#endregion
83
+ //#region src/shapes.ts
84
+ const createShapeRegistry = (defs = []) => {
85
+ const map = new Map(defs.map((d) => [d.type, d]));
86
+ return {
87
+ register: (def) => map.set(def.type, def),
88
+ get: (type) => map.get(type),
89
+ has: (type) => map.has(type)
90
+ };
91
+ };
92
+ const validateProps = (registry, type, props) => {
93
+ const schema = registry.get(type)?.schema;
94
+ if (!schema) return;
95
+ const result = schema["~standard"].validate(props);
96
+ if (result instanceof Promise) throw new Error(`shape "${type}": async schemas are not supported in canvas records`);
97
+ if (result.issues) {
98
+ const detail = result.issues.map((i) => i.message).join("; ");
99
+ throw new Error(`shape "${type}" props failed schema validation: ${detail}`);
100
+ }
101
+ };
102
+
103
+ //#endregion
104
+ //#region src/store.ts
105
+ const CANVAS_CHANGE_EVENT = "change";
106
+ const SNAPSHOT_VERSION = 1;
107
+ const inverseOf = (command, previous) => {
108
+ switch (command.op) {
109
+ case "create-shape": return [{
110
+ op: "delete",
111
+ id: command.record.id
112
+ }];
113
+ case "create-binding": return [{
114
+ op: "delete",
115
+ id: command.record.id
116
+ }];
117
+ case "delete":
118
+ if (!previous) return [];
119
+ return [isShapeRecord(previous) ? {
120
+ op: "create-shape",
121
+ record: previous
122
+ } : {
123
+ op: "create-binding",
124
+ record: previous
125
+ }];
126
+ case "update": {
127
+ if (!previous) return [];
128
+ const prior = {};
129
+ for (const key of Object.keys(command.props)) prior[key] = previous.props[key];
130
+ return [{
131
+ op: "update",
132
+ id: command.id,
133
+ props: prior
134
+ }];
135
+ }
136
+ case "reparent":
137
+ if (!previous || !isShapeRecord(previous)) return [];
138
+ return [{
139
+ op: "reparent",
140
+ id: command.id,
141
+ parentId: previous.parentId,
142
+ index: previous.index
143
+ }];
144
+ }
145
+ };
146
+ var CanvasStore = class extends EventTarget {
147
+ #shapes = /* @__PURE__ */ new Map();
148
+ #bindings = /* @__PURE__ */ new Map();
149
+ #undo = [];
150
+ #redo = [];
151
+ /** In-flight transaction's collected inverses; null = not transacting. */
152
+ #transaction = null;
153
+ #registry;
154
+ #migrations;
155
+ constructor(options = {}) {
156
+ super();
157
+ this.#registry = createShapeRegistry(options.defs ?? []);
158
+ this.#migrations = options.migrations ?? [];
159
+ if (options.snapshot) this.load(options.snapshot);
160
+ }
161
+ get registry() {
162
+ return this.#registry;
163
+ }
164
+ get(id) {
165
+ return this.#shapes.get(id) ?? this.#bindings.get(id);
166
+ }
167
+ getShape(id) {
168
+ return this.#shapes.get(id);
169
+ }
170
+ all() {
171
+ return [...this.#shapes.values(), ...this.#bindings.values()];
172
+ }
173
+ shapes() {
174
+ return [...this.#shapes.values()];
175
+ }
176
+ bindings() {
177
+ return [...this.#bindings.values()];
178
+ }
179
+ /** Direct children of a parent (null = root), in index order. */
180
+ children(parentId) {
181
+ return this.shapes().filter((s) => s.parentId === parentId).sort((a, b) => a.index < b.index ? -1 : a.index > b.index ? 1 : 0);
182
+ }
183
+ /** Index key for a new shape appended at the end of a parent's children. */
184
+ nextIndex(parentId) {
185
+ const last = this.children(parentId).at(-1);
186
+ return generateKeyBetween(last?.index ?? null, null);
187
+ }
188
+ get canUndo() {
189
+ return this.#undo.length > 0;
190
+ }
191
+ get canRedo() {
192
+ return this.#redo.length > 0;
193
+ }
194
+ apply(commands, options) {
195
+ const inverse = [];
196
+ const added = [];
197
+ const updated = [];
198
+ const removed = [];
199
+ for (const command of commands) {
200
+ if (!isCommand(command)) throw new Error(`not a command: ${JSON.stringify(command)}`);
201
+ const previous = this.get(commandTargetOf(command)) ?? null;
202
+ inverse.unshift(...inverseOf(command, previous));
203
+ this.#execute(command, previous);
204
+ if (command.op === "create-shape" || command.op === "create-binding") added.push(command.record.id);
205
+ else if (command.op === "delete") removed.push(command.id);
206
+ else updated.push(command.id);
207
+ }
208
+ if (options.history ?? true) {
209
+ if (this.#transaction) this.#transaction.push(inverse);
210
+ else this.#undo.push(inverse);
211
+ this.#redo = [];
212
+ }
213
+ const change = {
214
+ origin: options.origin,
215
+ commands,
216
+ inverse,
217
+ added,
218
+ updated,
219
+ removed
220
+ };
221
+ this.dispatchEvent(new CustomEvent(CANVAS_CHANGE_EVENT, { detail: change }));
222
+ return change;
223
+ }
224
+ /** Batch several applies into a single undo entry. */
225
+ transact(fn) {
226
+ if (this.#transaction) {
227
+ fn();
228
+ return;
229
+ }
230
+ this.#transaction = [];
231
+ try {
232
+ fn();
233
+ const batches = this.#transaction;
234
+ if (batches.length > 0) this.#undo.push(batches.flat());
235
+ } finally {
236
+ this.#transaction = null;
237
+ }
238
+ }
239
+ undo() {
240
+ const entry = this.#undo.pop();
241
+ if (!entry) return;
242
+ const change = this.apply(entry, {
243
+ origin: "user",
244
+ history: false
245
+ });
246
+ this.#redo.push([...change.inverse]);
247
+ }
248
+ redo() {
249
+ const entry = this.#redo.pop();
250
+ if (!entry) return;
251
+ const change = this.apply(entry, {
252
+ origin: "user",
253
+ history: false
254
+ });
255
+ this.#undo.push([...change.inverse]);
256
+ }
257
+ snapshot() {
258
+ return structuredClone({
259
+ version: 1,
260
+ shapes: this.shapes(),
261
+ bindings: this.bindings()
262
+ });
263
+ }
264
+ load(snapshot) {
265
+ const migrated = migrate(snapshot, this.#migrations);
266
+ this.#shapes.clear();
267
+ this.#bindings.clear();
268
+ this.#undo = [];
269
+ this.#redo = [];
270
+ for (const shape of migrated.shapes) this.#shapes.set(shape.id, shape);
271
+ for (const binding of migrated.bindings) this.#bindings.set(binding.id, binding);
272
+ this.dispatchEvent(new CustomEvent(CANVAS_CHANGE_EVENT, { detail: {
273
+ origin: "remote",
274
+ commands: [],
275
+ inverse: [],
276
+ added: this.all().map((r) => r.id),
277
+ updated: [],
278
+ removed: []
279
+ } }));
280
+ }
281
+ #execute(command, previous) {
282
+ switch (command.op) {
283
+ case "create-shape":
284
+ if (previous) throw new Error(`record ${command.record.id} already exists`);
285
+ validateProps(this.#registry, command.record.type, command.record.props);
286
+ this.#shapes.set(command.record.id, command.record);
287
+ return;
288
+ case "create-binding":
289
+ if (previous) throw new Error(`record ${command.record.id} already exists`);
290
+ validateProps(this.#registry, command.record.type, command.record.props);
291
+ this.#bindings.set(command.record.id, command.record);
292
+ return;
293
+ case "update":
294
+ if (!previous) throw new Error(`record ${command.id} does not exist`);
295
+ validateProps(this.#registry, previous.type, {
296
+ ...previous.props,
297
+ ...command.props
298
+ });
299
+ previous.props = {
300
+ ...previous.props,
301
+ ...command.props
302
+ };
303
+ return;
304
+ case "delete":
305
+ if (!previous) throw new Error(`record ${command.id} does not exist`);
306
+ if (isShapeRecord(previous)) this.#shapes.delete(command.id);
307
+ else this.#bindings.delete(command.id);
308
+ return;
309
+ case "reparent":
310
+ if (!previous || !isShapeRecord(previous)) throw new Error(`shape ${command.id} does not exist`);
311
+ if (command.parentId && !this.#shapes.has(command.parentId)) throw new Error(`parent shape ${command.parentId} does not exist`);
312
+ previous.parentId = command.parentId;
313
+ previous.index = command.index ?? this.nextIndex(command.parentId);
314
+ return;
315
+ }
316
+ }
317
+ };
318
+ const commandTargetOf = (command) => command.op === "create-shape" || command.op === "create-binding" ? command.record.id : command.id;
319
+ const createCanvasStore = (options = {}) => new CanvasStore(options);
320
+
321
+ //#endregion
322
+ //#region src/geometry.ts
323
+ const boundsContains = (outer, inner) => inner.x >= outer.x && inner.y >= outer.y && inner.x + inner.w <= outer.x + outer.w && inner.y + inner.h <= outer.y + outer.h;
324
+ const boundsOverlap = (a, b) => a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;
325
+ const pointInBounds = (point, bounds) => point.x >= bounds.x && point.x <= bounds.x + bounds.w && point.y >= bounds.y && point.y <= bounds.y + bounds.h;
326
+ const unionBounds = (bounds) => {
327
+ if (bounds.length === 0) return null;
328
+ const x = Math.min(...bounds.map((b) => b.x));
329
+ const y = Math.min(...bounds.map((b) => b.y));
330
+ const right = Math.max(...bounds.map((b) => b.x + b.w));
331
+ const bottom = Math.max(...bounds.map((b) => b.y + b.h));
332
+ return {
333
+ x,
334
+ y,
335
+ w: right - x,
336
+ h: bottom - y
337
+ };
338
+ };
339
+ const centerOf = (bounds) => ({
340
+ x: bounds.x + bounds.w / 2,
341
+ y: bounds.y + bounds.h / 2
342
+ });
343
+ const translate = (bounds, delta) => ({
344
+ ...bounds,
345
+ x: bounds.x + delta.x,
346
+ y: bounds.y + delta.y
347
+ });
348
+ const screenToCanvas = (point, camera) => ({
349
+ x: point.x / camera.z - camera.x,
350
+ y: point.y / camera.z - camera.y
351
+ });
352
+ const canvasToScreen = (point, camera) => ({
353
+ x: (point.x + camera.x) * camera.z,
354
+ y: (point.y + camera.y) * camera.z
355
+ });
356
+
357
+ //#endregion
358
+ export { CANVAS_CHANGE_EVENT, CanvasStore, SNAPSHOT_VERSION, boundsContains, boundsOverlap, canvasToScreen, centerOf, commandTargets, createCanvasStore, createShapeRegistry, createdRecord, generateKeyBetween, generateNKeysBetween, isBindingRecord, isCommand, isShapeRecord, isValidIndex, migrate, pointInBounds, screenToCanvas, translate, unionBounds };
359
+ //# sourceMappingURL=index.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.mjs","names":[],"sources":["../src/commands.ts","../src/keys.ts","../src/migrations.ts","../src/records.ts","../src/shapes.ts","../src/store.ts","../src/geometry.ts"],"sourcesContent":["import type { BindingRecord, CanvasRecord, RecordId, ShapeRecord } from \"./records\";\n\nexport interface CreateShapeCommand {\n op: \"create-shape\";\n record: ShapeRecord;\n}\n\nexport interface CreateBindingCommand {\n op: \"create-binding\";\n record: BindingRecord;\n}\n\nexport interface UpdateRecordCommand {\n op: \"update\";\n id: RecordId;\n /** Shallow-merged into the record's props. */\n props: Record<string, unknown>;\n}\n\nexport interface DeleteRecordCommand {\n op: \"delete\";\n id: RecordId;\n}\n\nexport interface ReparentCommand {\n op: \"reparent\";\n id: RecordId;\n parentId: RecordId | null;\n /** New sibling index; when omitted a key after the last sibling is generated. */\n index?: string;\n}\n\nexport type Command =\n | CreateShapeCommand\n | CreateBindingCommand\n | UpdateRecordCommand\n | DeleteRecordCommand\n | ReparentCommand;\n\nexport const isCommand = (value: unknown): value is Command =>\n typeof value === \"object\" &&\n value !== null &&\n \"op\" in value &&\n [\"create-shape\", \"create-binding\", \"update\", \"delete\", \"reparent\"].includes(\n (value as Command).op,\n );\n\nexport const commandTargets = (command: Command): RecordId => {\n switch (command.op) {\n case \"create-shape\":\n case \"create-binding\":\n return command.record.id;\n default:\n return command.id;\n }\n};\n\nexport const commandCreates = (\n command: Command,\n): command is CreateShapeCommand | CreateBindingCommand =>\n command.op === \"create-shape\" || command.op === \"create-binding\";\n\nexport const createdRecord = (command: Command): CanvasRecord | null =>\n commandCreates(command) ? command.record : null;\n","/**\n * Ordered index keys: a base-62 \"fraction\" string between two implicit\n * endpoints \"\" (the very start) and a top sentinel. Lexicographic string\n * comparison defines record order, so keys may only be compared as strings.\n *\n * Invariant: a key never ends in the smallest digit (\"0\") — otherwise\n * `\"a\"` and `\"a0\"` would be numerically equal but compare differently.\n */\nconst DIGITS = \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n\nexport const isValidIndex = (key: string): boolean =>\n key.length > 0 && !key.endsWith(DIGITS[0]) && [...key].every((c) => DIGITS.includes(c));\n\n/** Smallest index strictly between `a` and `b` (\"\" = unbounded side). */\nconst midpoint = (a: string, b: string): string => {\n let i = 0;\n while (i < a.length && i < b.length && a[i] === b[i]) i++;\n const digA = i < a.length ? DIGITS.indexOf(a[i]!) : 0;\n const digB = i < b.length ? DIGITS.indexOf(b[i]!) : DIGITS.length;\n if (digB - digA > 1) {\n return a.slice(0, i) + DIGITS[Math.floor((digA + digB) / 2)];\n }\n // Adjacent digits: extend on whichever side keeps us inside (a, b).\n // Taking a[i] leaves the tail unbounded above; an exhausted `a` means an\n // implied smallest-digit, so we descend into b's tail.\n const takeFromA = i < a.length;\n const headChar = takeFromA ? a[i] : DIGITS[0];\n const aTail = takeFromA ? a.slice(i + 1) : \"\";\n const bTail = takeFromA ? \"\" : b.slice(i + 1);\n return a.slice(0, i) + headChar + midpoint(aTail, bTail);\n};\n\n/** Key strictly between `a` and `b`; either may be `null` (list edge). */\nexport const generateKeyBetween = (a: string | null, b: string | null): string => {\n if (a !== null && !isValidIndex(a)) throw new Error(`invalid index key: ${a}`);\n if (b !== null && !isValidIndex(b)) throw new Error(`invalid index key: ${b}`);\n if (a !== null && b !== null && a >= b) throw new Error(\"generateKeyBetween: a must be < b\");\n return midpoint(a ?? \"\", b ?? \"\");\n};\n\nexport const generateNKeysBetween = (a: string | null, b: string | null, n: number): string[] => {\n if (n === 0) return [];\n const mid = generateKeyBetween(a, b);\n const lo = generateNKeysBetween(a, mid, Math.floor(n / 2));\n const hi = generateNKeysBetween(mid, b, n - Math.floor(n / 2) - 1);\n return [...lo, mid, ...hi];\n};\n","import type { CanvasSnapshot } from \"./store\";\n\nexport interface Migration {\n version: number;\n up(snapshot: CanvasSnapshot): CanvasSnapshot;\n}\n\n/**\n * Applies pending migrations in ascending version order. A snapshot whose\n * `version` is ahead of the newest known migration is rejected rather than\n * silently truncated.\n */\nexport const migrate = (snapshot: CanvasSnapshot, migrations: Migration[]): CanvasSnapshot => {\n const ordered = [...migrations].sort((a, b) => a.version - b.version);\n const latest = ordered.at(-1)?.version ?? snapshot.version;\n if (snapshot.version > latest) {\n throw new Error(\n `snapshot version ${snapshot.version} is newer than supported version ${latest}`,\n );\n }\n return ordered\n .filter((m) => m.version > snapshot.version)\n .reduce((snap, m) => m.up(snap), snapshot);\n};\n","export type RecordId = string;\n\nexport interface Point {\n x: number;\n y: number;\n}\n\nexport interface Bounds {\n x: number;\n y: number;\n w: number;\n h: number;\n}\n\nexport interface ShapeRecord<P = Record<string, unknown>> {\n id: RecordId;\n /** Shape type registered via ShapeDef (e.g. \"frame\", \"image\", \"note\"). */\n type: string;\n /** Parent frame/group id, or null for root-level shapes. */\n parentId: RecordId | null;\n /** Fractional index ordering siblings within a parent. */\n index: string;\n /** Shape-specific payload (position, size, content refs…). */\n props: P;\n meta?: Record<string, unknown>;\n}\n\nexport interface BindingRecord<P = Record<string, unknown>> {\n id: RecordId;\n /** Binding type (e.g. \"arrow\", \"edge\"). */\n type: string;\n fromId: RecordId;\n toId: RecordId;\n index: string;\n props: P;\n meta?: Record<string, unknown>;\n}\n\nexport type CanvasRecord = ShapeRecord | BindingRecord;\n\nexport const isShapeRecord = (record: CanvasRecord): record is ShapeRecord => \"parentId\" in record;\n\nexport const isBindingRecord = (record: CanvasRecord): record is BindingRecord =>\n \"fromId\" in record;\n","import type { StandardSchemaV1 } from \"@standard-schema/spec\";\n\n/**\n * Shape-type definition. `schema` is a type-only Standard Schema slot —\n * validation runs through `~standard.validate`, so this package stays\n * dependency-free while any schema library (zod, valibot, arktype) can\n * supply the contract.\n */\nexport interface ShapeDef<P = Record<string, unknown>> {\n type: string;\n schema?: StandardSchemaV1<P, P>;\n defaultProps?: () => P;\n}\n\nexport interface ShapeRegistry {\n register(def: ShapeDef): void;\n get(type: string): ShapeDef | undefined;\n has(type: string): boolean;\n}\n\nexport const createShapeRegistry = (defs: ShapeDef[] = []): ShapeRegistry => {\n const map = new Map<string, ShapeDef>(defs.map((d) => [d.type, d]));\n return {\n register: (def) => map.set(def.type, def),\n get: (type) => map.get(type),\n has: (type) => map.has(type),\n };\n};\n\nexport const validateProps = (registry: ShapeRegistry, type: string, props: unknown): void => {\n const schema = registry.get(type)?.schema;\n if (!schema) return;\n const result = schema[\"~standard\"].validate(props);\n if (result instanceof Promise) {\n throw new Error(`shape \"${type}\": async schemas are not supported in canvas records`);\n }\n if (result.issues) {\n const detail = result.issues.map((i) => i.message).join(\"; \");\n throw new Error(`shape \"${type}\" props failed schema validation: ${detail}`);\n }\n};\n","import { isCommand, type Command } from \"./commands\";\nimport { generateKeyBetween } from \"./keys\";\nimport { migrate, type Migration } from \"./migrations\";\nimport {\n isBindingRecord,\n isShapeRecord,\n type BindingRecord,\n type CanvasRecord,\n type RecordId,\n type ShapeRecord,\n} from \"./records\";\nimport { createShapeRegistry, validateProps, type ShapeDef, type ShapeRegistry } from \"./shapes\";\n\nexport type ChangeOrigin = \"user\" | \"agent\" | \"remote\";\n\nexport interface ApplyOptions {\n origin: ChangeOrigin;\n /** Record an undo entry (default true; remote sync applies pass history:false). */\n history?: boolean;\n}\n\nexport interface CanvasChange {\n origin: ChangeOrigin;\n commands: readonly Command[];\n /** Commands that restore the previous state — applied by undo(). */\n inverse: readonly Command[];\n added: readonly RecordId[];\n updated: readonly RecordId[];\n removed: readonly RecordId[];\n}\n\nexport const CANVAS_CHANGE_EVENT = \"change\";\n\nexport interface CanvasSnapshot {\n version: number;\n shapes: ShapeRecord[];\n bindings: BindingRecord[];\n}\n\nexport const SNAPSHOT_VERSION = 1;\n\nexport interface CanvasStoreOptions {\n defs?: ShapeDef[];\n migrations?: Migration[];\n snapshot?: CanvasSnapshot;\n}\n\nconst inverseOf = (command: Command, previous: CanvasRecord | null): Command[] => {\n switch (command.op) {\n case \"create-shape\":\n return [{ op: \"delete\", id: command.record.id }];\n case \"create-binding\":\n return [{ op: \"delete\", id: command.record.id }];\n case \"delete\":\n if (!previous) return [];\n return [\n isShapeRecord(previous)\n ? { op: \"create-shape\", record: previous }\n : { op: \"create-binding\", record: previous as BindingRecord },\n ];\n case \"update\": {\n if (!previous) return [];\n const prior: Record<string, unknown> = {};\n for (const key of Object.keys(command.props)) {\n prior[key] = previous.props[key];\n }\n return [{ op: \"update\", id: command.id, props: prior }];\n }\n case \"reparent\":\n if (!previous || !isShapeRecord(previous)) return [];\n return [\n {\n op: \"reparent\",\n id: command.id,\n parentId: previous.parentId,\n index: previous.index,\n },\n ];\n }\n};\n\nexport class CanvasStore extends EventTarget {\n #shapes = new Map<RecordId, ShapeRecord>();\n #bindings = new Map<RecordId, BindingRecord>();\n #undo: Command[][] = [];\n #redo: Command[][] = [];\n /** In-flight transaction's collected inverses; null = not transacting. */\n #transaction: Command[][] | null = null;\n #registry: ShapeRegistry;\n #migrations: Migration[];\n\n constructor(options: CanvasStoreOptions = {}) {\n super();\n this.#registry = createShapeRegistry(options.defs ?? []);\n this.#migrations = options.migrations ?? [];\n if (options.snapshot) this.load(options.snapshot);\n }\n\n get registry(): ShapeRegistry {\n return this.#registry;\n }\n\n get(id: RecordId): CanvasRecord | undefined {\n return this.#shapes.get(id) ?? this.#bindings.get(id);\n }\n\n getShape(id: RecordId): ShapeRecord | undefined {\n return this.#shapes.get(id);\n }\n\n all(): CanvasRecord[] {\n return [...this.#shapes.values(), ...this.#bindings.values()];\n }\n\n shapes(): ShapeRecord[] {\n return [...this.#shapes.values()];\n }\n\n bindings(): BindingRecord[] {\n return [...this.#bindings.values()];\n }\n\n /** Direct children of a parent (null = root), in index order. */\n children(parentId: RecordId | null): ShapeRecord[] {\n return this.shapes()\n .filter((s) => s.parentId === parentId)\n .sort((a, b) => (a.index < b.index ? -1 : a.index > b.index ? 1 : 0));\n }\n\n /** Index key for a new shape appended at the end of a parent's children. */\n nextIndex(parentId: RecordId | null): string {\n const last = this.children(parentId).at(-1);\n return generateKeyBetween(last?.index ?? null, null);\n }\n\n get canUndo(): boolean {\n return this.#undo.length > 0;\n }\n\n get canRedo(): boolean {\n return this.#redo.length > 0;\n }\n\n apply(commands: readonly Command[], options: ApplyOptions): CanvasChange {\n const inverse: Command[] = [];\n const added: RecordId[] = [];\n const updated: RecordId[] = [];\n const removed: RecordId[] = [];\n\n for (const command of commands) {\n if (!isCommand(command)) throw new Error(`not a command: ${JSON.stringify(command)}`);\n const previous = this.get(commandTargetOf(command)) ?? null;\n inverse.unshift(...inverseOf(command, previous));\n this.#execute(command, previous);\n if (command.op === \"create-shape\" || command.op === \"create-binding\") {\n added.push(command.record.id);\n } else if (command.op === \"delete\") {\n removed.push(command.id);\n } else {\n updated.push(command.id);\n }\n }\n\n if (options.history ?? true) {\n if (this.#transaction) {\n this.#transaction.push(inverse);\n } else {\n this.#undo.push(inverse);\n }\n this.#redo = [];\n }\n\n const change: CanvasChange = {\n origin: options.origin,\n commands,\n inverse,\n added,\n updated,\n removed,\n };\n this.dispatchEvent(new CustomEvent<CanvasChange>(CANVAS_CHANGE_EVENT, { detail: change }));\n return change;\n }\n\n /** Batch several applies into a single undo entry. */\n transact(fn: () => void): void {\n if (this.#transaction) {\n fn();\n return;\n }\n this.#transaction = [];\n try {\n fn();\n const batches = this.#transaction;\n if (batches.length > 0) {\n this.#undo.push(batches.flat());\n }\n } finally {\n this.#transaction = null;\n }\n }\n\n undo(): void {\n const entry = this.#undo.pop();\n if (!entry) return;\n const change = this.apply(entry, { origin: \"user\", history: false });\n this.#redo.push([...change.inverse]);\n }\n\n redo(): void {\n const entry = this.#redo.pop();\n if (!entry) return;\n const change = this.apply(entry, { origin: \"user\", history: false });\n this.#undo.push([...change.inverse]);\n }\n\n snapshot(): CanvasSnapshot {\n return structuredClone({\n version: SNAPSHOT_VERSION,\n shapes: this.shapes(),\n bindings: this.bindings(),\n });\n }\n\n load(snapshot: CanvasSnapshot): void {\n const migrated = migrate(snapshot, this.#migrations);\n this.#shapes.clear();\n this.#bindings.clear();\n this.#undo = [];\n this.#redo = [];\n for (const shape of migrated.shapes) this.#shapes.set(shape.id, shape);\n for (const binding of migrated.bindings) this.#bindings.set(binding.id, binding);\n this.dispatchEvent(\n new CustomEvent<CanvasChange>(CANVAS_CHANGE_EVENT, {\n detail: {\n origin: \"remote\",\n commands: [],\n inverse: [],\n added: this.all().map((r) => r.id),\n updated: [],\n removed: [],\n } satisfies CanvasChange,\n }),\n );\n }\n\n #execute(command: Command, previous: CanvasRecord | null): void {\n switch (command.op) {\n case \"create-shape\": {\n if (previous) throw new Error(`record ${command.record.id} already exists`);\n validateProps(this.#registry, command.record.type, command.record.props);\n this.#shapes.set(command.record.id, command.record);\n return;\n }\n case \"create-binding\": {\n if (previous) throw new Error(`record ${command.record.id} already exists`);\n validateProps(this.#registry, command.record.type, command.record.props);\n this.#bindings.set(command.record.id, command.record);\n return;\n }\n case \"update\": {\n if (!previous) throw new Error(`record ${command.id} does not exist`);\n validateProps(this.#registry, previous.type, {\n ...previous.props,\n ...command.props,\n });\n previous.props = { ...previous.props, ...command.props };\n return;\n }\n case \"delete\": {\n if (!previous) throw new Error(`record ${command.id} does not exist`);\n if (isShapeRecord(previous)) this.#shapes.delete(command.id);\n else this.#bindings.delete(command.id);\n return;\n }\n case \"reparent\": {\n if (!previous || !isShapeRecord(previous)) {\n throw new Error(`shape ${command.id} does not exist`);\n }\n if (command.parentId && !this.#shapes.has(command.parentId)) {\n throw new Error(`parent shape ${command.parentId} does not exist`);\n }\n previous.parentId = command.parentId;\n previous.index = command.index ?? this.nextIndex(command.parentId);\n return;\n }\n }\n }\n}\n\nconst commandTargetOf = (command: Command): RecordId =>\n command.op === \"create-shape\" || command.op === \"create-binding\" ? command.record.id : command.id;\n\nexport const createCanvasStore = (options: CanvasStoreOptions = {}): CanvasStore =>\n new CanvasStore(options);\n","import type { Bounds, Point } from \"./records\";\n\nexport const boundsContains = (outer: Bounds, inner: Bounds): boolean =>\n inner.x >= outer.x &&\n inner.y >= outer.y &&\n inner.x + inner.w <= outer.x + outer.w &&\n inner.y + inner.h <= outer.y + outer.h;\n\nexport const boundsOverlap = (a: Bounds, b: Bounds): boolean =>\n a.x < b.x + b.w && b.x < a.x + a.w && a.y < b.y + b.h && b.y < a.y + a.h;\n\nexport const pointInBounds = (point: Point, bounds: Bounds): boolean =>\n point.x >= bounds.x &&\n point.x <= bounds.x + bounds.w &&\n point.y >= bounds.y &&\n point.y <= bounds.y + bounds.h;\n\nexport const unionBounds = (bounds: readonly Bounds[]): Bounds | null => {\n if (bounds.length === 0) return null;\n const x = Math.min(...bounds.map((b) => b.x));\n const y = Math.min(...bounds.map((b) => b.y));\n const right = Math.max(...bounds.map((b) => b.x + b.w));\n const bottom = Math.max(...bounds.map((b) => b.y + b.h));\n return { x, y, w: right - x, h: bottom - y };\n};\n\nexport const centerOf = (bounds: Bounds): Point => ({\n x: bounds.x + bounds.w / 2,\n y: bounds.y + bounds.h / 2,\n});\n\nexport const translate = (bounds: Bounds, delta: Point): Bounds => ({\n ...bounds,\n x: bounds.x + delta.x,\n y: bounds.y + delta.y,\n});\n\nexport interface Camera {\n x: number;\n y: number;\n /** Zoom scale; 1 = 100%. */\n z: number;\n}\n\nexport const screenToCanvas = (point: Point, camera: Camera): Point => ({\n x: point.x / camera.z - camera.x,\n y: point.y / camera.z - camera.y,\n});\n\nexport const canvasToScreen = (point: Point, camera: Camera): Point => ({\n x: (point.x + camera.x) * camera.z,\n y: (point.y + camera.y) * camera.z,\n});\n"],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,39 @@
1
+ {
2
+ "name": "@calca/canvas-base",
3
+ "version": "0.7.3",
4
+ "private": false,
5
+ "license": "Apache-2.0",
6
+ "files": [
7
+ "dist"
8
+ ],
9
+ "type": "module",
10
+ "sideEffects": false,
11
+ "exports": {
12
+ ".": {
13
+ "types": "./dist/index.d.mts",
14
+ "import": "./dist/index.mjs"
15
+ }
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "scripts": {
21
+ "build": "tsdown",
22
+ "typecheck": "bunx tsc --noEmit",
23
+ "lint": "oxlint --fix .",
24
+ "format": "oxfmt --write .",
25
+ "test": "vitest run",
26
+ "clean": "rm -rf dist",
27
+ "validate": "bun ../../scripts/validate.ts --cwd $INIT_CWD",
28
+ "check:pack": "publint && attw --pack . --profile esm-only"
29
+ },
30
+ "dependencies": {},
31
+ "devDependencies": {
32
+ "@arethetypeswrong/cli": "^0.18.0",
33
+ "@standard-schema/spec": "^1.0.0",
34
+ "publint": "^0.3.0",
35
+ "tsdown": "^0.23.0",
36
+ "typescript": "^6.0.3",
37
+ "vitest": "^4.1.5"
38
+ }
39
+ }