@edv4h/usketch-plugin-scatter 0.1.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.
Files changed (51) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +29 -0
  3. package/dist/animate.d.ts +13 -0
  4. package/dist/animate.d.ts.map +1 -0
  5. package/dist/animate.js +33 -0
  6. package/dist/animate.js.map +1 -0
  7. package/dist/command.d.ts +3 -0
  8. package/dist/command.d.ts.map +1 -0
  9. package/dist/command.js +26 -0
  10. package/dist/command.js.map +1 -0
  11. package/dist/engine.d.ts +7 -0
  12. package/dist/engine.d.ts.map +1 -0
  13. package/dist/engine.js +173 -0
  14. package/dist/engine.js.map +1 -0
  15. package/dist/hud/register-scatter-hud.d.ts +3 -0
  16. package/dist/hud/register-scatter-hud.d.ts.map +1 -0
  17. package/dist/hud/register-scatter-hud.js +60 -0
  18. package/dist/hud/register-scatter-hud.js.map +1 -0
  19. package/dist/index.d.ts +11 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +10 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/patterns.d.ts +17 -0
  24. package/dist/patterns.d.ts.map +1 -0
  25. package/dist/patterns.js +108 -0
  26. package/dist/patterns.js.map +1 -0
  27. package/dist/plugin.d.ts +3 -0
  28. package/dist/plugin.d.ts.map +1 -0
  29. package/dist/plugin.js +18 -0
  30. package/dist/plugin.js.map +1 -0
  31. package/dist/resolvers.d.ts +15 -0
  32. package/dist/resolvers.d.ts.map +1 -0
  33. package/dist/resolvers.js +56 -0
  34. package/dist/resolvers.js.map +1 -0
  35. package/dist/rng.d.ts +5 -0
  36. package/dist/rng.d.ts.map +1 -0
  37. package/dist/rng.js +26 -0
  38. package/dist/rng.js.map +1 -0
  39. package/dist/scatter-service.d.ts +15 -0
  40. package/dist/scatter-service.d.ts.map +1 -0
  41. package/dist/scatter-service.js +23 -0
  42. package/dist/scatter-service.js.map +1 -0
  43. package/dist/scatter-state.d.ts +16 -0
  44. package/dist/scatter-state.d.ts.map +1 -0
  45. package/dist/scatter-state.js +37 -0
  46. package/dist/scatter-state.js.map +1 -0
  47. package/dist/types.d.ts +79 -0
  48. package/dist/types.d.ts.map +1 -0
  49. package/dist/types.js +2 -0
  50. package/dist/types.js.map +1 -0
  51. package/package.json +45 -0
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.
package/README.md ADDED
@@ -0,0 +1,29 @@
1
+ # @edv4h/usketch-plugin-scatter
2
+
3
+ 「関連する Shape をぶちまける」— an extensible engine that scatters a seed shape's
4
+ **related** shapes (and/or freshly spawned ones) outward across the canvas, as one
5
+ undoable step, optionally animated.
6
+
7
+ ```ts
8
+ import { createScatterPlugin, getScatterApi } from "@edv4h/usketch-plugin-scatter";
9
+ // register createScatterPlugin() in your app's plugin list.
10
+
11
+ const api = getScatterApi(app.services);
12
+ await api?.scatter({
13
+ seedId, // defaults to the sole selected shape
14
+ relation: "connectors", // built-in resolver, or pass items / a RelationResolver
15
+ pattern: "radial", // radial | scatter | unoverlap | grid, or a ScatterPattern
16
+ animate: true,
17
+ });
18
+ ```
19
+
20
+ - **Related = pluggable** — pass explicit `items` (existing ids ∪ new-shape specs) or
21
+ a `relation` resolver (`connectors`, `children`, or your own via `registerResolver`).
22
+ - **New shapes** — `{ kind: "new", spec: { type, width, height, … } }` items are
23
+ spawned + placed in the same undoable command.
24
+ - **Patterns** — `radial` / `scatter` (random + rotation) / `unoverlap` (delegates to
25
+ `findFreePosition`) / `grid`; add more with `registerPattern`.
26
+ - **Animation** — `animate` + `durationMs` + `easing` for a fly-out tween.
27
+
28
+ UI: registered on the Control HUD (`ぶちまけ設定` + the `関連Shapeをぶちまける` action,
29
+ enabled when exactly one shape is selected).
@@ -0,0 +1,13 @@
1
+ import type { BoardStore, Point } from "@edv4h/usketch-shared";
2
+ export interface TweenTarget {
3
+ id: string;
4
+ from: Point;
5
+ to: Point;
6
+ }
7
+ /** ease-in-out-cubic — the same feel as the store's viewport animation. */
8
+ export declare function easeInOutCubic(t: number): number;
9
+ export declare function animatePositions(store: BoardStore, targets: TweenTarget[], opts: {
10
+ durationMs: number;
11
+ easing: (t: number) => number;
12
+ }): Promise<void>;
13
+ //# sourceMappingURL=animate.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animate.d.ts","sourceRoot":"","sources":["../src/animate.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,MAAM,uBAAuB,CAAC;AAE/D,MAAM,WAAW,WAAW;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,KAAK,CAAC;IACZ,EAAE,EAAE,KAAK,CAAC;CACV;AAED,2EAA2E;AAC3E,wBAAgB,cAAc,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEhD;AAQD,wBAAgB,gBAAgB,CAC/B,KAAK,EAAE,UAAU,EACjB,OAAO,EAAE,WAAW,EAAE,EACtB,IAAI,EAAE;IAAE,UAAU,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAA;CAAE,GACzD,OAAO,CAAC,IAAI,CAAC,CAsBf"}
@@ -0,0 +1,33 @@
1
+ /** ease-in-out-cubic — the same feel as the store's viewport animation. */
2
+ export function easeInOutCubic(t) {
3
+ return t < 0.5 ? 4 * t * t * t : 1 - (-2 * t + 2) ** 3 / 2;
4
+ }
5
+ function prefersReducedMotion() {
6
+ return (typeof matchMedia !== "undefined" && matchMedia("(prefers-reduced-motion: reduce)").matches);
7
+ }
8
+ export function animatePositions(store, targets, opts) {
9
+ const raf = typeof requestAnimationFrame !== "undefined" ? requestAnimationFrame : null;
10
+ if (!raf || opts.durationMs <= 0 || targets.length === 0 || prefersReducedMotion()) {
11
+ return Promise.resolve();
12
+ }
13
+ const now = () => (typeof performance !== "undefined" ? performance.now() : 0);
14
+ const start = now();
15
+ return new Promise((resolve) => {
16
+ const step = () => {
17
+ const t = Math.min(1, (now() - start) / opts.durationMs);
18
+ const e = opts.easing(t);
19
+ for (const tg of targets) {
20
+ store.updateShape(tg.id, {
21
+ x: tg.from.x + (tg.to.x - tg.from.x) * e,
22
+ y: tg.from.y + (tg.to.y - tg.from.y) * e,
23
+ });
24
+ }
25
+ if (t < 1)
26
+ raf(step);
27
+ else
28
+ resolve();
29
+ };
30
+ raf(step);
31
+ });
32
+ }
33
+ //# sourceMappingURL=animate.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"animate.js","sourceRoot":"","sources":["../src/animate.ts"],"names":[],"mappings":"AAaA,2EAA2E;AAC3E,MAAM,UAAU,cAAc,CAAC,CAAS;IACvC,OAAO,CAAC,GAAG,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAC5D,CAAC;AAED,SAAS,oBAAoB;IAC5B,OAAO,CACN,OAAO,UAAU,KAAK,WAAW,IAAI,UAAU,CAAC,kCAAkC,CAAC,CAAC,OAAO,CAC3F,CAAC;AACH,CAAC;AAED,MAAM,UAAU,gBAAgB,CAC/B,KAAiB,EACjB,OAAsB,EACtB,IAA2D;IAE3D,MAAM,GAAG,GAAG,OAAO,qBAAqB,KAAK,WAAW,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,IAAI,CAAC;IACxF,IAAI,CAAC,GAAG,IAAI,IAAI,CAAC,UAAU,IAAI,CAAC,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,oBAAoB,EAAE,EAAE,CAAC;QACpF,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC;IAC1B,CAAC;IACD,MAAM,GAAG,GAAG,GAAG,EAAE,CAAC,CAAC,OAAO,WAAW,KAAK,WAAW,CAAC,CAAC,CAAC,WAAW,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/E,MAAM,KAAK,GAAG,GAAG,EAAE,CAAC;IACpB,OAAO,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE;QACpC,MAAM,IAAI,GAAG,GAAG,EAAE;YACjB,MAAM,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,GAAG,IAAI,CAAC,UAAU,CAAC,CAAC;YACzD,MAAM,CAAC,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YACzB,KAAK,MAAM,EAAE,IAAI,OAAO,EAAE,CAAC;gBAC1B,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC,EAAE,EAAE;oBACxB,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;oBACxC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC;iBACxC,CAAC,CAAC;YACJ,CAAC;YACD,IAAI,CAAC,GAAG,CAAC;gBAAE,GAAG,CAAC,IAAI,CAAC,CAAC;;gBAChB,OAAO,EAAE,CAAC;QAChB,CAAC,CAAC;QACF,GAAG,CAAC,IAAI,CAAC,CAAC;IACX,CAAC,CAAC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ import type { BoardStore, Command, ShapeData } from "@edv4h/usketch-shared";
2
+ export declare function createScatterCommand(store: BoardStore, newShapes: ShapeData[], existingBefore: Map<string, ShapeData>, existingAfter: Map<string, Partial<Omit<ShapeData, "id">>>): Command;
3
+ //# sourceMappingURL=command.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAKA,OAAO,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAQ5E,wBAAgB,oBAAoB,CACnC,KAAK,EAAE,UAAU,EACjB,SAAS,EAAE,SAAS,EAAE,EACtB,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,SAAS,CAAC,EACtC,aAAa,EAAE,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC,CAAC,GACxD,OAAO,CAcT"}
@@ -0,0 +1,26 @@
1
+ /** ShapeData without the immutable `id` — the patch shape `updateShape` accepts. */
2
+ function withoutId(shape) {
3
+ const { id: _id, ...rest } = shape;
4
+ return rest;
5
+ }
6
+ export function createScatterCommand(store, newShapes, existingBefore, existingAfter) {
7
+ return {
8
+ execute() {
9
+ for (const shape of newShapes) {
10
+ if (store.getShape(shape.id))
11
+ store.updateShape(shape.id, withoutId(shape));
12
+ else
13
+ store.addShape(shape);
14
+ }
15
+ for (const [id, patch] of existingAfter)
16
+ store.updateShape(id, patch);
17
+ },
18
+ undo() {
19
+ for (const shape of newShapes)
20
+ store.deleteShape(shape.id);
21
+ for (const [id, before] of existingBefore)
22
+ store.updateShape(id, withoutId(before));
23
+ },
24
+ };
25
+ }
26
+ //# sourceMappingURL=command.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"command.js","sourceRoot":"","sources":["../src/command.ts"],"names":[],"mappings":"AAOA,oFAAoF;AACpF,SAAS,SAAS,CAAC,KAAgB;IAClC,MAAM,EAAE,EAAE,EAAE,GAAG,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,CAAC;IACnC,OAAO,IAAI,CAAC;AACb,CAAC;AAED,MAAM,UAAU,oBAAoB,CACnC,KAAiB,EACjB,SAAsB,EACtB,cAAsC,EACtC,aAA0D;IAE1D,OAAO;QACN,OAAO;YACN,KAAK,MAAM,KAAK,IAAI,SAAS,EAAE,CAAC;gBAC/B,IAAI,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,CAAC;oBAAE,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,EAAE,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC;;oBACvE,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;YAC5B,CAAC;YACD,KAAK,MAAM,CAAC,EAAE,EAAE,KAAK,CAAC,IAAI,aAAa;gBAAE,KAAK,CAAC,WAAW,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;QACvE,CAAC;QACD,IAAI;YACH,KAAK,MAAM,KAAK,IAAI,SAAS;gBAAE,KAAK,CAAC,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;YAC3D,KAAK,MAAM,CAAC,EAAE,EAAE,MAAM,CAAC,IAAI,cAAc;gBAAE,KAAK,CAAC,WAAW,CAAC,EAAE,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC;QACrF,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,7 @@
1
+ import type { ScatterDeps, ScatterRequest, ScatterResult } from "./types.js";
2
+ /**
3
+ * Scatter a seed shape's related shapes (and/or freshly spawned shapes) across the
4
+ * canvas using the chosen pattern, as one undoable step. Optionally animated.
5
+ */
6
+ export declare function scatter(deps: ScatterDeps, request: ScatterRequest): Promise<ScatterResult>;
7
+ //# sourceMappingURL=engine.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.d.ts","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"AAiBA,OAAO,KAAK,EAGX,WAAW,EAEX,cAAc,EACd,aAAa,EACb,MAAM,YAAY,CAAC;AA0JpB;;;GAGG;AACH,wBAAsB,OAAO,CAAC,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAmChG"}
package/dist/engine.js ADDED
@@ -0,0 +1,173 @@
1
+ // The scatter orchestrator: resolve the seed + related items, run the chosen
2
+ // pattern to place them, spawn any new shapes, and apply everything as ONE
3
+ // undoable command (optionally animated first). Pure over its `deps` — the plugin
4
+ // and the service both call `scatter()`.
5
+ import { generateId, getRotatedAABB, safeRotation, } from "@edv4h/usketch-shared";
6
+ import { animatePositions, easeInOutCubic } from "./animate.js";
7
+ import { createScatterCommand } from "./command.js";
8
+ import { getScatterPattern } from "./patterns.js";
9
+ import { resolveItems } from "./resolvers.js";
10
+ import { hashSeed, mulberry32 } from "./rng.js";
11
+ const DEFAULT_SPACING = 24;
12
+ const DEFAULT_DURATION = 450;
13
+ function boundsOf(deps, shape) {
14
+ const def = deps.shapes.get(shape.type);
15
+ return def
16
+ ? def.getBounds(shape)
17
+ : { x: shape.x, y: shape.y, width: shape.width, height: shape.height };
18
+ }
19
+ function occupiedAABB(deps, shape) {
20
+ const b = boundsOf(deps, shape);
21
+ const rot = safeRotation(shape.rotation);
22
+ return rot ? getRotatedAABB(b, rot) : b;
23
+ }
24
+ /** The sole selected shape id, or undefined when the selection isn't exactly one. */
25
+ function soleSelection(deps) {
26
+ const sel = deps.store.getSelection();
27
+ return sel.size === 1 ? [...sel][0] : undefined;
28
+ }
29
+ /** Build the full plan: pattern placements → new shapes + existing move patches. */
30
+ function planScatter(deps, seedShape, request) {
31
+ const { store, shapes } = deps;
32
+ const items = resolveItems({ store }, seedShape.id, request.relation, request.items);
33
+ const seedBounds = boundsOf(deps, seedShape);
34
+ const seedCenter = {
35
+ x: seedBounds.x + seedBounds.width / 2,
36
+ y: seedBounds.y + seedBounds.height / 2,
37
+ };
38
+ // Occupied = every shape that ISN'T a moving item (so items avoid the seed +
39
+ // unrelated shapes). Used by the `unoverlap` pattern.
40
+ const movingIds = new Set(items.filter((i) => i.kind === "existing").map((i) => i.id));
41
+ const occupied = [];
42
+ for (const [id, s] of store.getShapes()) {
43
+ if (!movingIds.has(id))
44
+ occupied.push(occupiedAABB(deps, s));
45
+ }
46
+ const patternItems = [];
47
+ const sources = new Map();
48
+ const newIds = new Set();
49
+ items.forEach((it, i) => {
50
+ if (it.kind === "existing") {
51
+ const s = store.getShape(it.id);
52
+ if (!s)
53
+ return; // stale id — skip
54
+ const b = boundsOf(deps, s);
55
+ patternItems.push({ key: it.id, bounds: { x: 0, y: 0, width: b.width, height: b.height } });
56
+ sources.set(it.id, { kind: "existing", id: it.id });
57
+ }
58
+ else {
59
+ const { width, height } = it.spec;
60
+ if (!(width > 0) || !(height > 0)) {
61
+ throw new Error("[scatter] a new shape spec must carry positive width/height");
62
+ }
63
+ // Resolve + guard the id up-front so a caller-supplied `spec.id` can never
64
+ // overwrite an existing shape (addShape overwrites) — which undo would then
65
+ // wrongly delete — nor collide with another new spec in the same run.
66
+ const id = typeof it.spec.id === "string" ? it.spec.id : generateId();
67
+ if (store.getShape(id) || newIds.has(id)) {
68
+ throw new Error(`[scatter] new shape id collides with an existing/duplicate id: ${id}`);
69
+ }
70
+ newIds.add(id);
71
+ const key = `new:${i}`;
72
+ patternItems.push({ key, bounds: { x: 0, y: 0, width, height } });
73
+ sources.set(key, { kind: "new", id, spec: it.spec });
74
+ }
75
+ });
76
+ const patternFn = typeof request.pattern === "function" ? request.pattern : getScatterPattern(request.pattern);
77
+ if (!patternFn)
78
+ throw new Error(`[scatter] unknown pattern: ${String(request.pattern)}`);
79
+ const rng = mulberry32(hashSeed(request.seed ?? Math.floor(Math.random() * 0xffffffff)));
80
+ const placements = patternFn({
81
+ seedBounds,
82
+ seedCenter,
83
+ items: patternItems,
84
+ occupied,
85
+ spacing: request.spacing ?? DEFAULT_SPACING,
86
+ rng,
87
+ });
88
+ const plan = {
89
+ newShapes: [],
90
+ newStarts: new Map(),
91
+ existingBefore: new Map(),
92
+ existingAfter: new Map(),
93
+ tweens: [],
94
+ movedIds: [],
95
+ createdIds: [],
96
+ };
97
+ for (const p of placements) {
98
+ const src = sources.get(p.key);
99
+ if (!src)
100
+ continue;
101
+ if (src.kind === "existing") {
102
+ const s = store.getShape(src.id);
103
+ if (!s)
104
+ continue;
105
+ plan.existingBefore.set(src.id, { ...s });
106
+ const patch = { x: p.x, y: p.y };
107
+ if (p.rotation !== undefined)
108
+ patch.rotation = p.rotation;
109
+ plan.existingAfter.set(src.id, patch);
110
+ plan.movedIds.push(src.id);
111
+ plan.tweens.push({ id: src.id, from: { x: s.x, y: s.y }, to: { x: p.x, y: p.y } });
112
+ }
113
+ else {
114
+ const { spec, id } = src;
115
+ const base = shapes.get(spec.type)?.createDefault({ id, x: p.x, y: p.y });
116
+ if (!base)
117
+ throw new Error(`[scatter] unknown shape type: ${spec.type}`);
118
+ const { id: _sid, type: _stype, width, height, style: specStyle, ...rest } = spec;
119
+ const shape = {
120
+ ...base,
121
+ ...rest,
122
+ id,
123
+ type: spec.type,
124
+ x: p.x,
125
+ y: p.y,
126
+ width,
127
+ height,
128
+ style: { ...base.style, ...(specStyle ?? {}) },
129
+ };
130
+ if (p.rotation !== undefined)
131
+ shape.rotation = p.rotation;
132
+ plan.newShapes.push(shape);
133
+ plan.createdIds.push(id);
134
+ const start = { x: seedCenter.x - width / 2, y: seedCenter.y - height / 2 };
135
+ plan.newStarts.set(id, start);
136
+ plan.tweens.push({ id, from: start, to: { x: p.x, y: p.y } });
137
+ }
138
+ }
139
+ return plan;
140
+ }
141
+ /**
142
+ * Scatter a seed shape's related shapes (and/or freshly spawned shapes) across the
143
+ * canvas using the chosen pattern, as one undoable step. Optionally animated.
144
+ */
145
+ export async function scatter(deps, request) {
146
+ const seedId = request.seedId ?? soleSelection(deps);
147
+ if (!seedId) {
148
+ throw new Error("[scatter] no seed: select exactly one shape or pass request.seedId");
149
+ }
150
+ const seedShape = deps.store.getShape(seedId);
151
+ if (!seedShape)
152
+ throw new Error(`[scatter] seed shape not found: ${seedId}`);
153
+ const plan = planScatter(deps, seedShape, request);
154
+ if (plan.movedIds.length === 0 && plan.createdIds.length === 0) {
155
+ return { movedIds: [], createdIds: [] };
156
+ }
157
+ const command = createScatterCommand(deps.store, plan.newShapes, plan.existingBefore, plan.existingAfter);
158
+ if (request.animate) {
159
+ // Spawn new shapes at their START position (raw add — not a command), tween
160
+ // everything to the target, then commit the command (idempotent) for one undo.
161
+ for (const shape of plan.newShapes) {
162
+ const start = plan.newStarts.get(shape.id);
163
+ deps.store.addShape(start ? { ...shape, x: start.x, y: start.y } : shape);
164
+ }
165
+ await animatePositions(deps.store, plan.tweens, {
166
+ durationMs: request.durationMs ?? DEFAULT_DURATION,
167
+ easing: request.easing ?? easeInOutCubic,
168
+ });
169
+ }
170
+ deps.commands.execute(command);
171
+ return { movedIds: plan.movedIds, createdIds: plan.createdIds };
172
+ }
173
+ //# sourceMappingURL=engine.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { PluginContext } from "@edv4h/usketch-shared";
2
+ export declare function registerScatterHud(ctx: PluginContext): () => void;
3
+ //# sourceMappingURL=register-scatter-hud.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-scatter-hud.d.ts","sourceRoot":"","sources":["../../src/hud/register-scatter-hud.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAM3D,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,IAAI,CA2DjE"}
@@ -0,0 +1,60 @@
1
+ import { listScatterPatterns } from "../patterns.js";
2
+ import { listRelationResolvers } from "../resolvers.js";
3
+ import { getScatterApi } from "../scatter-service.js";
4
+ import { scatterStateStore } from "../scatter-state.js";
5
+ export function registerScatterHud(ctx) {
6
+ const offs = [];
7
+ offs.push(ctx.hud.registerSettings({
8
+ id: "usketch-scatter:opts",
9
+ label: "ぶちまけ設定",
10
+ fields: [
11
+ {
12
+ name: "pattern",
13
+ label: "パターン",
14
+ type: "enum",
15
+ options: listScatterPatterns().map((p) => ({ value: p, label: p })),
16
+ },
17
+ {
18
+ name: "relation",
19
+ label: "関連",
20
+ type: "enum",
21
+ options: listRelationResolvers().map((r) => ({ value: r, label: r })),
22
+ },
23
+ { name: "spacing", label: "間隔", type: "number", min: 0, max: 400, step: 4 },
24
+ { name: "animate", label: "アニメーション", type: "boolean" },
25
+ { name: "durationMs", label: "時間(ms)", type: "number", min: 0, max: 3000, step: 50 },
26
+ ],
27
+ get: (name) => scatterStateStore.get()[name],
28
+ set: (name, value) => scatterStateStore.set({ [name]: value }),
29
+ subscribe: scatterStateStore.subscribe,
30
+ }));
31
+ offs.push(ctx.actions.register({
32
+ id: "scatter:spill",
33
+ group: "ぶちまける",
34
+ label: "関連Shapeをぶちまける",
35
+ isEnabled: () => ctx.store.getSelection().size === 1,
36
+ // Return the Promise so the HUD action runner surfaces failures (e.g. the
37
+ // seed deleted between selection read and run) in its logs instead of them
38
+ // becoming unhandled rejections.
39
+ run: async () => {
40
+ const api = getScatterApi(ctx.services);
41
+ const sel = ctx.store.getSelection();
42
+ if (!api || sel.size !== 1)
43
+ return;
44
+ const st = scatterStateStore.get();
45
+ await api.scatter({
46
+ seedId: [...sel][0],
47
+ pattern: st.pattern,
48
+ relation: st.relation,
49
+ spacing: st.spacing,
50
+ animate: st.animate,
51
+ durationMs: st.durationMs,
52
+ });
53
+ },
54
+ }));
55
+ return () => {
56
+ for (const off of offs)
57
+ off();
58
+ };
59
+ }
60
+ //# sourceMappingURL=register-scatter-hud.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"register-scatter-hud.js","sourceRoot":"","sources":["../../src/hud/register-scatter-hud.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ export { animatePositions, easeInOutCubic, type TweenTarget } from "./animate.js";
2
+ export { createScatterCommand } from "./command.js";
3
+ export { scatter } from "./engine.js";
4
+ export { getScatterPattern, gridPattern, listScatterPatterns, radialPattern, registerScatterPattern, scatterPattern, unoverlapPattern, } from "./patterns.js";
5
+ export { createScatterPlugin } from "./plugin.js";
6
+ export { connectorNeighbors, getRelationResolver, listRelationResolvers, parentChildren, registerRelationResolver, resolveItems, } from "./resolvers.js";
7
+ export { hashSeed, mulberry32 } from "./rng.js";
8
+ export { createScatterApi, getScatterApi, type ScatterApi, scatterService, } from "./scatter-service.js";
9
+ export { type ReactiveStore, type ScatterState, scatterStateStore, } from "./scatter-state.js";
10
+ export type { NewShapeSpec, PatternContext, PatternItem, Placement, RelationResolver, ScatterDeps, ScatterItem, ScatterPattern, ScatterRequest, ScatterResult, } from "./types.js";
11
+ //# 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,gBAAgB,EAAE,cAAc,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC;AAClF,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EACN,iBAAiB,EACjB,WAAW,EACX,mBAAmB,EACnB,aAAa,EACb,sBAAsB,EACtB,cAAc,EACd,gBAAgB,GAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,EACd,wBAAwB,EACxB,YAAY,GACZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EACN,gBAAgB,EAChB,aAAa,EACb,KAAK,UAAU,EACf,cAAc,GACd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EACN,KAAK,aAAa,EAClB,KAAK,YAAY,EACjB,iBAAiB,GACjB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACX,YAAY,EACZ,cAAc,EACd,WAAW,EACX,SAAS,EACT,gBAAgB,EAChB,WAAW,EACX,WAAW,EACX,cAAc,EACd,cAAc,EACd,aAAa,GACb,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ export { animatePositions, easeInOutCubic } from "./animate.js";
2
+ export { createScatterCommand } from "./command.js";
3
+ export { scatter } from "./engine.js";
4
+ export { getScatterPattern, gridPattern, listScatterPatterns, radialPattern, registerScatterPattern, scatterPattern, unoverlapPattern, } from "./patterns.js";
5
+ export { createScatterPlugin } from "./plugin.js";
6
+ export { connectorNeighbors, getRelationResolver, listRelationResolvers, parentChildren, registerRelationResolver, resolveItems, } from "./resolvers.js";
7
+ export { hashSeed, mulberry32 } from "./rng.js";
8
+ export { createScatterApi, getScatterApi, scatterService, } from "./scatter-service.js";
9
+ export { scatterStateStore, } from "./scatter-state.js";
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAoB,MAAM,cAAc,CAAC;AAClF,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EACN,iBAAiB,EACjB,WAAW,EACX,mBAAmB,EACnB,aAAa,EACb,sBAAsB,EACtB,cAAc,EACd,gBAAgB,GAChB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EACN,kBAAkB,EAClB,mBAAmB,EACnB,qBAAqB,EACrB,cAAc,EACd,wBAAwB,EACxB,YAAY,GACZ,MAAM,gBAAgB,CAAC;AACxB,OAAO,EAAE,QAAQ,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAChD,OAAO,EACN,gBAAgB,EAChB,aAAa,EAEb,cAAc,GACd,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAGN,iBAAiB,GACjB,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { ScatterPattern } from "./types.js";
2
+ /** Even ring(s) around the seed centre. Ring radius grows to fit all items without
3
+ * overlap (arc length per item ≥ item size + spacing). */
4
+ export declare const radialPattern: ScatterPattern;
5
+ /** Random offsets within a radius + a small random rotation — the chaotic
6
+ * "dumped out" look. Deterministic given the seed (uses ctx.rng). */
7
+ export declare const scatterPattern: ScatterPattern;
8
+ /** Non-overlapping cluster: place each item near the seed, then push it to the
9
+ * nearest free spot (delegates to findFreePosition), accumulating placed AABBs so
10
+ * items don't collide with each other either. */
11
+ export declare const unoverlapPattern: ScatterPattern;
12
+ /** Square-ish grid centred on the seed; cells sized to the largest item + spacing. */
13
+ export declare const gridPattern: ScatterPattern;
14
+ export declare function registerScatterPattern(name: string, pattern: ScatterPattern): () => void;
15
+ export declare function getScatterPattern(name: string): ScatterPattern | undefined;
16
+ export declare function listScatterPatterns(): string[];
17
+ //# sourceMappingURL=patterns.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.d.ts","sourceRoot":"","sources":["../src/patterns.ts"],"names":[],"mappings":"AAMA,OAAO,KAAK,EAA0B,cAAc,EAAE,MAAM,YAAY,CAAC;AAsBzE;2DAC2D;AAC3D,eAAO,MAAM,aAAa,EAAE,cAgB3B,CAAC;AAEF;sEACsE;AACtE,eAAO,MAAM,cAAc,EAAE,cAkB5B,CAAC;AAEF;;kDAEkD;AAClD,eAAO,MAAM,gBAAgB,EAAE,cAoB9B,CAAC;AAEF,sFAAsF;AACtF,eAAO,MAAM,WAAW,EAAE,cAczB,CAAC;AASF,wBAAgB,sBAAsB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,IAAI,CAKxF;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAE1E;AAED,wBAAgB,mBAAmB,IAAI,MAAM,EAAE,CAE9C"}
@@ -0,0 +1,108 @@
1
+ // Layout patterns: pure functions placing the related items around the seed. All
2
+ // take a PatternContext and return Placements (top-left x/y + optional rotation).
3
+ // Registerable so hosts can add their own; built-ins self-register on import.
4
+ import { findFreePosition } from "@edv4h/usketch-shape-utils";
5
+ const TAU = Math.PI * 2;
6
+ /** Largest single item dimension (a lower bound for spacing between items). */
7
+ function maxItemDim(items) {
8
+ let m = 1;
9
+ for (const it of items)
10
+ m = Math.max(m, it.bounds.width, it.bounds.height);
11
+ return m;
12
+ }
13
+ /** Convert a target CENTER to a top-left placement for an item of these bounds. */
14
+ function atCenter(key, cx, cy, b, rotation) {
15
+ return { key, x: cx - b.width / 2, y: cy - b.height / 2, rotation };
16
+ }
17
+ /** Even ring(s) around the seed centre. Ring radius grows to fit all items without
18
+ * overlap (arc length per item ≥ item size + spacing). */
19
+ export const radialPattern = ({ seedBounds, seedCenter, items, spacing }) => {
20
+ const n = items.length;
21
+ if (n === 0)
22
+ return [];
23
+ const dim = maxItemDim(items);
24
+ const seedR = Math.max(seedBounds.width, seedBounds.height) / 2;
25
+ const circumferenceNeed = n * (dim + spacing);
26
+ const radius = Math.max(seedR + dim / 2 + spacing, circumferenceNeed / TAU);
27
+ return items.map((it, i) => {
28
+ const a = (i / n) * TAU;
29
+ return atCenter(it.key, seedCenter.x + Math.cos(a) * radius, seedCenter.y + Math.sin(a) * radius, it.bounds);
30
+ });
31
+ };
32
+ /** Random offsets within a radius + a small random rotation — the chaotic
33
+ * "dumped out" look. Deterministic given the seed (uses ctx.rng). */
34
+ export const scatterPattern = ({ seedBounds, seedCenter, items, spacing, rng }) => {
35
+ const n = items.length;
36
+ if (n === 0)
37
+ return [];
38
+ const dim = maxItemDim(items);
39
+ const seedR = Math.max(seedBounds.width, seedBounds.height) / 2;
40
+ const spread = Math.max(seedR * 2 + dim, Math.sqrt(n) * (dim + spacing));
41
+ return items.map((it) => {
42
+ const ang = rng() * TAU;
43
+ const dist = (0.35 + 0.65 * rng()) * spread;
44
+ const rotation = (rng() * 2 - 1) * 22; // ±22°
45
+ return atCenter(it.key, seedCenter.x + Math.cos(ang) * dist, seedCenter.y + Math.sin(ang) * dist, it.bounds, rotation);
46
+ });
47
+ };
48
+ /** Non-overlapping cluster: place each item near the seed, then push it to the
49
+ * nearest free spot (delegates to findFreePosition), accumulating placed AABBs so
50
+ * items don't collide with each other either. */
51
+ export const unoverlapPattern = ({ seedCenter, items, occupied, spacing }) => {
52
+ const placed = [...occupied];
53
+ const out = [];
54
+ for (const it of items) {
55
+ const desired = {
56
+ x: seedCenter.x - it.bounds.width / 2,
57
+ y: seedCenter.y - it.bounds.height / 2,
58
+ width: it.bounds.width,
59
+ height: it.bounds.height,
60
+ };
61
+ const free = findFreePosition({
62
+ desired,
63
+ occupied: placed,
64
+ strategy: "ring",
65
+ step: Math.max(8, spacing),
66
+ });
67
+ placed.push(free);
68
+ out.push({ key: it.key, x: free.x, y: free.y });
69
+ }
70
+ return out;
71
+ };
72
+ /** Square-ish grid centred on the seed; cells sized to the largest item + spacing. */
73
+ export const gridPattern = ({ seedCenter, items, spacing }) => {
74
+ const n = items.length;
75
+ if (n === 0)
76
+ return [];
77
+ const dim = maxItemDim(items);
78
+ const cell = dim + spacing;
79
+ const cols = Math.ceil(Math.sqrt(n));
80
+ const rows = Math.ceil(n / cols);
81
+ const startX = seedCenter.x - (cols * cell) / 2 + cell / 2;
82
+ const startY = seedCenter.y - (rows * cell) / 2 + cell / 2;
83
+ return items.map((it, i) => {
84
+ const c = i % cols;
85
+ const r = Math.floor(i / cols);
86
+ return atCenter(it.key, startX + c * cell, startY + r * cell, it.bounds);
87
+ });
88
+ };
89
+ const PATTERNS = new Map([
90
+ ["radial", radialPattern],
91
+ ["scatter", scatterPattern],
92
+ ["unoverlap", unoverlapPattern],
93
+ ["grid", gridPattern],
94
+ ]);
95
+ export function registerScatterPattern(name, pattern) {
96
+ PATTERNS.set(name, pattern);
97
+ return () => {
98
+ if (PATTERNS.get(name) === pattern)
99
+ PATTERNS.delete(name);
100
+ };
101
+ }
102
+ export function getScatterPattern(name) {
103
+ return PATTERNS.get(name);
104
+ }
105
+ export function listScatterPatterns() {
106
+ return [...PATTERNS.keys()];
107
+ }
108
+ //# sourceMappingURL=patterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"patterns.js","sourceRoot":"","sources":["../src/patterns.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import type { UsketchPlugin } from "@edv4h/usketch-shared";
2
+ export declare function createScatterPlugin(): UsketchPlugin;
3
+ //# sourceMappingURL=plugin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAI1E,wBAAgB,mBAAmB,IAAI,aAAa,CAkBnD"}
package/dist/plugin.js ADDED
@@ -0,0 +1,18 @@
1
+ import { registerScatterHud } from "./hud/register-scatter-hud.js";
2
+ import { createScatterApi, scatterService } from "./scatter-service.js";
3
+ export function createScatterPlugin() {
4
+ return {
5
+ id: "usketch-plugin-scatter",
6
+ name: "Scatter",
7
+ setup(ctx) {
8
+ const unregisterHud = registerScatterHud(ctx);
9
+ // Provided last so a HUD registration failure can't leak the service.
10
+ const unprovide = scatterService.provide(ctx.services, createScatterApi({ store: ctx.store, shapes: ctx.shapes, commands: ctx.commands }));
11
+ return () => {
12
+ unprovide();
13
+ unregisterHud();
14
+ };
15
+ },
16
+ };
17
+ }
18
+ //# sourceMappingURL=plugin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"plugin.js","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAIA,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AACnE,OAAO,EAAE,gBAAgB,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAExE,MAAM,UAAU,mBAAmB;IAClC,OAAO;QACN,EAAE,EAAE,wBAAwB;QAC5B,IAAI,EAAE,SAAS;QAEf,KAAK,CAAC,GAAkB;YACvB,MAAM,aAAa,GAAG,kBAAkB,CAAC,GAAG,CAAC,CAAC;YAC9C,sEAAsE;YACtE,MAAM,SAAS,GAAG,cAAc,CAAC,OAAO,CACvC,GAAG,CAAC,QAAQ,EACZ,gBAAgB,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,GAAG,CAAC,QAAQ,EAAE,CAAC,CAClF,CAAC;YACF,OAAO,GAAG,EAAE;gBACX,SAAS,EAAE,CAAC;gBACZ,aAAa,EAAE,CAAC;YACjB,CAAC,CAAC;QACH,CAAC;KACD,CAAC;AACH,CAAC"}
@@ -0,0 +1,15 @@
1
+ import type { RelationResolver, ScatterDeps, ScatterItem } from "./types.js";
2
+ /**
3
+ * Connector neighbours: every shape joined to the seed by a connector (a shape
4
+ * carrying `sourceId`/`targetId`) — the connector's OPPOSITE endpoint. Plugin-
5
+ * agnostic: it just matches the `ConnectableShapeData` field shape.
6
+ */
7
+ export declare const connectorNeighbors: RelationResolver;
8
+ /** Parent/children: shapes whose `parentId` is the seed (group/frame/container/island members). */
9
+ export declare const parentChildren: RelationResolver;
10
+ export declare function registerRelationResolver(name: string, resolver: RelationResolver): () => void;
11
+ export declare function getRelationResolver(name: string): RelationResolver | undefined;
12
+ export declare function listRelationResolvers(): string[];
13
+ /** Resolve the item set: explicit `items` win; else run the named/function resolver. */
14
+ export declare function resolveItems(deps: Pick<ScatterDeps, "store">, seedId: string, relation: string | RelationResolver | undefined, items: ScatterItem[] | undefined): ScatterItem[];
15
+ //# sourceMappingURL=resolvers.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolvers.d.ts","sourceRoot":"","sources":["../src/resolvers.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,gBAAgB,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAK7E;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,EAAE,gBAUhC,CAAC;AAEF,mGAAmG;AACnG,eAAO,MAAM,cAAc,EAAE,gBAM5B,CAAC;AAOF,wBAAgB,wBAAwB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,MAAM,IAAI,CAK7F;AAED,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,CAE9E;AAED,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAEhD;AAED,wFAAwF;AACxF,wBAAgB,YAAY,CAC3B,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,EAChC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,GAAG,gBAAgB,GAAG,SAAS,EAC/C,KAAK,EAAE,WAAW,EAAE,GAAG,SAAS,GAC9B,WAAW,EAAE,CAMf"}
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Connector neighbours: every shape joined to the seed by a connector (a shape
3
+ * carrying `sourceId`/`targetId`) — the connector's OPPOSITE endpoint. Plugin-
4
+ * agnostic: it just matches the `ConnectableShapeData` field shape.
5
+ */
6
+ export const connectorNeighbors = ({ store }, seedId) => {
7
+ const out = new Set();
8
+ for (const [, shape] of store.getShapes()) {
9
+ const r = shape;
10
+ const src = typeof r.sourceId === "string" ? r.sourceId : undefined;
11
+ const tgt = typeof r.targetId === "string" ? r.targetId : undefined;
12
+ if (src === seedId && tgt && tgt !== seedId)
13
+ out.add(tgt);
14
+ else if (tgt === seedId && src && src !== seedId)
15
+ out.add(src);
16
+ }
17
+ return [...out].map((id) => ({ kind: "existing", id }));
18
+ };
19
+ /** Parent/children: shapes whose `parentId` is the seed (group/frame/container/island members). */
20
+ export const parentChildren = ({ store }, seedId) => {
21
+ const out = [];
22
+ for (const [id, shape] of store.getShapes()) {
23
+ if (shape.parentId === seedId)
24
+ out.push({ kind: "existing", id });
25
+ }
26
+ return out;
27
+ };
28
+ const RESOLVERS = new Map([
29
+ ["connectors", connectorNeighbors],
30
+ ["children", parentChildren],
31
+ ]);
32
+ export function registerRelationResolver(name, resolver) {
33
+ RESOLVERS.set(name, resolver);
34
+ return () => {
35
+ if (RESOLVERS.get(name) === resolver)
36
+ RESOLVERS.delete(name);
37
+ };
38
+ }
39
+ export function getRelationResolver(name) {
40
+ return RESOLVERS.get(name);
41
+ }
42
+ export function listRelationResolvers() {
43
+ return [...RESOLVERS.keys()];
44
+ }
45
+ /** Resolve the item set: explicit `items` win; else run the named/function resolver. */
46
+ export function resolveItems(deps, seedId, relation, items) {
47
+ if (items)
48
+ return items;
49
+ if (!relation)
50
+ return [];
51
+ const resolver = typeof relation === "function" ? relation : getRelationResolver(relation);
52
+ if (!resolver)
53
+ throw new Error(`[scatter] unknown relation resolver: ${String(relation)}`);
54
+ return resolver(deps, seedId);
55
+ }
56
+ //# sourceMappingURL=resolvers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolvers.js","sourceRoot":"","sources":["../src/resolvers.ts"],"names":[],"mappings":"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"}
package/dist/rng.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ /** mulberry32: fast, decent-quality seeded PRNG returning floats in [0,1). */
2
+ export declare function mulberry32(seed: number): () => number;
3
+ /** Coerce a user seed (number or string) to a 32-bit unsigned int. */
4
+ export declare function hashSeed(seed: number | string): number;
5
+ //# sourceMappingURL=rng.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rng.d.ts","sourceRoot":"","sources":["../src/rng.ts"],"names":[],"mappings":"AAIA,8EAA8E;AAC9E,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,MAAM,CASrD;AAED,sEAAsE;AACtE,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,MAAM,CAQtD"}
package/dist/rng.js ADDED
@@ -0,0 +1,26 @@
1
+ // Small seeded PRNG so random scatter patterns are reproducible (same seed → same
2
+ // layout) for tests/repro. Copied from the map plugin's generators/rng.ts (not
3
+ // shared). No external deps.
4
+ /** mulberry32: fast, decent-quality seeded PRNG returning floats in [0,1). */
5
+ export function mulberry32(seed) {
6
+ let a = seed >>> 0;
7
+ return () => {
8
+ a |= 0;
9
+ a = (a + 0x6d2b79f5) | 0;
10
+ let t = Math.imul(a ^ (a >>> 15), 1 | a);
11
+ t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
12
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
13
+ };
14
+ }
15
+ /** Coerce a user seed (number or string) to a 32-bit unsigned int. */
16
+ export function hashSeed(seed) {
17
+ if (typeof seed === "number")
18
+ return seed >>> 0;
19
+ let h = 2166136261 >>> 0; // FNV-1a
20
+ for (let i = 0; i < seed.length; i++) {
21
+ h ^= seed.charCodeAt(i);
22
+ h = Math.imul(h, 16777619);
23
+ }
24
+ return h >>> 0;
25
+ }
26
+ //# sourceMappingURL=rng.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rng.js","sourceRoot":"","sources":["../src/rng.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,+EAA+E;AAC/E,6BAA6B;AAE7B,8EAA8E;AAC9E,MAAM,UAAU,UAAU,CAAC,IAAY;IACtC,IAAI,CAAC,GAAG,IAAI,KAAK,CAAC,CAAC;IACnB,OAAO,GAAG,EAAE;QACX,CAAC,IAAI,CAAC,CAAC;QACP,CAAC,GAAG,CAAC,CAAC,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC;QACzB,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC;QACzC,CAAC,GAAG,CAAC,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;QAC/C,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,UAAU,CAAC;IAC9C,CAAC,CAAC;AACH,CAAC;AAED,sEAAsE;AACtE,MAAM,UAAU,QAAQ,CAAC,IAAqB;IAC7C,IAAI,OAAO,IAAI,KAAK,QAAQ;QAAE,OAAO,IAAI,KAAK,CAAC,CAAC;IAChD,IAAI,CAAC,GAAG,UAAU,KAAK,CAAC,CAAC,CAAC,SAAS;IACnC,KAAK,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;QACtC,CAAC,IAAI,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC;QACxB,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;IAC5B,CAAC;IACD,OAAO,CAAC,KAAK,CAAC,CAAC;AAChB,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { type ServiceRegistry } from "@edv4h/usketch-shared";
2
+ import type { RelationResolver, ScatterDeps, ScatterPattern, ScatterRequest, ScatterResult } from "./types.js";
3
+ export interface ScatterApi {
4
+ scatter(request: ScatterRequest): Promise<ScatterResult>;
5
+ registerPattern(name: string, pattern: ScatterPattern): () => void;
6
+ registerResolver(name: string, resolver: RelationResolver): () => void;
7
+ listPatterns(): string[];
8
+ listResolvers(): string[];
9
+ }
10
+ export declare const scatterService: import("@edv4h/usketch-shared").ServiceHandle<ScatterApi>;
11
+ export declare function createScatterApi(deps: ScatterDeps): ScatterApi;
12
+ /** Host accessor: `getScatterApi(app.services)?.scatter({...})`. Undefined when the
13
+ * scatter plugin isn't active. Works with `ctx.services` too. */
14
+ export declare function getScatterApi(services: ServiceRegistry): ScatterApi | undefined;
15
+ //# sourceMappingURL=scatter-service.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scatter-service.d.ts","sourceRoot":"","sources":["../src/scatter-service.ts"],"names":[],"mappings":"AAGA,OAAO,EAAiB,KAAK,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAI5E,OAAO,KAAK,EACX,gBAAgB,EAChB,WAAW,EACX,cAAc,EACd,cAAc,EACd,aAAa,EACb,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,UAAU;IAC1B,OAAO,CAAC,OAAO,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC,CAAC;IACzD,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,GAAG,MAAM,IAAI,CAAC;IACnE,gBAAgB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,gBAAgB,GAAG,MAAM,IAAI,CAAC;IACvE,YAAY,IAAI,MAAM,EAAE,CAAC;IACzB,aAAa,IAAI,MAAM,EAAE,CAAC;CAC1B;AAED,eAAO,MAAM,cAAc,2DAAsD,CAAC;AAElF,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,WAAW,GAAG,UAAU,CAQ9D;AAED;kEACkE;AAClE,wBAAgB,aAAa,CAAC,QAAQ,EAAE,eAAe,GAAG,UAAU,GAAG,SAAS,CAE/E"}
@@ -0,0 +1,23 @@
1
+ // Host-facing scatter API, published on the `ctx.services` seam (defineService
2
+ // convention — see usketch-plugin-map/map-service.ts). Lets a host/other plugin
3
+ // run scatters + register custom patterns/resolvers without the Control HUD.
4
+ import { defineService } from "@edv4h/usketch-shared";
5
+ import { scatter } from "./engine.js";
6
+ import { listScatterPatterns, registerScatterPattern } from "./patterns.js";
7
+ import { listRelationResolvers, registerRelationResolver } from "./resolvers.js";
8
+ export const scatterService = defineService("usketch-plugin-scatter");
9
+ export function createScatterApi(deps) {
10
+ return {
11
+ scatter: (request) => scatter(deps, request),
12
+ registerPattern: registerScatterPattern,
13
+ registerResolver: registerRelationResolver,
14
+ listPatterns: listScatterPatterns,
15
+ listResolvers: listRelationResolvers,
16
+ };
17
+ }
18
+ /** Host accessor: `getScatterApi(app.services)?.scatter({...})`. Undefined when the
19
+ * scatter plugin isn't active. Works with `ctx.services` too. */
20
+ export function getScatterApi(services) {
21
+ return scatterService.get(services);
22
+ }
23
+ //# sourceMappingURL=scatter-service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scatter-service.js","sourceRoot":"","sources":["../src/scatter-service.ts"],"names":[],"mappings":"AAAA,+EAA+E;AAC/E,gFAAgF;AAChF,6EAA6E;AAC7E,OAAO,EAAE,aAAa,EAAwB,MAAM,uBAAuB,CAAC;AAC5E,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AACtC,OAAO,EAAE,mBAAmB,EAAE,sBAAsB,EAAE,MAAM,eAAe,CAAC;AAC5E,OAAO,EAAE,qBAAqB,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAiBjF,MAAM,CAAC,MAAM,cAAc,GAAG,aAAa,CAAa,wBAAwB,CAAC,CAAC;AAElF,MAAM,UAAU,gBAAgB,CAAC,IAAiB;IACjD,OAAO;QACN,OAAO,EAAE,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;QAC5C,eAAe,EAAE,sBAAsB;QACvC,gBAAgB,EAAE,wBAAwB;QAC1C,YAAY,EAAE,mBAAmB;QACjC,aAAa,EAAE,qBAAqB;KACpC,CAAC;AACH,CAAC;AAED;kEACkE;AAClE,MAAM,UAAU,aAAa,CAAC,QAAyB;IACtD,OAAO,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;AACrC,CAAC"}
@@ -0,0 +1,16 @@
1
+ export interface ScatterState {
2
+ /** Registered pattern name (radial | scatter | unoverlap | grid | …). */
3
+ pattern: string;
4
+ /** Registered relation resolver name (connectors | children | …). */
5
+ relation: string;
6
+ spacing: number;
7
+ animate: boolean;
8
+ durationMs: number;
9
+ }
10
+ export interface ReactiveStore<T> {
11
+ get(): T;
12
+ set(patch: Partial<T>): void;
13
+ subscribe(listener: () => void): () => void;
14
+ }
15
+ export declare const scatterStateStore: ReactiveStore<ScatterState>;
16
+ //# sourceMappingURL=scatter-state.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scatter-state.d.ts","sourceRoot":"","sources":["../src/scatter-state.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,YAAY;IAC5B,yEAAyE;IACzE,OAAO,EAAE,MAAM,CAAC;IAChB,qEAAqE;IACrE,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,aAAa,CAAC,CAAC;IAC/B,GAAG,IAAI,CAAC,CAAC;IACT,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC;IAC7B,SAAS,CAAC,QAAQ,EAAE,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC;CAC5C;AA4BD,eAAO,MAAM,iBAAiB,6BAM5B,CAAC"}
@@ -0,0 +1,37 @@
1
+ // Module-scoped reactive store backing the scatter HUD controls. App-local
2
+ // presentation state (never synced), same shape as the map plugin's stores.
3
+ function createReactiveStore(initial) {
4
+ let state = initial;
5
+ const listeners = new Set();
6
+ return {
7
+ get: () => state,
8
+ set(patch) {
9
+ const next = { ...state };
10
+ let changed = false;
11
+ for (const k of Object.keys(patch)) {
12
+ const v = patch[k];
13
+ if (v !== undefined && v !== state[k]) {
14
+ next[k] = v;
15
+ changed = true;
16
+ }
17
+ }
18
+ if (!changed)
19
+ return;
20
+ state = next;
21
+ for (const l of listeners)
22
+ l();
23
+ },
24
+ subscribe(listener) {
25
+ listeners.add(listener);
26
+ return () => listeners.delete(listener);
27
+ },
28
+ };
29
+ }
30
+ export const scatterStateStore = createReactiveStore({
31
+ pattern: "radial",
32
+ relation: "connectors",
33
+ spacing: 24,
34
+ animate: true,
35
+ durationMs: 450,
36
+ });
37
+ //# sourceMappingURL=scatter-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scatter-state.js","sourceRoot":"","sources":["../src/scatter-state.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,4EAA4E;AAkB5E,SAAS,mBAAmB,CAAmB,OAAU;IACxD,IAAI,KAAK,GAAG,OAAO,CAAC;IACpB,MAAM,SAAS,GAAG,IAAI,GAAG,EAAc,CAAC;IACxC,OAAO;QACN,GAAG,EAAE,GAAG,EAAE,CAAC,KAAK;QAChB,GAAG,CAAC,KAAK;YACR,MAAM,IAAI,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC;YAC1B,IAAI,OAAO,GAAG,KAAK,CAAC;YACpB,KAAK,MAAM,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,KAAK,CAAgB,EAAE,CAAC;gBACnD,MAAM,CAAC,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;gBACnB,IAAI,CAAC,KAAK,SAAS,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;oBACvC,IAAI,CAAC,CAAC,CAAC,GAAG,CAAe,CAAC;oBAC1B,OAAO,GAAG,IAAI,CAAC;gBAChB,CAAC;YACF,CAAC;YACD,IAAI,CAAC,OAAO;gBAAE,OAAO;YACrB,KAAK,GAAG,IAAI,CAAC;YACb,KAAK,MAAM,CAAC,IAAI,SAAS;gBAAE,CAAC,EAAE,CAAC;QAChC,CAAC;QACD,SAAS,CAAC,QAAQ;YACjB,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACxB,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACzC,CAAC;KACD,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,iBAAiB,GAAG,mBAAmB,CAAe;IAClE,OAAO,EAAE,QAAQ;IACjB,QAAQ,EAAE,YAAY;IACtB,OAAO,EAAE,EAAE;IACX,OAAO,EAAE,IAAI;IACb,UAAU,EAAE,GAAG;CACf,CAAC,CAAC"}
@@ -0,0 +1,79 @@
1
+ import type { BoardStore, BoundingBox, CommandRegistry, Point, ShapeData, ShapeRegistry, ShapeStyle } from "@edv4h/usketch-shared";
2
+ /** A brand-new shape to spawn as part of a scatter. The engine assigns an id
3
+ * (`generateId()`) + the final x/y from the pattern, builds a valid base via the
4
+ * type's `ShapeDefinition.createDefault`, then merges these fields over it. */
5
+ export interface NewShapeSpec {
6
+ id?: string;
7
+ type: string;
8
+ /** Required — the shape isn't in the store yet, so `getBounds` can't be used. */
9
+ width: number;
10
+ height: number;
11
+ style?: Partial<ShapeStyle>;
12
+ meta?: Record<string, unknown>;
13
+ /** Intrinsic per-type fields (text, etc.). */
14
+ [key: string]: unknown;
15
+ }
16
+ /** A scatter item is either an existing shape (moved) or a new shape (spawned). */
17
+ export type ScatterItem = {
18
+ kind: "existing";
19
+ id: string;
20
+ } | {
21
+ kind: "new";
22
+ spec: NewShapeSpec;
23
+ };
24
+ export interface ScatterDeps {
25
+ store: BoardStore;
26
+ shapes: ShapeRegistry;
27
+ commands: CommandRegistry;
28
+ }
29
+ /** Resolve the seed's related set — pure over the store. Pluggable so "related"
30
+ * isn't hardcoded (connector neighbours, parent/children, or a host's own). */
31
+ export type RelationResolver = (deps: Pick<ScatterDeps, "store">, seedId: string) => ScatterItem[];
32
+ /** One item to place, with its INTRINSIC (unrotated, origin-anchored) bounds. */
33
+ export interface PatternItem {
34
+ key: string;
35
+ bounds: BoundingBox;
36
+ }
37
+ export interface PatternContext {
38
+ seedBounds: BoundingBox;
39
+ seedCenter: Point;
40
+ items: PatternItem[];
41
+ /** AABBs to avoid (the seed + non-item shapes) — used by `unoverlap`. */
42
+ occupied: BoundingBox[];
43
+ /** Gap between placed items, in world px. */
44
+ spacing: number;
45
+ /** Seeded PRNG in [0,1). Patterns MUST use this (never `Math.random`) for repro. */
46
+ rng: () => number;
47
+ }
48
+ export interface Placement {
49
+ key: string;
50
+ x: number;
51
+ y: number;
52
+ rotation?: number;
53
+ }
54
+ /** A pure layout function: place the items around the seed. */
55
+ export type ScatterPattern = (ctx: PatternContext) => Placement[];
56
+ export interface ScatterRequest {
57
+ /** Seed shape id. Defaults to the sole selected shape. */
58
+ seedId?: string;
59
+ /** Explicit item set … */
60
+ items?: ScatterItem[];
61
+ /** … or derive it from the seed (a registered resolver name or a function). */
62
+ relation?: string | RelationResolver;
63
+ /** Layout (a registered pattern name or a function). */
64
+ pattern: string | ScatterPattern;
65
+ spacing?: number;
66
+ /** RNG seed (number or string) for reproducible random patterns. */
67
+ seed?: number | string;
68
+ /** Fly-out animation (default `false` = instant). */
69
+ animate?: boolean;
70
+ durationMs?: number;
71
+ easing?: (t: number) => number;
72
+ }
73
+ export interface ScatterResult {
74
+ movedIds: string[];
75
+ createdIds: string[];
76
+ }
77
+ /** Convenience alias for a fully-built shape produced by the engine. */
78
+ export type BuiltShape = ShapeData;
79
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACX,UAAU,EACV,WAAW,EACX,eAAe,EACf,KAAK,EACL,SAAS,EACT,aAAa,EACb,UAAU,EACV,MAAM,uBAAuB,CAAC;AAE/B;;gFAEgF;AAChF,MAAM,WAAW,YAAY;IAC5B,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,iFAAiF;IACjF,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;IAC5B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IAC/B,8CAA8C;IAC9C,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACvB;AAED,mFAAmF;AACnF,MAAM,MAAM,WAAW,GAAG;IAAE,IAAI,EAAE,UAAU,CAAC;IAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,IAAI,EAAE,KAAK,CAAC;IAAC,IAAI,EAAE,YAAY,CAAA;CAAE,CAAC;AAEjG,MAAM,WAAW,WAAW;IAC3B,KAAK,EAAE,UAAU,CAAC;IAClB,MAAM,EAAE,aAAa,CAAC;IACtB,QAAQ,EAAE,eAAe,CAAC;CAC1B;AAED;gFACgF;AAChF,MAAM,MAAM,gBAAgB,GAAG,CAAC,IAAI,EAAE,IAAI,CAAC,WAAW,EAAE,OAAO,CAAC,EAAE,MAAM,EAAE,MAAM,KAAK,WAAW,EAAE,CAAC;AAEnG,iFAAiF;AACjF,MAAM,WAAW,WAAW;IAC3B,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,WAAW,CAAC;CACpB;AAED,MAAM,WAAW,cAAc;IAC9B,UAAU,EAAE,WAAW,CAAC;IACxB,UAAU,EAAE,KAAK,CAAC;IAClB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,yEAAyE;IACzE,QAAQ,EAAE,WAAW,EAAE,CAAC;IACxB,6CAA6C;IAC7C,OAAO,EAAE,MAAM,CAAC;IAChB,oFAAoF;IACpF,GAAG,EAAE,MAAM,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACzB,GAAG,EAAE,MAAM,CAAC;IACZ,CAAC,EAAE,MAAM,CAAC;IACV,CAAC,EAAE,MAAM,CAAC;IACV,QAAQ,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,+DAA+D;AAC/D,MAAM,MAAM,cAAc,GAAG,CAAC,GAAG,EAAE,cAAc,KAAK,SAAS,EAAE,CAAC;AAElE,MAAM,WAAW,cAAc;IAC9B,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,0BAA0B;IAC1B,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;IACtB,+EAA+E;IAC/E,QAAQ,CAAC,EAAE,MAAM,GAAG,gBAAgB,CAAC;IACrC,wDAAwD;IACxD,OAAO,EAAE,MAAM,GAAG,cAAc,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oEAAoE;IACpE,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACvB,qDAAqD;IACrD,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,CAAC,EAAE,CAAC,CAAC,EAAE,MAAM,KAAK,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,aAAa;IAC7B,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,UAAU,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,wEAAwE;AACxE,MAAM,MAAM,UAAU,GAAG,SAAS,CAAC"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "@edv4h/usketch-plugin-scatter",
3
+ "version": "0.1.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": "4.12.0",
16
+ "@edv4h/usketch-shape-utils": "2.2.8"
17
+ },
18
+ "devDependencies": {
19
+ "typescript": "7.0.2",
20
+ "vitest": "4.1.11"
21
+ },
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "files": [
26
+ "dist",
27
+ "README.md"
28
+ ],
29
+ "license": "MIT",
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/EdV4H/usketch.git",
33
+ "directory": "plugins/usketch-plugin-scatter"
34
+ },
35
+ "homepage": "https://github.com/EdV4H/usketch#readme",
36
+ "bugs": {
37
+ "url": "https://github.com/EdV4H/usketch/issues"
38
+ },
39
+ "scripts": {
40
+ "build": "tsc -p tsconfig.json",
41
+ "typecheck": "tsc --noEmit",
42
+ "test": "vitest run --passWithNoTests",
43
+ "clean": "rm -rf dist"
44
+ }
45
+ }