@rasamjs/tools 0.0.0-stage → 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 (56) hide show
  1. package/API.md +188 -0
  2. package/CHANGELOG.md +45 -0
  3. package/LICENSE +21 -0
  4. package/README.md +101 -2
  5. package/RELEASING.md +75 -0
  6. package/dist/snapping/guides.d.ts +14 -0
  7. package/dist/snapping/guides.d.ts.map +1 -0
  8. package/dist/snapping/guides.js +109 -0
  9. package/dist/snapping/guides.js.map +1 -0
  10. package/dist/snapping/lines.d.ts +43 -0
  11. package/dist/snapping/lines.d.ts.map +1 -0
  12. package/dist/snapping/lines.js +74 -0
  13. package/dist/snapping/lines.js.map +1 -0
  14. package/dist/snapping/options.d.ts +18 -0
  15. package/dist/snapping/options.d.ts.map +1 -0
  16. package/dist/snapping/options.js +44 -0
  17. package/dist/snapping/options.js.map +1 -0
  18. package/dist/snapping/overlay.d.ts +22 -0
  19. package/dist/snapping/overlay.d.ts.map +1 -0
  20. package/dist/snapping/overlay.js +165 -0
  21. package/dist/snapping/overlay.js.map +1 -0
  22. package/dist/snapping/session.d.ts +25 -0
  23. package/dist/snapping/session.d.ts.map +1 -0
  24. package/dist/snapping/session.js +89 -0
  25. package/dist/snapping/session.js.map +1 -0
  26. package/dist/snapping/snapper.d.ts +22 -0
  27. package/dist/snapping/snapper.d.ts.map +1 -0
  28. package/dist/snapping/snapper.js +140 -0
  29. package/dist/snapping/snapper.js.map +1 -0
  30. package/dist/snapping/spacing.d.ts +27 -0
  31. package/dist/snapping/spacing.d.ts.map +1 -0
  32. package/dist/snapping/spacing.js +127 -0
  33. package/dist/snapping/spacing.js.map +1 -0
  34. package/dist/snapping/targets.d.ts +6 -0
  35. package/dist/snapping/targets.d.ts.map +1 -0
  36. package/dist/snapping/targets.js +67 -0
  37. package/dist/snapping/targets.js.map +1 -0
  38. package/dist/snapping/types.d.ts +35 -0
  39. package/dist/snapping/types.d.ts.map +1 -0
  40. package/dist/snapping/types.js +2 -0
  41. package/dist/snapping/types.js.map +1 -0
  42. package/dist/snapping.d.ts +4 -0
  43. package/dist/snapping.d.ts.map +1 -0
  44. package/dist/snapping.js +3 -0
  45. package/dist/snapping.js.map +1 -0
  46. package/package.json +53 -4
  47. package/src/snapping/guides.ts +132 -0
  48. package/src/snapping/lines.ts +108 -0
  49. package/src/snapping/options.ts +47 -0
  50. package/src/snapping/overlay.ts +186 -0
  51. package/src/snapping/session.ts +116 -0
  52. package/src/snapping/snapper.ts +147 -0
  53. package/src/snapping/spacing.ts +157 -0
  54. package/src/snapping/targets.ts +79 -0
  55. package/src/snapping/types.ts +27 -0
  56. package/src/snapping.ts +3 -0
