@oh-just-another/editor 0.58.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.
@@ -0,0 +1,78 @@
1
+ import { type RendererBackend } from "@oh-just-another/renderer-canvas";
2
+ /**
3
+ * Capability profile chosen for a `<Diagram>` mount. Calculated once
4
+ * via {@link detectCapabilities}; flows downstream into the renderer
5
+ * backend, WASM opt-ins, worker pool, tile cache, etc. The result is
6
+ * also logged to the console at boot so the host can see exactly
7
+ * what's been activated.
8
+ *
9
+ * Hosts may override individual fields via `<Diagram capabilities={…}>`
10
+ * — passing a partial profile shallow-merges over the auto-detect
11
+ * result, so e.g. `{ wasmText: false }` forces Canvas2D measureText
12
+ * even on a browser that would happily run the bundled WASM.
13
+ */
14
+ export interface CapabilityProfile {
15
+ /**
16
+ * Renderer backend the kernel will activate. `auto` is never the
17
+ * final value — detection collapses it to one of the concrete
18
+ * choices before mount.
19
+ */
20
+ readonly renderer: RendererBackend;
21
+ /** Use the bundled `WasmTextShaper` for wrap measurements. */
22
+ readonly wasmText: boolean;
23
+ /**
24
+ * Use the bundled `WasmRasterizer` for path flatten / stroke-to-
25
+ * fill. Currently only the WebGL2 backend calls into the
26
+ * rasterizer (Canvas2D has native `ctx.bezierCurveTo` which is
27
+ * faster than any WASM round-trip); detection still flips this
28
+ * field on for any backend so the host knows the wasm is loaded.
29
+ */
30
+ readonly wasmRaster: boolean;
31
+ /**
32
+ * Spawn OffscreenCanvas workers per layer for the main render
33
+ * pass. Independent from `renderer === "offscreen"` — even with
34
+ * a Canvas2D main path the tile pre-rasterisation can be off-
35
+ * thread. Set false on Safari < 16.4 (no OffscreenCanvas).
36
+ */
37
+ readonly workers: boolean;
38
+ /**
39
+ * Use the tile cache compositor for very large scenes. Only kicks
40
+ * in when scene size crosses `LARGE_SCENE_HIT_THRESHOLD` —
41
+ * smaller scenes pay no overhead from this flag.
42
+ */
43
+ readonly tiles: boolean;
44
+ /**
45
+ * Whether `pointer: coarse` was reported (touch primary input).
46
+ * Editor uses this for hit-slop tuning; surfaced here so the
47
+ * console log shows the chosen modality.
48
+ */
49
+ readonly touch: boolean;
50
+ }
51
+ /**
52
+ * What the host can pass to `<Diagram capabilities={…}>`. Each
53
+ * field becomes `auto` when omitted, meaning "let the detector
54
+ * pick"; anything else short-circuits detection for that field.
55
+ */
56
+ export interface CapabilityOverrides {
57
+ readonly renderer?: RendererBackend | "auto";
58
+ readonly wasmText?: boolean | "auto";
59
+ readonly wasmRaster?: boolean | "auto";
60
+ readonly workers?: boolean | "auto";
61
+ readonly tiles?: boolean | "auto";
62
+ }
63
+ /**
64
+ * Run the auto-detection sweep and apply caller overrides. Always
65
+ * async because WebGPU detection requires `await requestAdapter()`.
66
+ *
67
+ * Override semantics:
68
+ * • absent / `"auto"` → detector picks
69
+ * • concrete value (renderer string, true, false) → wins outright
70
+ */
71
+ export declare const detectCapabilities: (overrides?: CapabilityOverrides) => Promise<CapabilityProfile>;
72
+ /**
73
+ * One-line console.log of the unpacked profile — a fixed format so the
74
+ * host can grep it in DevTools. Runs once on mount; repeated calls
75
+ * print a new line.
76
+ */
77
+ export declare const logCapabilities: (profile: CapabilityProfile) => void;
78
+ //# sourceMappingURL=capabilities.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.d.ts","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,eAAe,EACrB,MAAM,kCAAkC,CAAC;AAE1C;;;;;;;;;;;GAWG;AACH,MAAM,WAAW,iBAAiB;IAChC;;;;OAIG;IACH,QAAQ,CAAC,QAAQ,EAAE,eAAe,CAAC;IACnC,8DAA8D;IAC9D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B;;;;;;OAMG;IACH,QAAQ,CAAC,UAAU,EAAE,OAAO,CAAC;IAC7B;;;;;OAKG;IACH,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,QAAQ,CAAC,EAAE,eAAe,GAAG,MAAM,CAAC;IAC7C,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACvC,QAAQ,CAAC,OAAO,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;IACpC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,GAAG,MAAM,CAAC;CACnC;AAmCD;;;;;;;GAOG;AACH,eAAO,MAAM,kBAAkB,GAC7B,YAAW,mBAAwB,KAClC,OAAO,CAAC,iBAAiB,CAoB3B,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,iBAAiB,KAAG,IAoB5D,CAAC"}
@@ -0,0 +1,74 @@
1
+ import { isWebGL2Available, isWebGPUAvailable, } from "@oh-just-another/renderer-canvas";
2
+ const supportsOffscreenCanvas = () => {
3
+ if (typeof OffscreenCanvas === "undefined")
4
+ return false;
5
+ if (typeof HTMLCanvasElement === "undefined")
6
+ return false;
7
+ return typeof HTMLCanvasElement.prototype.transferControlToOffscreen === "function";
8
+ };
9
+ const supportsWorkers = () => typeof Worker !== "undefined";
10
+ const supportsWasm = () => typeof WebAssembly !== "undefined" && typeof WebAssembly.instantiate === "function";
11
+ const isTouchPrimary = () => {
12
+ if (typeof matchMedia !== "function")
13
+ return false;
14
+ try {
15
+ return matchMedia("(pointer: coarse)").matches;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ };
21
+ /**
22
+ * Pick the best renderer the runtime actually supports. WebGPU is
23
+ * surrogated to WebGL2 today (no GPU pipeline shipped) — the
24
+ * detector returns `webgl2` when WebGPU is present so the host
25
+ * still gets the highest-fidelity available path.
26
+ */
27
+ const detectRenderer = async () => {
28
+ if (await isWebGPUAvailable())
29
+ return "webgl2";
30
+ if (isWebGL2Available())
31
+ return "webgl2";
32
+ if (supportsOffscreenCanvas() && supportsWorkers())
33
+ return "offscreen";
34
+ return "canvas2d";
35
+ };
36
+ /**
37
+ * Run the auto-detection sweep and apply caller overrides. Always
38
+ * async because WebGPU detection requires `await requestAdapter()`.
39
+ *
40
+ * Override semantics:
41
+ * • absent / `"auto"` → detector picks
42
+ * • concrete value (renderer string, true, false) → wins outright
43
+ */
44
+ export const detectCapabilities = async (overrides = {}) => {
45
+ const renderer = overrides.renderer && overrides.renderer !== "auto"
46
+ ? overrides.renderer
47
+ : await detectRenderer();
48
+ const wasmText = overrides.wasmText !== undefined && overrides.wasmText !== "auto"
49
+ ? overrides.wasmText
50
+ : supportsWasm();
51
+ const wasmRaster = overrides.wasmRaster !== undefined && overrides.wasmRaster !== "auto"
52
+ ? overrides.wasmRaster
53
+ : supportsWasm() && renderer === "webgl2";
54
+ const workers = overrides.workers !== undefined && overrides.workers !== "auto"
55
+ ? overrides.workers
56
+ : supportsOffscreenCanvas() && supportsWorkers();
57
+ const tiles = overrides.tiles !== undefined && overrides.tiles !== "auto" ? overrides.tiles : true;
58
+ return { renderer, wasmText, wasmRaster, workers, tiles, touch: isTouchPrimary() };
59
+ };
60
+ /**
61
+ * One-line console.log of the unpacked profile — a fixed format so the
62
+ * host can grep it in DevTools. Runs once on mount; repeated calls
63
+ * print a new line.
64
+ */
65
+ export const logCapabilities = (profile) => {
66
+ const reason = profile.renderer === "webgl2"
67
+ ? "WebGL2"
68
+ : profile.renderer === "offscreen"
69
+ ? "OffscreenCanvas + Worker"
70
+ : "Canvas2D";
71
+ // eslint-disable-next-line no-console
72
+ console.log("%c[diagram]%c renderer=%s, wasmText=%s, wasmRaster=%s, workers=%s, tiles=%s, touch=%s (%s)", "color: #1a73e8; font-weight: 700", "color: inherit", profile.renderer, profile.wasmText, profile.wasmRaster, profile.workers, profile.tiles, profile.touch, reason);
73
+ };
74
+ //# sourceMappingURL=capabilities.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../src/capabilities.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * True when the keyboard event is aimed at an editable element — a text
3
+ * field, `<select>`, or any `contenteditable` host. Global keyboard
4
+ * handlers (hotkeys, snap-suppress modifier tracking) must bail when this
5
+ * is true so the user's typing isn't hijacked by canvas shortcuts.
6
+ */
7
+ export declare const isEditableTarget: (target: EventTarget | null) => boolean;
8
+ //# sourceMappingURL=dom-focus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom-focus.d.ts","sourceRoot":"","sources":["../src/dom-focus.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,eAAO,MAAM,gBAAgB,GAAI,QAAQ,WAAW,GAAG,IAAI,KAAG,OAI7D,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * True when the keyboard event is aimed at an editable element — a text
3
+ * field, `<select>`, or any `contenteditable` host. Global keyboard
4
+ * handlers (hotkeys, snap-suppress modifier tracking) must bail when this
5
+ * is true so the user's typing isn't hijacked by canvas shortcuts.
6
+ */
7
+ export const isEditableTarget = (target) => {
8
+ if (!(target instanceof HTMLElement))
9
+ return false;
10
+ const tag = target.tagName;
11
+ return tag === "INPUT" || tag === "TEXTAREA" || tag === "SELECT" || target.isContentEditable;
12
+ };
13
+ //# sourceMappingURL=dom-focus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom-focus.js","sourceRoot":"","sources":["../src/dom-focus.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,MAA0B,EAAW,EAAE;IACtE,IAAI,CAAC,CAAC,MAAM,YAAY,WAAW,CAAC;QAAE,OAAO,KAAK,CAAC;IACnD,MAAM,GAAG,GAAG,MAAM,CAAC,OAAO,CAAC;IAC3B,OAAO,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,UAAU,IAAI,GAAG,KAAK,QAAQ,IAAI,MAAM,CAAC,iBAAiB,CAAC;AAC/F,CAAC,CAAC"}
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Public API of `@oh-just-another/editor` — the drop-in diagram editor.
3
+ * Import `<Editor>` and mount.
4
+ *
5
+ * ```tsx
6
+ * import { Editor } from "@oh-just-another/editor";
7
+ *
8
+ * function App() {
9
+ * return <Editor />;
10
+ * }
11
+ * ```
12
+ *
13
+ * Drive it programmatically via a `ref` — `EditorAPI` exposes curated
14
+ * verbs (mode / selection / undo-redo / zoom / scene) plus `editor`, the
15
+ * full live engine (`EditorInstance` from `@oh-just-another/state`) as the
16
+ * power-user escape hatch. See `EditorProps` for the customisation surface.
17
+ *
18
+ * `Diagram` is a deprecated alias for `Editor`, kept for back-compat.
19
+ */
20
+ export { Diagram as Editor, Diagram, type DiagramAPI as EditorAPI, type DiagramAPI, type DiagramProps as EditorProps, type DiagramProps, type DiagramTheme as EditorTheme, type DiagramTheme, } from "./Diagram.js";
21
+ export { detectCapabilities, logCapabilities, type CapabilityProfile, type CapabilityOverrides, } from "./capabilities.js";
22
+ export { isEditableTarget } from "./dom-focus.js";
23
+ export { exportSceneToPng, type PngExportBackground, type PngExportOptions } from "./png-export.js";
24
+ export { registerBounder, registerLayoutKind } from "@oh-just-another/scene";
25
+ export { registerElementRenderer, registerAnimationAdapter } from "@oh-just-another/renderer-core";
26
+ export { registerMigration } from "@oh-just-another/serialization";
27
+ export type { ElementId } from "@oh-just-another/types";
28
+ export type { Editor as EditorInstance, Mode, FileDropHandler } from "@oh-just-another/state";
29
+ export type { Scene, LayoutKindEntry } from "@oh-just-another/scene";
30
+ export type { Template } from "@oh-just-another/templates";
31
+ export type { AnimatedSourceAdapter } from "@oh-just-another/renderer-core";
32
+ export type { RendererBackend } from "@oh-just-another/renderer-canvas";
33
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EACL,OAAO,IAAI,MAAM,EACjB,OAAO,EACP,KAAK,UAAU,IAAI,SAAS,EAC5B,KAAK,UAAU,EACf,KAAK,YAAY,IAAI,WAAW,EAChC,KAAK,YAAY,EACjB,KAAK,YAAY,IAAI,WAAW,EAChC,KAAK,YAAY,GAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,KAAK,iBAAiB,EACtB,KAAK,mBAAmB,GACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAE,KAAK,mBAAmB,EAAE,KAAK,gBAAgB,EAAE,MAAM,iBAAiB,CAAC;AAKpG,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AACnG,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC;AAOnE,YAAY,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,YAAY,EAAE,MAAM,IAAI,cAAc,EAAE,IAAI,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AAC9F,YAAY,EAAE,KAAK,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AACrE,YAAY,EAAE,QAAQ,EAAE,MAAM,4BAA4B,CAAC;AAC3D,YAAY,EAAE,qBAAqB,EAAE,MAAM,gCAAgC,CAAC;AAC5E,YAAY,EAAE,eAAe,EAAE,MAAM,kCAAkC,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Public API of `@oh-just-another/editor` — the drop-in diagram editor.
3
+ * Import `<Editor>` and mount.
4
+ *
5
+ * ```tsx
6
+ * import { Editor } from "@oh-just-another/editor";
7
+ *
8
+ * function App() {
9
+ * return <Editor />;
10
+ * }
11
+ * ```
12
+ *
13
+ * Drive it programmatically via a `ref` — `EditorAPI` exposes curated
14
+ * verbs (mode / selection / undo-redo / zoom / scene) plus `editor`, the
15
+ * full live engine (`EditorInstance` from `@oh-just-another/state`) as the
16
+ * power-user escape hatch. See `EditorProps` for the customisation surface.
17
+ *
18
+ * `Diagram` is a deprecated alias for `Editor`, kept for back-compat.
19
+ */
20
+ export { Diagram as Editor, Diagram, } from "./Diagram.js";
21
+ export { detectCapabilities, logCapabilities, } from "./capabilities.js";
22
+ export { isEditableTarget } from "./dom-focus.js";
23
+ export { exportSceneToPng } from "./png-export.js";
24
+ // Plug-in registries from the underlying packages, surfaced here so the
25
+ // umbrella package is the single import for extending the editor without
26
+ // reaching into the lower-level packages.
27
+ export { registerBounder, registerLayoutKind } from "@oh-just-another/scene";
28
+ export { registerElementRenderer, registerAnimationAdapter } from "@oh-just-another/renderer-core";
29
+ export { registerMigration } from "@oh-just-another/serialization";
30
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,EACL,OAAO,IAAI,MAAM,EACjB,OAAO,GAOR,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,kBAAkB,EAClB,eAAe,GAGhB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAClD,OAAO,EAAE,gBAAgB,EAAmD,MAAM,iBAAiB,CAAC;AAEpG,wEAAwE;AACxE,yEAAyE;AACzE,0CAA0C;AAC1C,OAAO,EAAE,eAAe,EAAE,kBAAkB,EAAE,MAAM,wBAAwB,CAAC;AAC7E,OAAO,EAAE,uBAAuB,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAC;AACnG,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAC"}
@@ -0,0 +1,36 @@
1
+ import { type Scene } from "@oh-just-another/scene";
2
+ /**
3
+ * Browser-side PNG export — renders the **full scene** (not just the
4
+ * current viewport) into an OffscreenCanvas via the standard
5
+ * `renderScene` + `renderLinks` pipeline, then converts to a PNG blob.
6
+ *
7
+ * Three variants, exposed as separate menu items:
8
+ *
9
+ * • "transparent" — no background fill, PNG alpha channel preserved.
10
+ * • "color" — solid fill in the host's canvas colour.
11
+ * • "color-and-grid" — solid fill + the same grid the user sees on
12
+ * the canvas (same gridSize / gridStyle).
13
+ *
14
+ * This host-side helper is used instead of `@oh-just-another/exporter.exportPng`
15
+ * because exporter's path goes through `@headless.renderToPng`, which pulls
16
+ * `@resvg/resvg-js` (~3 MB WASM peer dep). For a browser host the kernel's
17
+ * `Canvas2DTarget` + `renderScene` are already on hand — no extra
18
+ * dependency, no SVG round-trip, identical visual output.
19
+ *
20
+ * Returns `null` when the scene has no shapes (host shows an alert).
21
+ */
22
+ /** Variant selector — drives background / grid handling. */
23
+ export type PngExportBackground = "transparent" | "color" | "color-and-grid";
24
+ export interface PngExportOptions {
25
+ readonly background: PngExportBackground;
26
+ /** Device-pixel scale. 2 = retina-quality (host default). */
27
+ readonly scale: number;
28
+ /**
29
+ * CSS colour string used for the solid background fill. Ignored when
30
+ * `background === "transparent"`. Host reads the current
31
+ * `--du-canvas-bg` CSS variable so the PNG matches what the user sees.
32
+ */
33
+ readonly backgroundColor: string;
34
+ }
35
+ export declare const exportSceneToPng: (scene: Scene, options: PngExportOptions) => Promise<Blob | null>;
36
+ //# sourceMappingURL=png-export.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"png-export.d.ts","sourceRoot":"","sources":["../src/png-export.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,KAAK,EACX,MAAM,wBAAwB,CAAC;AAKhC;;;;;;;;;;;;;;;;;;;GAmBG;AAEH,4DAA4D;AAC5D,MAAM,MAAM,mBAAmB,GAAG,aAAa,GAAG,OAAO,GAAG,gBAAgB,CAAC;AAE7E,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAC;IACzC,6DAA6D;IAC7D,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;CAClC;AAUD,eAAO,MAAM,gBAAgB,GAC3B,OAAO,KAAK,EACZ,SAAS,gBAAgB,KACxB,OAAO,CAAC,IAAI,GAAG,IAAI,CA6DrB,CAAC"}
@@ -0,0 +1,84 @@
1
+ import { getLayersInOrder, getElementWorldBounds, getElementsInLayer, } from "@oh-just-another/scene";
2
+ import { renderLinks, renderGrid, renderScene } from "@oh-just-another/renderer-core";
3
+ import { createOffscreenCanvas2DTarget } from "@oh-just-another/renderer-canvas";
4
+ /**
5
+ * Padding around the scene bbox, in world units. Matches the
6
+ * `zoomToFit` default so the exported framing feels like "what fit
7
+ * on screen would look like". Hosts that want a tight bbox can crop
8
+ * after the fact.
9
+ */
10
+ const EXPORT_PADDING_WORLD = 20;
11
+ export const exportSceneToPng = async (scene, options) => {
12
+ if (typeof OffscreenCanvas === "undefined")
13
+ return null;
14
+ const bbox = computeSceneBbox(scene);
15
+ if (!bbox)
16
+ return null; // empty scene — host shows an alert
17
+ const padded = {
18
+ x: bbox.x - EXPORT_PADDING_WORLD,
19
+ y: bbox.y - EXPORT_PADDING_WORLD,
20
+ width: bbox.width + 2 * EXPORT_PADDING_WORLD,
21
+ height: bbox.height + 2 * EXPORT_PADDING_WORLD,
22
+ };
23
+ const canvasW = Math.max(1, Math.ceil(padded.width * options.scale));
24
+ const canvasH = Math.max(1, Math.ceil(padded.height * options.scale));
25
+ const { canvas, target } = createOffscreenCanvas2DTarget(canvasW, canvasH);
26
+ // Background fill via the kernel target before shapes render (with
27
+ // skipClear so the fill survives the renderScene pass). Drawing
28
+ // through the target keeps it backend-agnostic — the same path works
29
+ // for any RenderTarget impl.
30
+ if (options.background !== "transparent") {
31
+ target.setFill(options.backgroundColor);
32
+ target.beginPath();
33
+ target.rect(0, 0, canvasW, canvasH);
34
+ target.fill();
35
+ }
36
+ // Synthesise a viewport that maps the padded world bbox onto the
37
+ // OffscreenCanvas backbuffer at the requested scale.
38
+ // `pan = (padded.x, padded.y)` → world origin pixel (0, 0).
39
+ // `zoom = scale` → world unit covers `scale` device pixels.
40
+ // `size = canvasW × canvasH` → renderers see the full backbuffer.
41
+ const exportScene = {
42
+ ...scene,
43
+ viewport: {
44
+ pan: { x: padded.x, y: padded.y },
45
+ zoom: options.scale,
46
+ rotation: 0,
47
+ size: { width: canvasW, height: canvasH },
48
+ ...(scene.viewport.gridSize !== undefined ? { gridSize: scene.viewport.gridSize } : {}),
49
+ ...(scene.viewport.gridStyle !== undefined ? { gridStyle: scene.viewport.gridStyle } : {}),
50
+ },
51
+ };
52
+ // Grid pass — only for the color-and-grid variant. Skipped when the
53
+ // scene has no gridSize (user disabled it).
54
+ if (options.background === "color-and-grid" &&
55
+ exportScene.viewport.gridSize &&
56
+ exportScene.viewport.gridSize > 0) {
57
+ renderGrid(exportScene, target);
58
+ }
59
+ // Shapes (skipClear: true so background / grid survive).
60
+ renderScene(exportScene, target, { skipClear: true });
61
+ renderLinks(exportScene, target);
62
+ return canvas.convertToBlob({ type: "image/png" });
63
+ };
64
+ const computeSceneBbox = (scene) => {
65
+ let acc = null;
66
+ for (const layer of getLayersInOrder(scene)) {
67
+ if (!layer.visible)
68
+ continue;
69
+ for (const shape of getElementsInLayer(scene, layer.id)) {
70
+ const b = getElementWorldBounds(shape);
71
+ acc = acc ? unionBounds(acc, b) : b;
72
+ }
73
+ }
74
+ return acc;
75
+ };
76
+ /** Inlined AABB union — avoids pulling `@math` into the host package. */
77
+ const unionBounds = (a, b) => {
78
+ const x = Math.min(a.x, b.x);
79
+ const y = Math.min(a.y, b.y);
80
+ const right = Math.max(a.x + a.width, b.x + b.width);
81
+ const bottom = Math.max(a.y + a.height, b.y + b.height);
82
+ return { x, y, width: right - x, height: bottom - y };
83
+ };
84
+ //# sourceMappingURL=png-export.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"png-export.js","sourceRoot":"","sources":["../src/png-export.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "@oh-just-another/editor",
3
+ "version": "0.58.0",
4
+ "description": "Drop-in diagram editor as a single React component. Auto-detects renderer / WASM / worker capabilities and exposes a programmatic editor API.",
5
+ "license": "MIT",
6
+ "author": "Rustam Garifulin <rustam@n69.in>",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "git+https://github.com/oh-just-another/diagram.git",
10
+ "directory": "packages/editor"
11
+ },
12
+ "homepage": "https://github.com/oh-just-another/diagram#readme",
13
+ "bugs": "https://github.com/oh-just-another/diagram/issues",
14
+ "keywords": [
15
+ "diagram",
16
+ "canvas",
17
+ "svg",
18
+ "editor",
19
+ "graph",
20
+ "drawing"
21
+ ],
22
+ "type": "module",
23
+ "main": "./dist/index.js",
24
+ "module": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js"
30
+ }
31
+ },
32
+ "files": [
33
+ "dist",
34
+ "README.md",
35
+ "CHANGELOG.md"
36
+ ],
37
+ "sideEffects": false,
38
+ "publishConfig": {
39
+ "access": "public"
40
+ },
41
+ "dependencies": {
42
+ "lucide-react": "^1.16.0",
43
+ "@oh-just-another/raster-wasm": "0.57.0",
44
+ "@oh-just-another/react-ui": "0.57.1",
45
+ "@oh-just-another/renderer-canvas": "0.58.0",
46
+ "@oh-just-another/renderer-core": "0.57.0",
47
+ "@oh-just-another/renderer-svg": "0.57.0",
48
+ "@oh-just-another/state": "0.57.0",
49
+ "@oh-just-another/templates": "0.57.0",
50
+ "@oh-just-another/scene": "0.57.0",
51
+ "@oh-just-another/text-wasm": "0.57.0",
52
+ "@oh-just-another/serialization": "0.57.0",
53
+ "@oh-just-another/types": "0.57.0"
54
+ },
55
+ "peerDependencies": {
56
+ "react": ">=18",
57
+ "react-dom": ">=18"
58
+ },
59
+ "devDependencies": {
60
+ "@testing-library/react": "^16.1.0",
61
+ "@types/react": "^19.0.0",
62
+ "@types/react-dom": "^19.0.0",
63
+ "jsdom": "^25.0.0",
64
+ "react": "^19.0.0",
65
+ "react-dom": "^19.0.0"
66
+ },
67
+ "scripts": {
68
+ "build": "tsc -b tsconfig.build.json",
69
+ "typecheck": "tsc --noEmit",
70
+ "test": "vitest run",
71
+ "test:watch": "vitest",
72
+ "lint": "eslint src tests",
73
+ "clean": "rm -rf dist *.tsbuildinfo"
74
+ }
75
+ }