@edv4h/usketch-dom-renderer 1.0.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 EdV4H
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,10 @@
1
+ import type { LayerRenderContext, ShapeRegistry } from "@edv4h/usketch-shared";
2
+ export declare function DomShapeLayer({ ctx, shapeRegistry, claimedIds, dropTargetId, }: {
3
+ ctx: LayerRenderContext;
4
+ shapeRegistry: ShapeRegistry;
5
+ /** Shape IDs already claimed by another renderer (e.g. GPU). These are skipped. */
6
+ claimedIds?: ReadonlySet<string>;
7
+ /** Frame/group ID that the dragged shapes are hovering over. */
8
+ dropTargetId?: string | null;
9
+ }): import("react/jsx-runtime").JSX.Element | null;
10
+ //# sourceMappingURL=dom-shape-layer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom-shape-layer.d.ts","sourceRoot":"","sources":["../src/dom-shape-layer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAa,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAsH1F,wBAAgB,aAAa,CAAC,EAC7B,GAAG,EACH,aAAa,EACb,UAAU,EACV,YAAY,GACZ,EAAE;IACF,GAAG,EAAE,kBAAkB,CAAC;IACxB,aAAa,EAAE,aAAa,CAAC;IAC7B,mFAAmF;IACnF,UAAU,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACjC,gEAAgE;IAChE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B,kDAiHA"}
@@ -0,0 +1,148 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { safeRotation } from "@edv4h/usketch-shared";
3
+ import { LodFallback } from "./lod-fallback.js";
4
+ /**
5
+ * Reorder shapes so that each child appears immediately after its parent container,
6
+ * while preserving the relative order within each sibling group. The input must
7
+ * already be sorted by z-order (e.g. zIndex ascending).
8
+ *
9
+ * Rationale: DOM z-ordering is controlled by source order + zIndex; containers
10
+ * (frame/island/group) must render before their children so the child elements
11
+ * appear on top. Using a stable group-by-parent pass keeps the zIndex-based
12
+ * order within siblings intact.
13
+ */
14
+ function stableParentSort(sorted, _containerTypes) {
15
+ // Group children by parentId (preserving order from `sorted`)
16
+ const childrenByParent = new Map();
17
+ const topLevel = [];
18
+ for (const shape of sorted) {
19
+ const parentId = typeof shape.parentId === "string" && shape.parentId.length > 0 ? shape.parentId : null;
20
+ if (parentId === null) {
21
+ topLevel.push(shape);
22
+ }
23
+ else {
24
+ let bucket = childrenByParent.get(parentId);
25
+ if (!bucket) {
26
+ bucket = [];
27
+ childrenByParent.set(parentId, bucket);
28
+ }
29
+ bucket.push(shape);
30
+ }
31
+ }
32
+ // Recursive emit: parent first, then its children (each child may also be a parent)
33
+ const result = [];
34
+ function emit(shape) {
35
+ result.push(shape);
36
+ const children = childrenByParent.get(shape.id);
37
+ if (children) {
38
+ for (const child of children)
39
+ emit(child);
40
+ }
41
+ }
42
+ for (const shape of topLevel)
43
+ emit(shape);
44
+ // Append orphaned children (whose parent is missing) in their original order
45
+ const emittedIds = new Set(result.map((s) => s.id));
46
+ for (const shape of sorted) {
47
+ if (!emittedIds.has(shape.id))
48
+ result.push(shape);
49
+ }
50
+ return result;
51
+ }
52
+ // When dragging shapes onto a frame (drop target detected), boost their
53
+ // zIndex so they render above the frame's opaque background.
54
+ // Only applied when a drop target is active, not during normal selection.
55
+ const DROP_TARGET_Z_BOOST = 100_000;
56
+ const CONTAINER_SHAPE_TYPES = new Set(["frame", "island", "group"]);
57
+ function ShapeWrapper({ shape, index, selected, overDropTarget, def, }) {
58
+ const bounds = { x: shape.x, y: shape.y, width: shape.width, height: shape.height };
59
+ const rotation = safeRotation(shape.rotation);
60
+ const isHtml = def.renderTarget === "html";
61
+ // For SVG shapes with zero width or height, use a minimum viewBox size
62
+ // to avoid NaN from SVG coordinate scaling (e.g. horizontal/vertical lines,
63
+ // groups with no children, connectors between aligned shapes).
64
+ const svgW = Math.max(bounds.width, 1);
65
+ const svgH = Math.max(bounds.height, 1);
66
+ // Containers (island/frame/group) use pointerEvents: none to let children receive clicks
67
+ const isContainer = CONTAINER_SHAPE_TYPES.has(shape.type);
68
+ const boosted = selected && !isContainer && overDropTarget;
69
+ return (_jsx("div", { style: {
70
+ position: "absolute",
71
+ left: bounds.x,
72
+ top: bounds.y,
73
+ width: bounds.width,
74
+ height: bounds.height,
75
+ zIndex: boosted ? index + DROP_TARGET_Z_BOOST : index,
76
+ pointerEvents: isContainer ? "none" : "auto",
77
+ transform: rotation ? `rotate(${rotation}deg)` : undefined,
78
+ transformOrigin: "center center",
79
+ }, children: isHtml ? (def.render(shape)) : (_jsx("svg", { width: svgW, height: svgH, viewBox: `${bounds.x} ${bounds.y} ${svgW} ${svgH}`, style: { display: "block", overflow: "visible" }, children: def.render(shape) })) }));
80
+ }
81
+ export function DomShapeLayer({ ctx, shapeRegistry, claimedIds, dropTargetId, }) {
82
+ // `gpu-only` mode: hide the DOM layer entirely (reserved for future use).
83
+ if (ctx.renderMode === "gpu-only")
84
+ return null;
85
+ // In `interactive` mode the GPU layer does not render, so any lingering
86
+ // claimedIds from a previous `lod` pass must be ignored — otherwise a
87
+ // stale closure in the plugin layer can keep hiding shapes until the next
88
+ // store mutation. DOM owns the full shape set in interactive mode.
89
+ const isLod = ctx.renderMode === "lod";
90
+ const effectiveClaimedIds = isLod ? claimedIds : undefined;
91
+ // Use pre-sorted shapes (by zIndex ascending) from the layer render context,
92
+ // then reorder so that each child appears immediately after its parent container.
93
+ // This preserves user-controlled z-order within each sibling group while keeping
94
+ // the container-before-child invariant required by the DOM stacking context.
95
+ const CONTAINER_TYPES = new Set(["frame", "island", "group"]);
96
+ const shapes = stableParentSort(ctx.shapesSorted, CONTAINER_TYPES);
97
+ return (_jsx("div", { "data-layer": "shapes", children: shapes.map((shape, index) => {
98
+ // Skip invalid shapes (e.g. leaked Yjs internal objects)
99
+ if (!shape || typeof shape.id !== "string" || typeof shape.type !== "string") {
100
+ return null;
101
+ }
102
+ // Skip shapes claimed by another renderer (lod mode only)
103
+ if (effectiveClaimedIds?.has(shape.id)) {
104
+ return null;
105
+ }
106
+ const def = shapeRegistry.get(shape.type);
107
+ // LOD mode: render the shape's simplifiedComponent (or LodFallback)
108
+ // instead of its full component. The simplifiedComponent is
109
+ // responsible for its own positioning (it receives the full
110
+ // ShapeData and uses shape.x/y directly), so no wrapper div is
111
+ // needed — a wrapper without position:absolute would break
112
+ // zIndex stacking anyway.
113
+ //
114
+ // Additionally, skip shapes that the GPU renderer is capable of
115
+ // drawing (even if it has not yet claimed them on this frame).
116
+ // Without this, GPU-eligible shapes would briefly get a DOM
117
+ // LodFallback rendered on top until the next GPU claim arrives,
118
+ // producing a visible "double render" flash.
119
+ if (isLod) {
120
+ if (def?.gpuPrimitive)
121
+ return null;
122
+ const Simplified = def?.simplifiedComponent ?? LodFallback;
123
+ return _jsx(Simplified, { shape: shape }, shape.id);
124
+ }
125
+ if (!def) {
126
+ const sx = shape.x || 0;
127
+ const sy = shape.y || 0;
128
+ const sw = shape.width || 0;
129
+ const sh = shape.height || 0;
130
+ const fbW = Math.max(sw, 1);
131
+ const fbH = Math.max(sh, 1);
132
+ const fbRotation = safeRotation(shape.rotation);
133
+ return (_jsx("div", { style: {
134
+ position: "absolute",
135
+ left: sx,
136
+ top: sy,
137
+ width: sw,
138
+ height: sh,
139
+ zIndex: index,
140
+ pointerEvents: "auto",
141
+ transform: fbRotation ? `rotate(${fbRotation}deg)` : undefined,
142
+ transformOrigin: "center center",
143
+ }, children: _jsxs("svg", { width: fbW, height: fbH, viewBox: `${sx} ${sy} ${fbW} ${fbH}`, style: { display: "block", overflow: "visible" }, children: [_jsx("rect", { x: sx, y: sy, width: sw, height: sh, fill: "rgba(200,200,200,0.3)", stroke: "#999", strokeWidth: 1, strokeDasharray: "4 2" }), _jsx("text", { x: sx + sw / 2, y: sy + sh / 2, textAnchor: "middle", dominantBaseline: "central", fontSize: 11, fill: "#999", children: shape.type })] }) }, shape.id));
144
+ }
145
+ return (_jsx(ShapeWrapper, { shape: shape, index: index, selected: ctx.selection.has(shape.id), overDropTarget: !!dropTargetId, def: def }, shape.id));
146
+ }) }));
147
+ }
148
+ //# sourceMappingURL=dom-shape-layer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dom-shape-layer.js","sourceRoot":"","sources":["../src/dom-shape-layer.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { DomShapeLayer } from "./dom-shape-layer.js";
2
+ export { createDomRendererPlugin } from "./plugin.js";
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { DomShapeLayer } from "./dom-shape-layer.js";
2
+ export { createDomRendererPlugin } from "./plugin.js";
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AACrD,OAAO,EAAE,uBAAuB,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { ShapeData } from "@edv4h/usketch-shared";
2
+ /**
3
+ * Default lightweight component used when a shape has no `simplifiedComponent`
4
+ * defined. Renders a solid-fill rectangle matching the shape's bounds and fill
5
+ * color. Pointer events are disabled because LOD mode is for read-only zoomed
6
+ * out viewing.
7
+ */
8
+ export declare function LodFallback({ shape }: {
9
+ shape: ShapeData;
10
+ }): import("react/jsx-runtime").JSX.Element;
11
+ //# sourceMappingURL=lod-fallback.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lod-fallback.d.ts","sourceRoot":"","sources":["../src/lod-fallback.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGvD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,SAAS,CAAA;CAAE,2CAkB1D"}
@@ -0,0 +1,24 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { safeRotation } from "@edv4h/usketch-shared";
3
+ /**
4
+ * Default lightweight component used when a shape has no `simplifiedComponent`
5
+ * defined. Renders a solid-fill rectangle matching the shape's bounds and fill
6
+ * color. Pointer events are disabled because LOD mode is for read-only zoomed
7
+ * out viewing.
8
+ */
9
+ export function LodFallback({ shape }) {
10
+ const style = (shape.style ?? {});
11
+ const rotation = safeRotation(shape.rotation);
12
+ return (_jsx("div", { style: {
13
+ position: "absolute",
14
+ left: shape.x,
15
+ top: shape.y,
16
+ width: shape.width,
17
+ height: shape.height,
18
+ backgroundColor: style.fill || "#cccccc",
19
+ pointerEvents: "none",
20
+ transform: rotation ? `rotate(${rotation}deg)` : undefined,
21
+ transformOrigin: "center center",
22
+ } }));
23
+ }
24
+ //# sourceMappingURL=lod-fallback.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lod-fallback.js","sourceRoot":"","sources":["../src/lod-fallback.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,YAAY,EAAE,MAAM,uBAAuB,CAAC;AAErD;;;;;GAKG;AACH,MAAM,UAAU,WAAW,CAAC,EAAE,KAAK,EAAwB;IAC1D,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,KAAK,IAAI,EAAE,CAAsB,CAAC;IACvD,MAAM,QAAQ,GAAG,YAAY,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;IAC9C,OAAO,CACN,cACC,KAAK,EAAE;YACN,QAAQ,EAAE,UAAU;YACpB,IAAI,EAAE,KAAK,CAAC,CAAC;YACb,GAAG,EAAE,KAAK,CAAC,CAAC;YACZ,KAAK,EAAE,KAAK,CAAC,KAAK;YAClB,MAAM,EAAE,KAAK,CAAC,MAAM;YACpB,eAAe,EAAE,KAAK,CAAC,IAAI,IAAI,SAAS;YACxC,aAAa,EAAE,MAAM;YACrB,SAAS,EAAE,QAAQ,CAAC,CAAC,CAAC,UAAU,QAAQ,MAAM,CAAC,CAAC,CAAC,SAAS;YAC1D,eAAe,EAAE,eAAe;SAChC,GACA,CACF,CAAC;AACH,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { UsketchPlugin } from "@edv4h/usketch-shared";
2
+ export declare function createDomRendererPlugin(): UsketchPlugin;
3
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAG1E,wBAAgB,uBAAuB,IAAI,aAAa,CAyEvD"}
package/dist/plugin.js ADDED
@@ -0,0 +1,57 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { DomShapeLayer } from "./dom-shape-layer.js";
3
+ export function createDomRendererPlugin() {
4
+ let cleanup;
5
+ return {
6
+ id: "usketch-dom-renderer",
7
+ name: "DOM Renderer",
8
+ setup(ctx) {
9
+ // Track shape IDs claimed by other renderers (e.g. GPU)
10
+ let claimedIds = new Set();
11
+ // Track drop target frame (set by select tool during drag)
12
+ let dropTargetId = null;
13
+ const unsubClaim = ctx.events.on("renderer:claim-shapes", (data) => {
14
+ // Only nudge the canvas to re-render if the claim set
15
+ // actually changed — otherwise we'd loop with GpuShapeLayer's
16
+ // post-commit emit.
17
+ const next = data.ids;
18
+ let changed = next.size !== claimedIds.size;
19
+ if (!changed) {
20
+ for (const id of next) {
21
+ if (!claimedIds.has(id)) {
22
+ changed = true;
23
+ break;
24
+ }
25
+ }
26
+ }
27
+ claimedIds = next;
28
+ if (changed) {
29
+ // Re-trigger the canvas layer render so DomShapeLayer
30
+ // picks up the new claimedIds value. Closure state
31
+ // alone would not cause React to re-render.
32
+ ctx.events.emit("layers:changed", {});
33
+ }
34
+ });
35
+ const unsubDropTarget = ctx.events.on("drop-target:changed", (data) => {
36
+ if (dropTargetId !== data.id) {
37
+ dropTargetId = data.id;
38
+ ctx.events.emit("layers:changed", {});
39
+ }
40
+ });
41
+ ctx.layers.register({
42
+ id: "dom-shapes",
43
+ order: 50,
44
+ render: (renderCtx) => (_jsx(DomShapeLayer, { ctx: renderCtx, shapeRegistry: ctx.shapes, claimedIds: claimedIds.size > 0 ? claimedIds : undefined, dropTargetId: dropTargetId })),
45
+ });
46
+ cleanup = () => {
47
+ unsubClaim();
48
+ unsubDropTarget();
49
+ ctx.layers.unregister("dom-shapes");
50
+ };
51
+ },
52
+ teardown() {
53
+ cleanup?.();
54
+ },
55
+ };
56
+ }
57
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAErD,MAAM,UAAU,uBAAuB;IACtC,IAAI,OAAiC,CAAC;IAEtC,OAAO;QACN,EAAE,EAAE,sBAAsB;QAC1B,IAAI,EAAE,cAAc;QAEpB,KAAK,CAAC,GAAkB;YACvB,wDAAwD;YACxD,IAAI,UAAU,GAAwB,IAAI,GAAG,EAAE,CAAC;YAChD,2DAA2D;YAC3D,IAAI,YAAY,GAAkB,IAAI,CAAC;YAEvC,MAAM,UAAU,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAC/B,uBAAuB,EACvB,CAAC,IAAI,EAAE,EAAE;gBACR,sDAAsD;gBACtD,8DAA8D;gBAC9D,oBAAoB;gBACpB,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC;gBACtB,IAAI,OAAO,GAAG,IAAI,CAAC,IAAI,KAAK,UAAU,CAAC,IAAI,CAAC;gBAC5C,IAAI,CAAC,OAAO,EAAE,CAAC;oBACd,KAAK,MAAM,EAAE,IAAI,IAAI,EAAE,CAAC;wBACvB,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC;4BACzB,OAAO,GAAG,IAAI,CAAC;4BACf,MAAM;wBACP,CAAC;oBACF,CAAC;gBACF,CAAC;gBACD,UAAU,GAAG,IAAI,CAAC;gBAClB,IAAI,OAAO,EAAE,CAAC;oBACb,sDAAsD;oBACtD,mDAAmD;oBACnD,4CAA4C;oBAC5C,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;gBACvC,CAAC;YACF,CAAC,CACD,CAAC;YAEF,MAAM,eAAe,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CACpC,qBAAqB,EACrB,CAAC,IAAI,EAAE,EAAE;gBACR,IAAI,YAAY,KAAK,IAAI,CAAC,EAAE,EAAE,CAAC;oBAC9B,YAAY,GAAG,IAAI,CAAC,EAAE,CAAC;oBACvB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,gBAAgB,EAAE,EAAE,CAAC,CAAC;gBACvC,CAAC;YACF,CAAC,CACD,CAAC;YAEF,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC;gBACnB,EAAE,EAAE,YAAY;gBAChB,KAAK,EAAE,EAAE;gBACT,MAAM,EAAE,CAAC,SAAS,EAAE,EAAE,CAAC,CACtB,KAAC,aAAa,IACb,GAAG,EAAE,SAAS,EACd,aAAa,EAAE,GAAG,CAAC,MAAM,EACzB,UAAU,EAAE,UAAU,CAAC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,EACxD,YAAY,EAAE,YAAY,GACzB,CACF;aACD,CAAC,CAAC;YAEH,OAAO,GAAG,GAAG,EAAE;gBACd,UAAU,EAAE,CAAC;gBACb,eAAe,EAAE,CAAC;gBAClB,GAAG,CAAC,MAAM,CAAC,UAAU,CAAC,YAAY,CAAC,CAAC;YACrC,CAAC,CAAC;QACH,CAAC;QAED,QAAQ;YACP,OAAO,EAAE,EAAE,CAAC;QACb,CAAC;KACD,CAAC;AACH,CAAC"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@edv4h/usketch-dom-renderer",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "main": "./dist/index.js",
6
+ "types": "./dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "source": "./src/index.ts",
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "dependencies": {
15
+ "@edv4h/usketch-shared": "1.0.0"
16
+ },
17
+ "peerDependencies": {
18
+ "react": ">=19",
19
+ "react-dom": ">=19"
20
+ },
21
+ "devDependencies": {
22
+ "@types/react": "19.2.14",
23
+ "@types/react-dom": "19.2.3",
24
+ "typescript": "5.9.3",
25
+ "vitest": "3.2.4"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "files": [
31
+ "dist",
32
+ "README.md"
33
+ ],
34
+ "license": "MIT",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "git+https://github.com/EdV4H/usketch.git",
38
+ "directory": "packages/dom-renderer"
39
+ },
40
+ "homepage": "https://github.com/EdV4H/usketch#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/EdV4H/usketch/issues"
43
+ },
44
+ "scripts": {
45
+ "build": "tsc -p tsconfig.json",
46
+ "typecheck": "tsc --noEmit",
47
+ "test": "vitest run --passWithNoTests",
48
+ "clean": "rm -rf dist"
49
+ }
50
+ }