package/API.md ADDED
@@ -0,0 +1,188 @@
1
+ # Tools API
2
+
3
+ ## Snapping entry
4
+
5
+ ```ts
6
+ import { Snapper } from '@rasamjs/tools/snapping';
7
+ import type { SnapperOptions, SnapRequest, SnapSession } from '@rasamjs/tools/snapping';
8
+
9
+ declare class Snapper {
10
+ constructor(drawing: Svg, options?: SnapperOptions);
11
+ enabled: boolean;
12
+ readonly isDestroyed: boolean;
13
+ begin(request: SnapRequest): SnapSession | null;
14
+ update(): this;
15
+ destroy(): void;
16
+ }
17
+ ```
18
+
19
+ `Svg`, `Point` and `AnyNode` come from `@rasamjs/core`. The constructor needs a live,
20
+ mounted rasam SVG drawing with an owning window. Invalid drawings or options fail
21
+ before registering disposal callbacks. Construction and sessions leave managed SVG
22
+ content unchanged; applications use the returned pointer to perform their own edits.
23
+
24
+ ## Options
25
+
26
+ | Option | Default | Meaning |
27
+ | ----------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
28
+ | `tolerance: number` | `6` | Nonnegative, finite client pixels, converted independently for the current root x/y mapping on each `snap()` call. |
29
+ | `shapes: boolean` | `true` | Enable alignment to target edges and centers. |
30
+ | `spacing: boolean` | `true` | Equalize gaps to two neighbors or match an existing positive gap. Independent of `shapes`. |
31
+ | `grid: number \| { size: number; origin?: Point } \| null` | `null` | Positive, finite size in drawing units. Origin defaults to `{ x: 0, y: 0 }` and is copied. Only moving edges or points snap to grid lines. |
32
+ | `targets: (nodes: readonly AnyNode[]) => Iterable<AnyNode>` | Siblings | Replaces the default target list; called once per `begin()` when shapes or spacing are enabled. |
33
+ | `modifier: 'ctrl' \| 'none'` | `'ctrl'` | Whether the caller's `ignore` flag temporarily disables snapping. |
34
+ | `style: { stroke?: string; gapStroke?: string }` | `#f00` | Nonempty SVG strokes. `stroke` colors alignment; `gapStroke` colors gap markers and defaults to `stroke`. |
35
+
36
+ Default targets combine siblings from every moving node's parent, deduplicated in
37
+ document order. Selected nodes, their descendants and ancestor containers are excluded.
38
+ Detached, destroyed, foreign, CSS-hidden, empty and unavailable targets are skipped.
39
+ Point requests default to drawing children and pass `[]` to `targets`.
40
+
41
+ `begin()` snapshots target boxes and moving features in drawing units. Removing or
42
+ moving a target afterward does not change the active session. Shape candidates win
43
+ equal-distance grid ties. Shape ties use document order even when a custom callback
44
+ returns nodes in another order. Each root axis snaps independently.
45
+
46
+ ## Requests and sessions
47
+
48
+ ```ts
49
+ type SnapRequest =
50
+ | { readonly kind: 'point'; readonly client: Point }
51
+ | {
52
+ readonly kind: 'move' | 'resize';
53
+ readonly nodes: readonly AnyNode[];
54
+ readonly client: Point;
55
+ readonly corners: readonly Point[];
56
+ readonly anchor?: Point;
57
+ readonly centered?: boolean;
58
+ };
59
+
60
+ interface SnapSession {
61
+ snap(client: Point, modifiers: { readonly ignore: boolean; readonly centered?: boolean }): Point;
62
+ end(): void;
63
+ }
64
+ ```
65
+
66
+ `client` is the initial pointer in client pixels. Move requests snap the union of the
67
+ nodes' root-space axis-aligned boxes. Pointer offsets from shape edges are preserved.
68
+ The `corners` field may be empty for moves. Point requests snap a single root-space
69
+ point without a box.
70
+
71
+ Resize requests need four client-space frame corners in local clockwise order:
72
+ top-left, top-right, bottom-right, bottom-left. `anchor` is an edge or corner of the
73
+ unit frame, using `0`, `0.5` and `1`, with both coordinates at `0.5` disallowed. Only
74
+ dragged axes snap. Quarter turns and reflections are supported; arbitrarily rotated,
75
+ skewed or fully degenerate frames return `null`. An axis-aligned flat frame can still
76
+ snap its nondegenerate axis. The alignment test uses relative floating point tolerance.
77
+ The active-edge correction is the same for centered resizing; the caller applies its
78
+ centered, ratio and minimum-size constraints after receiving the corrected pointer.
79
+ Pass the resolved current policy as `modifiers.centered` if it can change mid-session.
80
+ Omitting it uses the initial request's `centered` value. Transformer 0.3.0 forwards the
81
+ current policy on every sample, including stationary Alt changes.
82
+
83
+ No eligible targets and no grid, empty moving geometry, unavailable mappings, disabled
84
+ snapping or a destroyed owner yield `null`. Invalid request kinds, node membership,
85
+ pointer coordinates, resize corners or anchors throw `TypeError`.
86
+
87
+ Call `snap(client, { ignore: event.ctrlKey || event.metaKey })` for the decided Ctrl/Cmd
88
+ override. `modifier: 'none'` disregards `ignore`. Pan and zoom may change during a
89
+ session: the current client mapping and per-axis tolerance are read on every call.
90
+ Unavailable mappings return the original client point. Inputs are never mutated.
91
+
92
+ ## Lifecycle
93
+
94
+ `enabled` starts at `true`. Disabling it makes an active session return the original
95
+ pointer; re-enabling it resumes the session. A new `begin()` ends the previous session.
96
+ `end()` is idempotent, and ended sessions return their input unchanged. Starting no
97
+ replacement session also ends the previous one. Custom target callbacks that destroy
98
+ the owner or begin another session cannot install a stale outer session.
99
+
100
+ `isDestroyed` starts at `false`. `destroy()` ends the active session, marks the owner
101
+ destroyed and removes the drawing-disposal subscription. It can be repeated safely.
102
+ It leaves the drawing and other snappers usable. Drawing disposal destroys its
103
+ snappers automatically. Changing `enabled` cannot revive a destroyed instance.
104
+
105
+ The only runtime export is `Snapper`; `SnapperOptions`, `SnapRequest` and `SnapSession`
106
+ are type exports. Imports are safe without a document. There is no package root export,
107
+ and internal module subpaths are blocked.
108
+
109
+ ## Equal spacing
110
+
111
+ Spacing is enabled independently of shape alignment. Disable both `shapes` and `spacing`
112
+ for a grid-only tool. Target boxes and reference gaps are snapshotted once at `begin()`;
113
+ pointer samples use cached numbers without measuring target DOM geometry again.
114
+
115
+ Neighbors must overlap the proposed moving box on the perpendicular axis (touching
116
+ boundaries and the zero-size point case count). Each axis uses its closest surrounding
117
+ target edges. Targets that contain the moving box on an axis, such as a background or
118
+ panel behind it, are not neighbors on that axis; partially overlapping targets still
119
+ block. A move can center the box between two neighbors or put an edge one
120
+ reference-gap length before/after a neighbor. Reference gaps are positive gaps after
121
+ the furthest preceding target that overlaps the next target perpendicularly; overlapping
122
+ targets do not create a gap. Targets that contain the next target on that axis are
123
+ backgrounds and neither create nor block its gap. Reference gaps may come from another row or column.
124
+
125
+ Resize correction changes only the dragged edge, including after a flip. An ordinary
126
+ resize can equalize its two neighboring gaps or match a reference gap at that edge.
127
+ Centered resizing can match a reference at the dragged edge; already-equal opposing
128
+ gaps are marked with zero correction because changing size cannot fix an off-center
129
+ frame. Other transformer constraints keep their existing priority.
130
+
131
+ The closest correction within the current client-pixel tolerance wins. Exact-distance
132
+ ties prefer shape alignment, then grid, then spacing. Spacing ties prefer the
133
+ two-neighbor result, then reference gaps in snapshot document order. Gap markers are
134
+ shown only when all accepted gaps still match within half a client pixel and the
135
+ moving geometry still overlaps its neighbor perpendicularly.
136
+
137
+ Gap discovery sorts cached far edges once and stops at the nearest eligible preceding
138
+ edge for each target. Its worst case is still quadratic in target
139
+ count. Each pointer query scans cached neighbors and reference gaps (linear), with no
140
+ target DOM reads. Applications can narrow `targets` or disable spacing for large scenes.
141
+
142
+ ## Guides
143
+
144
+ Guides live in a separate native SVG beside the outer drawing SVG. They stay out of
145
+ managed drawing content, exports, queries and history. The overlay ignores pointer
146
+ events and accessibility navigation. Its default red lines use a fixed one-client-pixel
147
+ stroke; cross marks remain six client pixels wide. `style.stroke` changes their color.
148
+
149
+ Each snapped axis spans the moving box and its snapshotted target box, with eight
150
+ client pixels of padding and a cross at each box's perpendicular center. Grid and
151
+ point alignment guides use the moving geometry only; this overlay does not draw a grid.
152
+ Applications draw their own grid; the example uses a CSS background that follows the camera.
153
+ Equal gaps use two dimension lines with fixed six-client-pixel perpendicular end caps;
154
+ `gapStroke` defaults to the alignment stroke. Their endpoint positions follow the
155
+ accepted moving box and the snapshotted target edges.
156
+
157
+ Pointer samples coalesce into one animation-frame draw. For shape requests the draw
158
+ reads accepted geometry after the caller applies the corrected pointer. An axis is
159
+ shown only if its accepted feature lies within half a client pixel of the target.
160
+ This hides misleading guides when ratio, minimum-size or other constraints reject
161
+ alignment. Resize guides follow the dragged anchor, including reflected groups.
162
+
163
+ `update()` redraws immediately and returns the snapper, so it fits the viewport's
164
+ `overlays` list alongside the transformer. Call it after changing the camera or layout
165
+ yourself. Page and ancestor scrolls and window resizes schedule redraws automatically.
166
+ It is safe after destruction. Disabling snapping, bypassing it, ending a session or
167
+ destroying the owner clears pending paint and hides guides immediately. Destruction
168
+ also removes the overlay and its window listeners.
169
+
170
+ ```ts
171
+ const snapper = new Snapper(drawing);
172
+ const transformer = new Transformer(drawing, { snap: snapper });
173
+ const viewport = new Viewport(drawing, { overlays: [transformer, snapper] });
174
+ ```
175
+
176
+ ## Transformer connection
177
+
178
+ ```ts
179
+ import { Transformer } from '@rasamjs/transformer';
180
+ const transformer = new Transformer(drawing, { snap: new Snapper(drawing, { grid: 10 }) });
181
+ ```
182
+
183
+ The bridge is structural: transformer imports no tools runtime. Pointer acquisition
184
+ starts one session for a move or eligible resize; finish, Escape, cancellation, detach
185
+ and destruction end it. Keyboard, rotation and pivot gestures do not acquire sessions.
186
+ Ctrl/Cmd is forwarded on pointer samples and re-evaluated when held or released while
187
+ stationary. Existing ratio, minimum-size, `boundBox` and commit-precision rules can
188
+ alter the final alignment after pointer correction.
package/CHANGELOG.md ADDED
@@ -0,0 +1,45 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0 — unreleased
4
+
5
+ - Add a 1,000-target drag benchmark in all three engines (`bench:drag`, run by the
6
+ release gate) that reports drag-start, snap-call and frame times without a hardware
7
+ threshold, and checks guides, the corrected move and exact undo.
8
+ - Rebuild the example with toggles for snapping, shapes, grid and spacing, and an
9
+ application-drawn grid that follows the viewport camera.
10
+ - Add default equal-spacing snapping for moves, dragged resize edges and points:
11
+ equalize two neighboring gaps or match a positive snapshotted gap.
12
+ - Skip targets that contain the moving box or a gap's next target on that axis, so a
13
+ background or panel sibling no longer disables spacing for the shapes above it.
14
+ - Keep spacing independent of shape alignment, preserve alignment/grid ties and
15
+ recheck accepted gap lengths and perpendicular overlap before drawing markers.
16
+ - Add `spacing` and `style.gapStroke`, with fixed six-client-pixel gap end caps.
17
+ - Accept optional current `centered` snap metadata so live Alt changes retain correct
18
+ spacing; callers that omit it keep the initial request policy.
19
+ - Validate real single/group moves and resizes, exact undo, zoom, target snapshots,
20
+ modifier bypass, opt-out and disposal in all three browser engines.
21
+ - Add a separate red SVG guide overlay, fixed one-pixel strokes, six-pixel cross marks,
22
+ optional `style.stroke` and viewport-compatible `update()`.
23
+ - Draw once per animation frame from accepted geometry, suppress constrained alignment,
24
+ and clear pending guides on bypass, disable, cancellation, replacement and disposal.
25
+ - Cover guide alignment through zoom, scrolling, camera changes, transformed containers
26
+ and the real viewport overlay list in Chromium, Firefox and WebKit.
27
+ - Connect the snapping example to the transformer's structural `snap` option, and
28
+ validate real single/group drags, modifiers, exact undo, cancellation and error cleanup.
29
+ - Extend the packed consumer to typecheck the real tools/transformer bridge after
30
+ validating tools independently with its core-only runtime peer.
31
+ - Add Phase 2 engine sessions for moves, dragged resize axes and points, with shape
32
+ alignment, optional grid snapping and document-order tie rules.
33
+ - Snapshot root-space targets once; convert the six-pixel tolerance through the current
34
+ client mapping per axis, including mid-session pan and zoom.
35
+ - Add custom and multi-parent sibling targets, hidden-target filtering, temporary
36
+ Ctrl/Cmd override and safe session replacement/destruction.
37
+ - Add the private `@rasamjs/tools` package with the `./snapping` entry only.
38
+ - Add `Snapper` construction, the `enabled` flag and idempotent destruction, including
39
+ automatic cleanup when the drawing is destroyed.
40
+ - Add lifecycle tests, the development example, declaration contracts and package gates.
41
+ - Reserve port 4179 for tools browser tests. The owner-approved gzip budget is 6 KiB
42
+ with core external, increased from the initial 4 KiB cap during Phase 4.
43
+
44
+ Equal spacing is a later phase. Publication requires separate
45
+ authorization.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 rasam contributors
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 CHANGED
@@ -1,3 +1,102 @@
1
- # Temporary Holding Version
1
+ # @rasamjs/tools
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Opt-in editing tools for rasam drawings. The first entry is `@rasamjs/tools/snapping`;
4
+ the package has no main entry yet. Its only runtime peer is `@rasamjs/core` `^0.1.0`.
5
+
6
+ ## Snapping
7
+
8
+ While a shape is moved or resized, its edges and centers line up with other shapes,
9
+ its edges land on an optional grid, and its gaps match its neighbors. Red guides and
10
+ gap markers show what it snapped to. Holding Ctrl (Cmd on macOS) bypasses snapping
11
+ while held.
12
+
13
+ ```ts
14
+ import { SVG } from '@rasamjs/core';
15
+ import { Transformer } from '@rasamjs/transformer';
16
+ import { Snapper } from '@rasamjs/tools/snapping';
17
+
18
+ const drawing = SVG(document.querySelector<HTMLElement>('#drawing')!, {
19
+ width: 600,
20
+ height: 400,
21
+ });
22
+ const snapper = new Snapper(drawing, { grid: 10 });
23
+ const transformer = new Transformer(drawing, { snap: snapper });
24
+
25
+ snapper.enabled = false; // a toolbar toggle
26
+ snapper.enabled = true;
27
+ ```
28
+
29
+ The transformer takes any structural `snap` provider and never imports this package.
30
+ Snapping runs during pointer moves and resizes of frames that are axis-aligned in
31
+ drawing space. Keyboard nudges, rotation and pivot edits keep their existing behavior.
32
+ Ratio, minimum-size, `boundBox` and commit-precision rules keep priority over snapping,
33
+ and guides only show alignments the accepted geometry still has.
34
+
35
+ ### Targets
36
+
37
+ Targets default to the moving nodes' siblings, combined across parents in document
38
+ order. Moving subtrees, their ancestor containers, hidden nodes and unavailable bounds
39
+ are skipped. `targets(nodes)` replaces the list, for example to limit snapping to an
40
+ artboard or skip locked layers. Targets are measured once per drag, in drawing units.
41
+
42
+ The tolerance is six client pixels by default and follows the current zoom, so snapping
43
+ feels the same at every zoom level and keeps working when the view changes mid-drag.
44
+ Shape lines win equal-distance ties with the grid, and the grid wins ties with spacing.
45
+
46
+ ### Grid
47
+
48
+ The grid is off by default. `grid: 10` or `grid: { size: 10, origin: { x: 5, y: 5 } }`
49
+ snaps edges, not centers, to grid lines in drawing units. The snapper does not draw the
50
+ grid; applications draw their own, as the example does with a CSS background.
51
+
52
+ ### Equal spacing
53
+
54
+ Spacing is on by default and independent of `shapes`. A move can center a shape between
55
+ two neighbors, or put it one existing gap away from a neighbor; existing gaps may come
56
+ from another row or column. Neighbors must overlap the shape on the other axis.
57
+ Backgrounds and panels that contain the shape are not neighbors, while partial overlaps
58
+ block a gap. Resizing corrects only the dragged edge. `spacing: false` turns it off;
59
+ turn off both `shapes` and `spacing` for grid-only snapping.
60
+
61
+ ### Guides
62
+
63
+ Guides and gap markers live in their own SVG overlay beside the drawing, so they never
64
+ appear in exports, queries or history. Strokes stay one client pixel wide at every zoom.
65
+ `style: { stroke, gapStroke }` sets their colors (default `#f00`). The overlay redraws
66
+ once per animation frame while dragging; `snapper.update()` redraws it immediately.
67
+ With a viewport, list the snapper beside the transformer:
68
+
69
+ ```ts
70
+ import { Viewport } from '@rasamjs/viewport';
71
+ const viewport = new Viewport(drawing, { overlays: [transformer, snapper] });
72
+ ```
73
+
74
+ ### Lifecycle
75
+
76
+ Construct the snapper with a live, mounted rasam SVG drawing. It starts enabled. Options
77
+ are fixed at construction; to change them, destroy the snapper and create another
78
+ behind a small provider object, as the example does. Destroying the drawing destroys its
79
+ snappers. `destroy()` ends an active drag session, removes the overlay and can be
80
+ repeated safely.
81
+
82
+ ### Other tools
83
+
84
+ Drawing tools and the path editor can snap single points with the same engine:
85
+
86
+ ```ts
87
+ const client = drawing.toClient({ x: 8, y: 8 })!;
88
+ const session = snapper.begin({ kind: 'point', client });
89
+ const corrected = session?.snap(client, { ignore: false }) ?? client;
90
+ session?.end();
91
+ ```
92
+
93
+ `session.snap(client, { ignore: ctrlKey || metaKey })` implements the bypass;
94
+ `modifier: 'none'` ignores that flag.
95
+
96
+ ## Example
97
+
98
+ Run `npm run dev -w @rasamjs/tools` and open [the snapping example](examples/snapping.html).
99
+ It combines selection, transformer, viewport and snapper, with toggles for snapping,
100
+ shapes, grid and spacing. The server uses port **4179** and refuses an occupied port.
101
+
102
+ See [API.md](API.md), [RELEASING.md](RELEASING.md) and [CHANGELOG.md](CHANGELOG.md).
package/RELEASING.md ADDED
@@ -0,0 +1,75 @@
1
+ # Releasing tools
2
+
3
+ The package remains private. Its initial candidate is `@rasamjs/tools@0.1.0`, with
4
+ `@rasamjs/core@^0.1.0` as its only runtime peer. History, selection, transformer and viewport
5
+ are development dependencies for the example and tests.
6
+
7
+ The snapping entry has a **6 KiB (6,144 bytes) gzip** budget with all core entry points
8
+ external. The owner approved increasing Phase 1's 4 KiB cap during Phase 4 on 2026-10-31
9
+ after guide rendering measured 4,544 gzip bytes, leaving room for Phase 5 spacing.
10
+ There is no root entry or root bundle budget.
11
+
12
+ ```sh
13
+ npm run build
14
+ npm run release:check -w @rasamjs/tools
15
+ npm run release:check
16
+ ```
17
+
18
+ The tools package gate checks formatting, typechecking, lifecycle unit and browser
19
+ tests, build output, exported declarations, documentation links, the packed consumer
20
+ and bundle size, then runs the drag benchmark. Browser tests use port **4179** in
21
+ Chromium, Firefox and WebKit; the benchmark uses **4189**. Both refuse to reuse an
22
+ existing server. Coordinate these ports before running the gate.
23
+
24
+ ## Drag benchmark
25
+
26
+ `npm run bench:drag -w @rasamjs/tools` moves one shape with the transformer over 1,000
27
+ targets: a background sibling and 999 small rectangles in rows, with shapes, a 10-unit
28
+ grid and spacing enabled. It times 11 direct `begin()` calls (target measurement, line
29
+ sorting and gap discovery), reporting the first, cold call apart from the 10 warm ones.
30
+ It then sends 600 synthetic pointer moves during native mouse capture in burst and
31
+ paced modes, and writes [drag-benchmark.json](reports/drag-benchmark.json); CI uploads
32
+ it as `tools-drag-benchmark`. Timings are observations with no hardware threshold. The
33
+ run fails unless each drag opens one session, snaps once per applied frame, shows
34
+ guides and hides them on release, follows the corrected pointer exactly, records one
35
+ undo step and undoes to the exact original markup.
36
+
37
+ Run the full workspace gate before merging when its other package ports are available.
38
+ Do not run it alongside another session's gate. The owner has deferred the local full
39
+ workspace gate pending coordination; a passing tools gate does not complete that check.
40
+ The shared runner registers tools after its development dependencies and rechecks source
41
+ fingerprints after every package gate.
42
+
43
+ Build the package and review its emitted declarations before updating the API contract:
44
+
45
+ ```sh
46
+ npm run api:update -w @rasamjs/tools
47
+ npm run api:check -w @rasamjs/tools
48
+ ```
49
+
50
+ The packed consumer installs only core and tools, typechecks through default `dist`
51
+ resolution, verifies document-free imports and source maps, and rejects the package
52
+ root and internal subpaths. Development packages must not become runtime requirements.
53
+ Reports in `reports/` record the reviewed API, size and candidate readiness. The release
54
+ gate builds a candidate tarball; it does not publish it.
55
+
56
+ Phase 2 adds the engine and options. Its unit suite covers pure box/line/session math
57
+ without a DOM, plus drawing adapters and lifecycle. Browser tests verify engine calls
58
+ through zoomed and panned roots, nested rotations/viewports, multi-parent selections,
59
+ resize eligibility, hidden targets and modifiers. Phase 3 adds the approved transformer
60
+ bridge, real single/group mouse drags, exact undo/cancellation and provider cleanup.
61
+ The packed consumer first verifies tools independently, then installs the transformer
62
+ tarball to typecheck the real structural bridge.
63
+ Phase 4 adds guide rendering and accepted-geometry checks, animation-frame coalescing,
64
+ window listener cleanup, the `update()`/`style.stroke` public API and viewport overlay
65
+ wiring. Browser tests cover fixed pixel dimensions, multiple zoom levels, page scroll,
66
+ mid-drag camera changes, transformed containers and constraint rejection.
67
+ Phase 5 adds the DOM-free spacing engine, accepted gap-marker geometry, independent
68
+ `spacing`/`shapes` flags and `style.gapStroke`. Discovery sorts cached far edges once
69
+ and stops at each target's nearest eligible preceding edge, with quadratic worst-case
70
+ behavior; targets containing a box are backgrounds, not neighbors or obstructions.
71
+ Pointer queries scan cached boxes/gaps. Tests cover both equal-neighbor and existing-gap
72
+ patterns, backgrounds, dragged/centered resize, tie rules, scale-dependent tolerance,
73
+ real undo and fixed-pixel markers. The 6 KiB cap remains unchanged.
74
+ Phase 6 adds the drag benchmark and the example's shape, grid and spacing toggles,
75
+ with an application-drawn grid. It changes no runtime source.
@@ -0,0 +1,14 @@
1
+ import type { Box, Point, Svg } from '@rasamjs/core';
2
+ import type { Match } from './lines.js';
3
+ import type { SnapRequest } from './types.js';
4
+ export interface Guide {
5
+ readonly axis: 'x' | 'y';
6
+ readonly position: number;
7
+ readonly from: number;
8
+ readonly to: number;
9
+ readonly marks: readonly number[];
10
+ readonly gap?: boolean;
11
+ }
12
+ /** Read accepted geometry after the transformer has applied its own constraints. */
13
+ export declare function guideReader(drawing: Svg, request: SnapRequest, boxes: readonly Box[]): (matches: readonly (Match | null)[], point: Point | null) => Guide[];
14
+ //# sourceMappingURL=guides.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guides.d.ts","sourceRoot":"","sources":["../../src/snapping/guides.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAW,GAAG,EAAE,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AAG9D,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACxC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,IAAI,EAAE,GAAG,GAAG,GAAG,CAAC;IACzB,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAC;IAClC,QAAQ,CAAC,GAAG,CAAC,EAAE,OAAO,CAAC;CACxB;AAmCD,oFAAoF;AACpF,wBAAgB,WAAW,CAAC,OAAO,EAAE,GAAG,EAAE,OAAO,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,GAAG,EAAE,aAKlE,SAAS,CAAC,KAAK,GAAG,IAAI,CAAC,EAAE,SAAS,KAAK,GAAG,IAAI,KAAG,KAAK,EAAE,CA6E1E"}
@@ -0,0 +1,109 @@
1
+ import { Guards } from '@rasamjs/core/advanced';
2
+ import { movingBox } from './targets.js';
3
+ function rootMatrix(drawing, node) {
4
+ return Guards.isTransformable(node)
5
+ ? drawing.worldMatrix().inverse()?.multiply(node.worldMatrix())
6
+ : null;
7
+ }
8
+ /** Follow the dragged anchor, including group flips that exchange the outer edges. */
9
+ function draggedAnchor(drawing, request) {
10
+ if (request.kind !== 'resize')
11
+ return () => null;
12
+ const nodes = [...new Set(request.nodes)];
13
+ const anchor = { ...request.anchor };
14
+ if (nodes.length === 1)
15
+ return () => {
16
+ const matrix = rootMatrix(drawing, nodes[0]), bounds = nodes[0].bounds();
17
+ return matrix && 'box' in bounds
18
+ ? matrix.apply({
19
+ x: bounds.box.x + bounds.box.width * anchor.x,
20
+ y: bounds.box.y + bounds.box.height * anchor.y,
21
+ })
22
+ : null;
23
+ };
24
+ const inverse = rootMatrix(drawing, nodes[0])?.inverse();
25
+ const corners = request.corners.map((point) => drawing.fromClient(point));
26
+ if (!inverse || corners.some((point) => !point))
27
+ return () => null;
28
+ const [a, b, , d] = corners;
29
+ const initial = {
30
+ x: a.x + (b.x - a.x) * anchor.x + (d.x - a.x) * anchor.y,
31
+ y: a.y + (b.y - a.y) * anchor.x + (d.y - a.y) * anchor.y,
32
+ };
33
+ return () => rootMatrix(drawing, nodes[0])?.multiply(inverse).apply(initial) ?? null;
34
+ }
35
+ /** Read accepted geometry after the transformer has applied its own constraints. */
36
+ export function guideReader(drawing, request, boxes) {
37
+ const pointOnly = request.kind === 'point', resizing = request.kind === 'resize';
38
+ const nodes = pointOnly ? [] : [...new Set(request.nodes)];
39
+ const dragged = draggedAnchor(drawing, request);
40
+ return (matches, point) => {
41
+ if (!point ||
42
+ drawing.isDestroyed ||
43
+ nodes.some((node) => node.isDestroyed || !drawing.el.contains(node.el)))
44
+ return [];
45
+ const box = pointOnly ? { ...point, width: 0, height: 0 } : movingBox(nodes, drawing);
46
+ if (!box)
47
+ return [];
48
+ const active = dragged();
49
+ const guides = [];
50
+ for (const [index, axis] of ['x', 'y'].entries()) {
51
+ const match = matches[index];
52
+ if (!match)
53
+ continue;
54
+ const other = axis === 'x' ? 'y' : 'x', size = axis === 'x' ? 'width' : 'height', span = other === 'x' ? 'width' : 'height';
55
+ const values = resizing
56
+ ? active
57
+ ? [active[axis]]
58
+ : []
59
+ : [box[axis], box[axis] + box[size] / 2, box[axis] + box[size]];
60
+ const value = values.reduce((best, next) => Math.abs(next - match.target) < Math.abs(best - match.target) ? next : best, Infinity);
61
+ if (!Number.isFinite(value))
62
+ continue;
63
+ const from = drawing.toClient({ ...point, [axis]: value }), to = drawing.toClient({ ...point, [axis]: match.target });
64
+ if (!from || !to || Math.hypot(to.x - from.x, to.y - from.y) > 0.5)
65
+ continue;
66
+ if (match.gaps) {
67
+ let valid = true;
68
+ const gaps = match.gaps.map((gap) => {
69
+ const from = gap.moving === 'from' ? box[axis] + box[size] : gap.from;
70
+ const to = gap.moving === 'to' ? box[axis] : gap.to;
71
+ const expected = drawing.toClient({ ...point, [axis]: gap.to - gap.from });
72
+ const accepted = drawing.toClient({ ...point, [axis]: to - from });
73
+ const neighbor = gap.neighbor === undefined ? null : boxes[gap.neighbor];
74
+ const low = neighbor ? Math.max(box[other], neighbor[other]) : gap.at;
75
+ const high = neighbor
76
+ ? Math.min(box[other] + box[span], neighbor[other] + neighbor[span])
77
+ : gap.at;
78
+ if (!expected ||
79
+ !accepted ||
80
+ to <= from ||
81
+ low > high ||
82
+ Math.hypot(accepted.x - expected.x, accepted.y - expected.y) > 0.5)
83
+ valid = false;
84
+ return {
85
+ axis: other,
86
+ position: (low + high) / 2,
87
+ from,
88
+ to,
89
+ marks: [from, to],
90
+ gap: true,
91
+ };
92
+ });
93
+ if (valid)
94
+ guides.push(...gaps);
95
+ continue;
96
+ }
97
+ const target = !match.grid && boxes[match.order];
98
+ guides.push({
99
+ axis,
100
+ position: match.target,
101
+ from: Math.min(box[other], target ? target[other] : box[other]),
102
+ to: Math.max(box[other] + box[span], target ? target[other] + target[span] : box[other] + box[span]),
103
+ marks: [box[other] + box[span] / 2, ...(target ? [target[other] + target[span] / 2] : [])],
104
+ });
105
+ }
106
+ return guides;
107
+ };
108
+ }
109
+ //# sourceMappingURL=guides.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"guides.js","sourceRoot":"","sources":["../../src/snapping/guides.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,43 @@
1
+ import type { Box } from '@rasamjs/core';
2
+ import type { Gap } from './spacing.js';
3
+ export interface MovingLine {
4
+ readonly value: number;
5
+ readonly edge: boolean;
6
+ }
7
+ export interface TargetLine {
8
+ readonly value: number;
9
+ readonly order: number;
10
+ }
11
+ export interface Features {
12
+ readonly x: readonly MovingLine[];
13
+ readonly y: readonly MovingLine[];
14
+ }
15
+ export interface Targets {
16
+ readonly x: readonly TargetLine[];
17
+ readonly y: readonly TargetLine[];
18
+ readonly boxes: readonly Box[];
19
+ }
20
+ export interface Match {
21
+ readonly delta: number;
22
+ readonly target: number;
23
+ readonly order: number;
24
+ readonly grid: boolean;
25
+ readonly gaps?: readonly Gap[];
26
+ }
27
+ interface AxisRequest {
28
+ readonly moving: readonly MovingLine[];
29
+ readonly targets: readonly TargetLine[];
30
+ readonly offset: number;
31
+ readonly tolerance: number;
32
+ readonly grid: {
33
+ readonly size: number;
34
+ readonly origin: number;
35
+ } | null;
36
+ }
37
+ export declare function boxLines(box: Box): Features;
38
+ export declare function targetLines(boxes: readonly Box[]): Targets;
39
+ export declare function prefer(current: Match | null, candidate: Match): Match;
40
+ export declare function lowerBound(lines: readonly TargetLine[], value: number): number;
41
+ export declare function snapAxis(request: AxisRequest): Match | null;
42
+ export {};
43
+ //# sourceMappingURL=lines.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lines.d.ts","sourceRoot":"","sources":["../../src/snapping/lines.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,eAAe,CAAC;AACzC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,cAAc,CAAC;AAExC,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;CACxB;AACD,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;CACxB;AACD,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAClC,QAAQ,CAAC,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;CACnC;AACD,MAAM,WAAW,OAAO;IACtB,QAAQ,CAAC,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAClC,QAAQ,CAAC,CAAC,EAAE,SAAS,UAAU,EAAE,CAAC;IAClC,QAAQ,CAAC,KAAK,EAAE,SAAS,GAAG,EAAE,CAAC;CAChC;AACD,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,GAAG,EAAE,CAAC;CAChC;AACD,UAAU,WAAW;IACnB,QAAQ,CAAC,MAAM,EAAE,SAAS,UAAU,EAAE,CAAC;IACvC,QAAQ,CAAC,OAAO,EAAE,SAAS,UAAU,EAAE,CAAC;IACxC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE;QAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;CAC1E;AAUD,wBAAgB,QAAQ,CAAC,GAAG,EAAE,GAAG,GAAG,QAAQ,CAE3C;AAQD,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,GAAG,EAAE,GAAG,OAAO,CAS1D;AAED,wBAAgB,MAAM,CAAC,OAAO,EAAE,KAAK,GAAG,IAAI,EAAE,SAAS,EAAE,KAAK,GAAG,KAAK,CAOrE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,SAAS,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAS9E;AAED,wBAAgB,QAAQ,CAAC,OAAO,EAAE,WAAW,GAAG,KAAK,GAAG,IAAI,CAuB3D"}