@edv4h/usketch-plugin-shape-island 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,20 @@
1
+ import type { BoardStore } from "@edv4h/usketch-shared";
2
+ /**
3
+ * 全島の接触状態を分析し、接触している島同士を group シェイプでまとめる。
4
+ * 離れた島はグループから外す。
5
+ */
6
+ export declare function reconcileIslandGroups(store: BoardStore, commands: {
7
+ execute: (cmd: {
8
+ execute: () => void;
9
+ undo: () => void;
10
+ }) => void;
11
+ }): void;
12
+ /** Detach a single island from its island-group */
13
+ export declare function detachIsland(store: BoardStore, commands: {
14
+ execute: (cmd: {
15
+ execute: () => void;
16
+ undo: () => void;
17
+ }) => void;
18
+ }, islandId: string): void;
19
+ export declare function cleanupIslandGroupTimers(): void;
20
+ //# sourceMappingURL=auto-group.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-group.d.ts","sourceRoot":"","sources":["../src/auto-group.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAA0B,MAAM,uBAAuB,CAAC;AA6EhF;;;GAGG;AACH,wBAAgB,qBAAqB,CACpC,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE;IAAE,OAAO,EAAE,CAAC,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,IAAI,CAAA;KAAE,KAAK,IAAI,CAAA;CAAE,GAC7E,IAAI,CA2LN;AAED,mDAAmD;AACnD,wBAAgB,YAAY,CAC3B,KAAK,EAAE,UAAU,EACjB,QAAQ,EAAE;IAAE,OAAO,EAAE,CAAC,GAAG,EAAE;QAAE,OAAO,EAAE,MAAM,IAAI,CAAC;QAAC,IAAI,EAAE,MAAM,IAAI,CAAA;KAAE,KAAK,IAAI,CAAA;CAAE,EAC/E,QAAQ,EAAE,MAAM,GACd,IAAI,CAsCN;AAED,wBAAgB,wBAAwB,IAAI,IAAI,CAK/C"}
@@ -0,0 +1,276 @@
1
+ import { generateId } from "@edv4h/usketch-shared";
2
+ import { computeGroupBounds, createGroupCommand, createUngroupCommand, getChildShapes, intersectsAABB, } from "@edv4h/usketch-store";
3
+ const TOUCH_PADDING = 4;
4
+ const MERGE_ANIMATION_MS = 700;
5
+ function expandBounds(b, pad) {
6
+ return {
7
+ x: b.x - pad,
8
+ y: b.y - pad,
9
+ width: b.width + pad * 2,
10
+ height: b.height + pad * 2,
11
+ };
12
+ }
13
+ // Union-Find for connected component analysis
14
+ class UnionFind {
15
+ parent = new Map();
16
+ find(x) {
17
+ if (!this.parent.has(x)) {
18
+ this.parent.set(x, x);
19
+ }
20
+ let root = x;
21
+ while (this.parent.get(root) !== root) {
22
+ root = this.parent.get(root);
23
+ }
24
+ let curr = x;
25
+ while (curr !== root) {
26
+ const next = this.parent.get(curr);
27
+ this.parent.set(curr, root);
28
+ curr = next;
29
+ }
30
+ return root;
31
+ }
32
+ union(a, b) {
33
+ const ra = this.find(a);
34
+ const rb = this.find(b);
35
+ if (ra !== rb) {
36
+ if (ra < rb) {
37
+ this.parent.set(rb, ra);
38
+ }
39
+ else {
40
+ this.parent.set(ra, rb);
41
+ }
42
+ }
43
+ }
44
+ }
45
+ // Track merge animation timers
46
+ const mergeTimers = new Map();
47
+ function triggerMergeAnimation(store, islandId) {
48
+ store.updateShape(islandId, { _islandJustMerged: true });
49
+ const existing = mergeTimers.get(islandId);
50
+ if (existing)
51
+ clearTimeout(existing);
52
+ mergeTimers.set(islandId, setTimeout(() => {
53
+ mergeTimers.delete(islandId);
54
+ const shape = store.getShapes().get(islandId);
55
+ if (shape?._islandJustMerged) {
56
+ store.updateShape(islandId, { _islandJustMerged: undefined });
57
+ }
58
+ }, MERGE_ANIMATION_MS));
59
+ }
60
+ /**
61
+ * 全島の接触状態を分析し、接触している島同士を group シェイプでまとめる。
62
+ * 離れた島はグループから外す。
63
+ */
64
+ export function reconcileIslandGroups(store, commands) {
65
+ // 1. Collect all top-level island shapes (not already in an island-group)
66
+ // and islands that are in island-groups
67
+ const allIslands = [];
68
+ const islandGroupIds = new Set();
69
+ for (const [id, shape] of store.getShapes()) {
70
+ if (shape.type !== "island")
71
+ continue;
72
+ const parentId = shape.parentId;
73
+ // Track which groups are island-groups
74
+ if (parentId) {
75
+ const parent = store.getShapes().get(parentId);
76
+ if (parent?.type === "group" && parent._isIslandGroup) {
77
+ islandGroupIds.add(parentId);
78
+ }
79
+ }
80
+ allIslands.push({
81
+ id,
82
+ bounds: { x: shape.x, y: shape.y, width: shape.width, height: shape.height },
83
+ parentId,
84
+ });
85
+ }
86
+ if (allIslands.length < 2) {
87
+ // If only 0-1 islands, dissolve any existing island-groups
88
+ for (const groupId of islandGroupIds) {
89
+ commands.execute(createUngroupCommand(store, groupId));
90
+ }
91
+ return;
92
+ }
93
+ // 2. Build adjacency via Union-Find
94
+ const uf = new UnionFind();
95
+ for (let i = 0; i < allIslands.length; i++) {
96
+ uf.find(allIslands[i].id);
97
+ for (let j = i + 1; j < allIslands.length; j++) {
98
+ const a = expandBounds(allIslands[i].bounds, TOUCH_PADDING);
99
+ const b = expandBounds(allIslands[j].bounds, TOUCH_PADDING);
100
+ if (intersectsAABB(a, b)) {
101
+ uf.union(allIslands[i].id, allIslands[j].id);
102
+ }
103
+ }
104
+ }
105
+ // 3. Build desired clusters
106
+ const desiredClusters = new Map();
107
+ for (const island of allIslands) {
108
+ const root = uf.find(island.id);
109
+ if (!desiredClusters.has(root)) {
110
+ desiredClusters.set(root, []);
111
+ }
112
+ desiredClusters.get(root)?.push(island.id);
113
+ }
114
+ // 4. Build current clusters (based on existing island-groups)
115
+ const currentClusters = new Map(); // groupId → island IDs
116
+ for (const island of allIslands) {
117
+ if (island.parentId && islandGroupIds.has(island.parentId)) {
118
+ if (!currentClusters.has(island.parentId)) {
119
+ currentClusters.set(island.parentId, new Set());
120
+ }
121
+ currentClusters.get(island.parentId)?.add(island.id);
122
+ }
123
+ }
124
+ // 5. Find which island belongs to which current group
125
+ const islandToCurrentGroup = new Map();
126
+ for (const [groupId, members] of currentClusters) {
127
+ for (const memberId of members) {
128
+ islandToCurrentGroup.set(memberId, groupId);
129
+ }
130
+ }
131
+ // 6. Reconcile: compare desired vs current
132
+ const processedGroups = new Set();
133
+ for (const [, members] of desiredClusters) {
134
+ if (members.length < 2) {
135
+ // Single island — should not be in an island-group
136
+ const island = members[0];
137
+ const currentGroupId = islandToCurrentGroup.get(island);
138
+ if (currentGroupId && !processedGroups.has(currentGroupId)) {
139
+ processedGroups.add(currentGroupId);
140
+ commands.execute(createUngroupCommand(store, currentGroupId));
141
+ }
142
+ continue;
143
+ }
144
+ // Multi-island cluster — check if already correctly grouped
145
+ const memberSet = new Set(members);
146
+ let existingGroupId;
147
+ let needsRegroup = false;
148
+ // Check if all members share the same island-group
149
+ for (const id of members) {
150
+ const gid = islandToCurrentGroup.get(id);
151
+ if (!gid) {
152
+ needsRegroup = true;
153
+ break;
154
+ }
155
+ if (!existingGroupId) {
156
+ existingGroupId = gid;
157
+ }
158
+ else if (gid !== existingGroupId) {
159
+ needsRegroup = true;
160
+ break;
161
+ }
162
+ }
163
+ // Check if the existing group has extra members
164
+ if (existingGroupId && !needsRegroup) {
165
+ const currentMembers = currentClusters.get(existingGroupId);
166
+ if (currentMembers && currentMembers.size !== memberSet.size) {
167
+ needsRegroup = true;
168
+ }
169
+ }
170
+ if (!needsRegroup && existingGroupId) {
171
+ processedGroups.add(existingGroupId);
172
+ continue; // Already correctly grouped
173
+ }
174
+ // Dissolve any existing groups that overlap with this cluster
175
+ const groupsToDissolve = new Set();
176
+ for (const id of members) {
177
+ const gid = islandToCurrentGroup.get(id);
178
+ if (gid && !processedGroups.has(gid)) {
179
+ groupsToDissolve.add(gid);
180
+ }
181
+ }
182
+ for (const gid of groupsToDissolve) {
183
+ processedGroups.add(gid);
184
+ commands.execute(createUngroupCommand(store, gid));
185
+ }
186
+ // Create new group
187
+ const children = [];
188
+ for (const id of members) {
189
+ const shape = store.getShape(id);
190
+ if (shape)
191
+ children.push(shape);
192
+ }
193
+ if (children.length < 2)
194
+ continue;
195
+ const bounds = computeGroupBounds(children);
196
+ const groupId = generateId();
197
+ const groupShape = {
198
+ id: groupId,
199
+ type: "group",
200
+ x: bounds.x,
201
+ y: bounds.y,
202
+ width: bounds.width,
203
+ height: bounds.height,
204
+ style: { fill: "transparent", stroke: "transparent", strokeWidth: 0, opacity: 1 },
205
+ _isIslandGroup: true, // Mark as island-specific group
206
+ };
207
+ commands.execute(createGroupCommand(store, groupShape, members));
208
+ // Trigger merge animation on newly grouped islands
209
+ for (const id of members) {
210
+ triggerMergeAnimation(store, id);
211
+ }
212
+ }
213
+ // Dissolve any remaining island-groups not processed
214
+ for (const groupId of islandGroupIds) {
215
+ if (!processedGroups.has(groupId)) {
216
+ // Check if this group still has the right members
217
+ const currentMembers = currentClusters.get(groupId);
218
+ if (!currentMembers)
219
+ continue;
220
+ let allInSameCluster = true;
221
+ let clusterRoot;
222
+ for (const memberId of currentMembers) {
223
+ const root = uf.find(memberId);
224
+ if (!clusterRoot) {
225
+ clusterRoot = root;
226
+ }
227
+ else if (root !== clusterRoot) {
228
+ allInSameCluster = false;
229
+ break;
230
+ }
231
+ }
232
+ if (!allInSameCluster) {
233
+ commands.execute(createUngroupCommand(store, groupId));
234
+ }
235
+ }
236
+ }
237
+ }
238
+ /** Detach a single island from its island-group */
239
+ export function detachIsland(store, commands, islandId) {
240
+ const shape = store.getShape(islandId);
241
+ if (!shape || shape.type !== "island")
242
+ return;
243
+ const parentId = shape.parentId;
244
+ if (!parentId)
245
+ return;
246
+ const parent = store.getShapes().get(parentId);
247
+ if (!parent || parent.type !== "group" || !parent._isIslandGroup)
248
+ return;
249
+ // Get remaining children
250
+ const siblings = getChildShapes(store, parentId).filter((s) => s.id !== islandId);
251
+ // Ungroup the entire group
252
+ commands.execute(createUngroupCommand(store, parentId));
253
+ // If 2+ siblings remain, re-group them
254
+ if (siblings.length >= 2) {
255
+ const bounds = computeGroupBounds(siblings);
256
+ const newGroupId = generateId();
257
+ const newGroupShape = {
258
+ id: newGroupId,
259
+ type: "group",
260
+ x: bounds.x,
261
+ y: bounds.y,
262
+ width: bounds.width,
263
+ height: bounds.height,
264
+ style: { fill: "transparent", stroke: "transparent", strokeWidth: 0, opacity: 1 },
265
+ _isIslandGroup: true,
266
+ };
267
+ commands.execute(createGroupCommand(store, newGroupShape, siblings.map((s) => s.id)));
268
+ }
269
+ }
270
+ export function cleanupIslandGroupTimers() {
271
+ for (const timer of mergeTimers.values()) {
272
+ clearTimeout(timer);
273
+ }
274
+ mergeTimers.clear();
275
+ }
276
+ //# sourceMappingURL=auto-group.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auto-group.js","sourceRoot":"","sources":["../src/auto-group.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export { islandPlugin } from "./plugin.js";
2
+ //# 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,YAAY,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ export { islandPlugin } from "./plugin.js";
2
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC"}
@@ -0,0 +1,5 @@
1
+ import type { BoardStore } from "@edv4h/usketch-shared";
2
+ export declare function IslandMetaballLayer({ store }: {
3
+ store: BoardStore;
4
+ }): import("react/jsx-runtime").JSX.Element | null;
5
+ //# sourceMappingURL=island-metaball-layer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"island-metaball-layer.d.ts","sourceRoot":"","sources":["../src/island-metaball-layer.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC;AAsCxD,wBAAgB,mBAAmB,CAAC,EAAE,KAAK,EAAE,EAAE;IAAE,KAAK,EAAE,UAAU,CAAA;CAAE,kDAsJnE"}
@@ -0,0 +1,112 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { useCallback, useEffect, useRef, useState } from "react";
3
+ function collectBlobs(store) {
4
+ const blobs = [];
5
+ for (const [, shape] of store.getShapes()) {
6
+ if (shape.type !== "island")
7
+ continue;
8
+ let groupId;
9
+ const parentId = shape.parentId;
10
+ if (parentId) {
11
+ const parent = store.getShapes().get(parentId);
12
+ if (parent?.type === "group" && parent._isIslandGroup) {
13
+ groupId = parentId;
14
+ }
15
+ }
16
+ blobs.push({
17
+ id: shape.id,
18
+ x: shape.x,
19
+ y: shape.y,
20
+ width: shape.width,
21
+ height: shape.height,
22
+ color: shape.islandColor ?? "#a8d5ba",
23
+ groupId,
24
+ });
25
+ }
26
+ return blobs;
27
+ }
28
+ export function IslandMetaballLayer({ store }) {
29
+ const [blobs, setBlobs] = useState([]);
30
+ const [viewport, setViewport] = useState(store.getViewport());
31
+ const rafRef = useRef(0);
32
+ const update = useCallback(() => {
33
+ cancelAnimationFrame(rafRef.current);
34
+ rafRef.current = requestAnimationFrame(() => {
35
+ setBlobs(collectBlobs(store));
36
+ setViewport(store.getViewport());
37
+ });
38
+ }, [store]);
39
+ useEffect(() => {
40
+ update();
41
+ const unsub = store.subscribe(update);
42
+ return () => {
43
+ unsub();
44
+ cancelAnimationFrame(rafRef.current);
45
+ };
46
+ }, [store, update]);
47
+ if (blobs.length === 0)
48
+ return null;
49
+ // Group blobs by island-group
50
+ const groups = new Map();
51
+ const singles = [];
52
+ for (const blob of blobs) {
53
+ if (blob.groupId) {
54
+ if (!groups.has(blob.groupId)) {
55
+ groups.set(blob.groupId, []);
56
+ }
57
+ groups.get(blob.groupId)?.push(blob);
58
+ }
59
+ else {
60
+ singles.push(blob);
61
+ }
62
+ }
63
+ const R = 24;
64
+ return (_jsxs("div", { style: {
65
+ position: "absolute",
66
+ top: 0,
67
+ left: 0,
68
+ width: "100%",
69
+ height: "100%",
70
+ pointerEvents: "none",
71
+ overflow: "hidden",
72
+ }, children: [[...groups.entries()].map(([groupId, groupBlobs]) => {
73
+ let minX = Infinity;
74
+ let minY = Infinity;
75
+ let maxX = -Infinity;
76
+ let maxY = -Infinity;
77
+ for (const b of groupBlobs) {
78
+ minX = Math.min(minX, b.x);
79
+ minY = Math.min(minY, b.y);
80
+ maxX = Math.max(maxX, b.x + b.width);
81
+ maxY = Math.max(maxY, b.y + b.height);
82
+ }
83
+ const pad = 40;
84
+ const gx = minX - pad;
85
+ const gy = minY - pad;
86
+ const gw = maxX - minX + pad * 2;
87
+ const gh = maxY - minY + pad * 2;
88
+ const color = groupBlobs[0].color;
89
+ const filterId = `island-merge-${groupId}`;
90
+ return (_jsx("div", { style: {
91
+ position: "absolute",
92
+ left: gx * viewport.zoom + viewport.x,
93
+ top: gy * viewport.zoom + viewport.y,
94
+ width: gw * viewport.zoom,
95
+ height: gh * viewport.zoom,
96
+ pointerEvents: "none",
97
+ }, children: _jsxs("svg", { width: gw * viewport.zoom, height: gh * viewport.zoom, viewBox: `0 0 ${gw} ${gh}`, style: { display: "block", overflow: "visible" }, children: [_jsx("defs", { children: _jsxs("filter", { id: filterId, x: "-20%", y: "-20%", width: "140%", height: "140%", children: [_jsx("feGaussianBlur", { in: "SourceGraphic", stdDeviation: "5", result: "blur" }), _jsx("feColorMatrix", { in: "blur", type: "matrix", values: "1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 18 -7", result: "metaball" }), _jsx("feComposite", { in: "SourceGraphic", in2: "metaball", operator: "atop" })] }) }), _jsx("g", { filter: `url(#${filterId})`, style: {
98
+ filter: `drop-shadow(0 4px 8px rgba(0,0,0,0.12)) drop-shadow(0 0 12px ${color}66)`,
99
+ }, children: groupBlobs.map((blob) => (_jsx("rect", { x: blob.x - gx, y: blob.y - gy, width: blob.width, height: blob.height, rx: R, ry: R, fill: color, fillOpacity: 0.8, stroke: color, strokeWidth: 2 }, blob.id))) })] }) }, groupId));
100
+ }), singles.map((blob) => (_jsx("div", { style: {
101
+ position: "absolute",
102
+ left: blob.x * viewport.zoom + viewport.x,
103
+ top: blob.y * viewport.zoom + viewport.y,
104
+ width: blob.width * viewport.zoom,
105
+ height: blob.height * viewport.zoom,
106
+ borderRadius: R * viewport.zoom,
107
+ background: `linear-gradient(145deg, ${blob.color}cc, ${blob.color}88)`,
108
+ boxShadow: "0 4px 16px rgba(0,0,0,0.1)",
109
+ pointerEvents: "none",
110
+ } }, blob.id)))] }));
111
+ }
112
+ //# sourceMappingURL=island-metaball-layer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"island-metaball-layer.js","sourceRoot":"","sources":["../src/island-metaball-layer.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { BoundingBox, Point, ShapeData } from "@edv4h/usketch-shared";
2
+ export declare function renderIsland(data: ShapeData): import("react/jsx-runtime").JSX.Element;
3
+ export declare function getBoundsIsland(data: ShapeData): BoundingBox;
4
+ export declare function hitTestIsland(data: ShapeData, point: Point): boolean;
5
+ export declare function resizeIsland(data: ShapeData, handle: string, delta: Point): ShapeData;
6
+ export declare function createDefaultIsland(params: {
7
+ id: string;
8
+ x: number;
9
+ y: number;
10
+ }): ShapeData;
11
+ //# sourceMappingURL=island-render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"island-render.d.ts","sourceRoot":"","sources":["../src/island-render.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AA0B3E,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,2CAsB3C;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,SAAS,GAAG,WAAW,CAE5D;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,SAAS,EAAE,KAAK,EAAE,KAAK,GAAG,OAAO,CAOpE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,GAAG,SAAS,CAqBrF;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE;IAAE,EAAE,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAC;IAAC,CAAC,EAAE,MAAM,CAAA;CAAE,GAAG,SAAS,CAY3F"}
@@ -0,0 +1,83 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ const MIN_W = 100;
3
+ const MIN_H = 100;
4
+ // ── CSS injection for animations ──
5
+ let styleInjected = false;
6
+ function injectStyle() {
7
+ if (styleInjected)
8
+ return;
9
+ styleInjected = true;
10
+ const style = document.createElement("style");
11
+ style.textContent = `
12
+ @keyframes usketch-island-merge-pulse {
13
+ 0% { box-shadow: 0 0 0 0 rgba(100, 200, 150, 0.6), 0 4px 16px rgba(0,0,0,0.1); }
14
+ 50% { box-shadow: 0 0 24px 12px rgba(100, 200, 150, 0.3), 0 4px 16px rgba(0,0,0,0.1); }
15
+ 100% { box-shadow: 0 0 16px 4px rgba(100, 200, 150, 0.15), 0 4px 16px rgba(0,0,0,0.1); }
16
+ }
17
+ @keyframes usketch-island-merge-ring {
18
+ 0% { transform: scale(0.8); opacity: 0.8; }
19
+ 100% { transform: scale(2); opacity: 0; }
20
+ }
21
+ `;
22
+ document.head.appendChild(style);
23
+ }
24
+ export function renderIsland(data) {
25
+ injectStyle();
26
+ const justMerged = data._islandJustMerged;
27
+ // Island body is transparent — background is rendered by the metaball layer.
28
+ // This div serves as the container for child shapes and hit-test target.
29
+ return (_jsx("div", { style: {
30
+ width: "100%",
31
+ height: "100%",
32
+ borderRadius: 24,
33
+ background: "transparent",
34
+ boxSizing: "border-box",
35
+ position: "relative",
36
+ pointerEvents: "none",
37
+ overflow: "visible",
38
+ animation: justMerged ? "usketch-island-merge-pulse 600ms ease-out" : undefined,
39
+ } }));
40
+ }
41
+ export function getBoundsIsland(data) {
42
+ return { x: data.x, y: data.y, width: data.width, height: data.height };
43
+ }
44
+ export function hitTestIsland(data, point) {
45
+ return (point.x >= data.x &&
46
+ point.x <= data.x + data.width &&
47
+ point.y >= data.y &&
48
+ point.y <= data.y + data.height);
49
+ }
50
+ export function resizeIsland(data, handle, delta) {
51
+ const result = { ...data };
52
+ if (handle.includes("e")) {
53
+ result.width = Math.max(MIN_W, data.width + delta.x);
54
+ }
55
+ if (handle.includes("w")) {
56
+ const newWidth = Math.max(MIN_W, data.width - delta.x);
57
+ result.x = data.x + data.width - newWidth;
58
+ result.width = newWidth;
59
+ }
60
+ if (handle.includes("s")) {
61
+ result.height = Math.max(MIN_H, data.height + delta.y);
62
+ }
63
+ if (handle.includes("n")) {
64
+ const newHeight = Math.max(MIN_H, data.height - delta.y);
65
+ result.y = data.y + data.height - newHeight;
66
+ result.height = newHeight;
67
+ }
68
+ return result;
69
+ }
70
+ export function createDefaultIsland(params) {
71
+ return {
72
+ id: params.id,
73
+ type: "island",
74
+ x: params.x,
75
+ y: params.y,
76
+ width: 400,
77
+ height: 300,
78
+ style: { fill: "#a8d5ba", stroke: "#8bc4a0", strokeWidth: 2, opacity: 1 },
79
+ islandTitle: "Island",
80
+ islandColor: "#a8d5ba",
81
+ };
82
+ }
83
+ //# sourceMappingURL=island-render.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"island-render.js","sourceRoot":"","sources":["../src/island-render.tsx"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { UsketchPlugin } from "@edv4h/usketch-shared";
2
+ export declare const islandPlugin: 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,EAKX,aAAa,EACb,MAAM,uBAAuB,CAAC;AAwB/B,eAAO,MAAM,YAAY,EAAE,aA+L1B,CAAC"}
package/dist/plugin.js ADDED
@@ -0,0 +1,191 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { generateId } from "@edv4h/usketch-shared";
3
+ import { containsAABB, createAddShapeCommand, createReparentCommand } from "@edv4h/usketch-store";
4
+ import { cleanupIslandGroupTimers, detachIsland, reconcileIslandGroups } from "./auto-group.js";
5
+ import { IslandMetaballLayer } from "./island-metaball-layer.js";
6
+ import { createDefaultIsland, getBoundsIsland, hitTestIsland, renderIsland, resizeIsland, } from "./island-render.js";
7
+ function IslandIcon() {
8
+ return (_jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", children: [_jsx("ellipse", { cx: "10", cy: "14", rx: "8", ry: "4", fill: "#a8d5ba", stroke: "#8bc4a0", strokeWidth: "1.5" }), _jsx("circle", { cx: "8", cy: "10", r: "2", fill: "#6b9" }), _jsx("circle", { cx: "12", cy: "8", r: "3", fill: "#5a8" }), _jsx("line", { x1: "12", y1: "5", x2: "12", y2: "8", stroke: "#764", strokeWidth: "1.5" })] }));
9
+ }
10
+ export const islandPlugin = {
11
+ id: "usketch-plugin-shape-island",
12
+ name: "Island",
13
+ setup(ctx) {
14
+ // ── Shape registration ──
15
+ ctx.shapes.register("island", {
16
+ render: renderIsland,
17
+ getBounds: getBoundsIsland,
18
+ hitTest: hitTestIsland,
19
+ resize: resizeIsland,
20
+ createDefault: createDefaultIsland,
21
+ renderTarget: "html",
22
+ minSize: { width: 100, height: 100 },
23
+ });
24
+ // ── Metaball background layer (renders below shapes) ──
25
+ ctx.layers.register({
26
+ id: "island-metaball",
27
+ order: -1,
28
+ fixed: true,
29
+ render: () => _jsx(IslandMetaballLayer, { store: ctx.store }),
30
+ });
31
+ // ── Drawing tool ──
32
+ let drawState = null;
33
+ function onPointerDown(toolCtx, event) {
34
+ const id = generateId();
35
+ drawState = { startX: event.worldPoint.x, startY: event.worldPoint.y, shapeId: id };
36
+ const shape = createDefaultIsland({ id, x: event.worldPoint.x, y: event.worldPoint.y });
37
+ shape.width = 0;
38
+ shape.height = 0;
39
+ toolCtx.store.addShape(shape);
40
+ }
41
+ function onPointerMove(toolCtx, event) {
42
+ if (!drawState)
43
+ return;
44
+ const x = Math.min(drawState.startX, event.worldPoint.x);
45
+ const y = Math.min(drawState.startY, event.worldPoint.y);
46
+ const width = Math.abs(event.worldPoint.x - drawState.startX);
47
+ const height = Math.abs(event.worldPoint.y - drawState.startY);
48
+ toolCtx.store.updateShape(drawState.shapeId, { x, y, width, height });
49
+ }
50
+ function onPointerUp(toolCtx) {
51
+ if (!drawState)
52
+ return;
53
+ const shape = toolCtx.store.getShape(drawState.shapeId);
54
+ if (shape && shape.width > 10 && shape.height > 10) {
55
+ toolCtx.store.deleteShape(drawState.shapeId);
56
+ toolCtx.commands.execute(createAddShapeCommand(toolCtx.store, shape));
57
+ }
58
+ else {
59
+ toolCtx.store.deleteShape(drawState.shapeId);
60
+ }
61
+ drawState = null;
62
+ toolCtx.store.setActiveToolId("select");
63
+ }
64
+ ctx.tools.register("island-draw", {
65
+ icon: IslandIcon,
66
+ cursor: "crosshair",
67
+ shortcut: "i",
68
+ order: 6,
69
+ onPointerDown,
70
+ onPointerMove,
71
+ onPointerUp,
72
+ onDeactivate() {
73
+ if (drawState) {
74
+ ctx.store.deleteShape(drawState.shapeId);
75
+ drawState = null;
76
+ }
77
+ },
78
+ });
79
+ // ── Auto-parenting (same pattern as frame plugin) ──
80
+ const CONTAINER_TYPES = new Set(["frame", "island"]);
81
+ function autoReparent(shapeId) {
82
+ const shape = ctx.store.getShape(shapeId);
83
+ if (!shape || CONTAINER_TYPES.has(shape.type) || shape.type === "connector")
84
+ return;
85
+ const shapeBounds = { x: shape.x, y: shape.y, width: shape.width, height: shape.height };
86
+ let bestContainer = null;
87
+ for (const [id, candidate] of ctx.store.getShapes()) {
88
+ if (id === shapeId || !CONTAINER_TYPES.has(candidate.type))
89
+ continue;
90
+ const containerBounds = {
91
+ x: candidate.x,
92
+ y: candidate.y,
93
+ width: candidate.width,
94
+ height: candidate.height,
95
+ };
96
+ if (containsAABB(containerBounds, shapeBounds)) {
97
+ if (!bestContainer ||
98
+ candidate.width * candidate.height < bestContainer.width * bestContainer.height) {
99
+ bestContainer = candidate;
100
+ }
101
+ }
102
+ }
103
+ const currentParentId = shape.parentId;
104
+ const newParentId = bestContainer?.id;
105
+ if (currentParentId !== newParentId) {
106
+ ctx.commands.execute(createReparentCommand(ctx.store, [shapeId], newParentId ?? undefined));
107
+ }
108
+ }
109
+ // ── Auto-grouping: islands that touch form a group ──
110
+ const unsubMoveEnd = ctx.events.on("shapes:move-end", (data) => {
111
+ if (!data?.shapeIds)
112
+ return;
113
+ setTimeout(() => {
114
+ for (const shapeId of data.shapeIds) {
115
+ const shape = ctx.store.getShape(shapeId);
116
+ if (shape?.type === "island") {
117
+ // Island moved — adopt shapes now inside it
118
+ adoptContainedShapes(shapeId);
119
+ }
120
+ else {
121
+ // Non-island moved — reparent into nearest container
122
+ autoReparent(shapeId);
123
+ }
124
+ }
125
+ const prevSelection = [...ctx.store.getSelection()];
126
+ reconcileIslandGroups(ctx.store, ctx.commands);
127
+ if (prevSelection.length > 0) {
128
+ ctx.store.setSelection(prevSelection.filter((id) => ctx.store.getShape(id)));
129
+ }
130
+ }, 0);
131
+ });
132
+ // Adopt all non-container shapes fully inside an island
133
+ function adoptContainedShapes(islandId) {
134
+ const island = ctx.store.getShape(islandId);
135
+ if (!island || island.type !== "island")
136
+ return;
137
+ const islandBounds = {
138
+ x: island.x,
139
+ y: island.y,
140
+ width: island.width,
141
+ height: island.height,
142
+ };
143
+ for (const [childId, child] of ctx.store.getShapes()) {
144
+ if (childId === islandId || CONTAINER_TYPES.has(child.type) || child.type === "connector")
145
+ continue;
146
+ if (child.parentId === islandId)
147
+ continue;
148
+ const childBounds = { x: child.x, y: child.y, width: child.width, height: child.height };
149
+ if (containsAABB(islandBounds, childBounds)) {
150
+ ctx.commands.execute(createReparentCommand(ctx.store, [childId], islandId));
151
+ }
152
+ }
153
+ }
154
+ const unsubAdded = ctx.store.onMutation((event) => {
155
+ if (event.type !== "shape:added")
156
+ return;
157
+ const payload = event.payload;
158
+ if (!payload?.id)
159
+ return;
160
+ const shape = ctx.store.getShape(payload.id);
161
+ if (!shape)
162
+ return;
163
+ if (shape.type === "island") {
164
+ // Island was just created — adopt shapes already inside it
165
+ adoptContainedShapes(payload.id);
166
+ return;
167
+ }
168
+ if (CONTAINER_TYPES.has(shape.type) || shape.type === "connector")
169
+ return;
170
+ autoReparent(payload.id);
171
+ });
172
+ // ── Detach: Shift+D to detach selected island from its group ──
173
+ const unsubDetach = ctx.shortcuts.register("Shift+D", () => {
174
+ const selection = ctx.store.getSelection();
175
+ for (const id of selection) {
176
+ const shape = ctx.store.getShape(id);
177
+ if (shape?.type === "island") {
178
+ detachIsland(ctx.store, ctx.commands, id);
179
+ }
180
+ }
181
+ });
182
+ this.teardown = () => {
183
+ unsubMoveEnd();
184
+ unsubAdded();
185
+ unsubDetach();
186
+ cleanupIslandGroupTimers();
187
+ ctx.layers.unregister("island-metaball");
188
+ };
189
+ },
190
+ };
191
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.tsx"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@edv4h/usketch-plugin-shape-island",
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
+ "@edv4h/usketch-store": "1.0.0"
17
+ },
18
+ "peerDependencies": {
19
+ "react": ">=19"
20
+ },
21
+ "devDependencies": {
22
+ "@types/react": "19.2.14",
23
+ "typescript": "5.9.3",
24
+ "vitest": "3.2.4"
25
+ },
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "files": [
30
+ "dist",
31
+ "README.md"
32
+ ],
33
+ "license": "MIT",
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/EdV4H/usketch.git",
37
+ "directory": "plugins/usketch-plugin-shape-island"
38
+ },
39
+ "homepage": "https://github.com/EdV4H/usketch#readme",
40
+ "bugs": {
41
+ "url": "https://github.com/EdV4H/usketch/issues"
42
+ },
43
+ "scripts": {
44
+ "build": "tsc -p tsconfig.json",
45
+ "typecheck": "tsc --noEmit",
46
+ "test": "vitest run --passWithNoTests",
47
+ "clean": "rm -rf dist"
48
+ }
49
+ }