@wrenautomation/orrery 0.0.0-stage → 0.24.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +61 -0
- package/README.md +22 -2
- package/dist/glide.d.ts +63 -0
- package/dist/glide.js +144 -0
- package/dist/glide.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/nest.d.ts +61 -0
- package/dist/nest.js +50 -0
- package/dist/nest.js.map +1 -0
- package/dist/tree.d.ts +42 -0
- package/dist/tree.js +89 -0
- package/dist/tree.js.map +1 -0
- package/package.json +28 -3
package/LICENSE.md
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
# PolyForm Strict License 1.0.0
|
|
2
|
+
|
|
3
|
+
<https://polyformproject.org/licenses/strict/1.0.0>
|
|
4
|
+
|
|
5
|
+
## Acceptance
|
|
6
|
+
|
|
7
|
+
In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
|
|
8
|
+
|
|
9
|
+
## Copyright License
|
|
10
|
+
|
|
11
|
+
The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose, other than distributing the software or making changes or new works based on the software.
|
|
12
|
+
|
|
13
|
+
## Patent License
|
|
14
|
+
|
|
15
|
+
The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
|
|
16
|
+
|
|
17
|
+
## Noncommercial Purposes
|
|
18
|
+
|
|
19
|
+
Any noncommercial purpose is a permitted purpose.
|
|
20
|
+
|
|
21
|
+
## Personal Uses
|
|
22
|
+
|
|
23
|
+
Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, is use for a permitted purpose.
|
|
24
|
+
|
|
25
|
+
## Noncommercial Organizations
|
|
26
|
+
|
|
27
|
+
Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution is use for a permitted purpose regardless of the source of funding or obligations resulting from the funding.
|
|
28
|
+
|
|
29
|
+
## Fair Use
|
|
30
|
+
|
|
31
|
+
You may have "fair use" rights for the software under the law. These terms do not limit them.
|
|
32
|
+
|
|
33
|
+
## No Other Rights
|
|
34
|
+
|
|
35
|
+
These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
|
|
36
|
+
|
|
37
|
+
## Patent Defense
|
|
38
|
+
|
|
39
|
+
If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
|
|
40
|
+
|
|
41
|
+
## Violations
|
|
42
|
+
|
|
43
|
+
The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
|
|
44
|
+
|
|
45
|
+
## No Liability
|
|
46
|
+
|
|
47
|
+
***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
|
|
48
|
+
|
|
49
|
+
## Definitions
|
|
50
|
+
|
|
51
|
+
The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
|
|
52
|
+
|
|
53
|
+
**You** refers to the individual or entity agreeing to these terms.
|
|
54
|
+
|
|
55
|
+
**Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
|
|
56
|
+
|
|
57
|
+
**Your licenses** are all the licenses granted to you for the software under these terms.
|
|
58
|
+
|
|
59
|
+
**Use** means anything you do with the software requiring one of your licenses.
|
|
60
|
+
|
|
61
|
+
Required Notice: Copyright 2026 William Jin (https://wrenautomation.com)
|
package/README.md
CHANGED
|
@@ -1,3 +1,23 @@
|
|
|
1
|
-
#
|
|
1
|
+
# orrery
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
The logic of a map you fold. No renderer, no framework: each map draws its own frames.
|
|
4
|
+
|
|
5
|
+
- `tree`: the fold tree. A depth cut, plus nodes opened or closed by hand. `openSet`, `toggle`, `reveal`, `stepDepth`, `fullDepth`.
|
|
6
|
+
- `nest`: nested layout over an engine you pass (ELK, a grid, your own). Open containers are sized by their kids. Positions add up to the root. A cache re-lays only the changed path.
|
|
7
|
+
- `glide`: the motion between two frames. Marks that stay slide, new ones grow out of an anchor, gone ones shrink into it. `Marks` says which elements are marks.
|
|
8
|
+
|
|
9
|
+
Depends on nothing. Runs in a browser: `glide` measures the DOM and uses the Web Animations API.
|
|
10
|
+
|
|
11
|
+
```sh
|
|
12
|
+
npm i @wrenautomation/orrery
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import { nest, openSet } from "@wrenautomation/orrery";
|
|
17
|
+
|
|
18
|
+
const open = openSet(nodes, { depth: 1, open: { "pkg:core": true } });
|
|
19
|
+
const map = await nest({ nodes, root: "repo", open, size, pad, engine: myLayout });
|
|
20
|
+
// map.boxes: absolute, parents first. map.levels: each open container's own layout.
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Used by kestrel's viewer and wren's metro map.
|
package/dist/glide.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The glide between two frames of a map (a depth step, a node opened or folded), from wren's metro
|
|
3
|
+
* map. Marks that stay slide and scale to their new place, marks that appear grow out of the
|
|
4
|
+
* anchor, marks that go shrink into it. Imperative: it measures the DOM after the renderer
|
|
5
|
+
* commits and uses the Web Animations API, so it costs no re-render. Which elements are marks is
|
|
6
|
+
* the renderer's (`Marks`).
|
|
7
|
+
*/
|
|
8
|
+
export type Point = readonly [x: number, y: number];
|
|
9
|
+
export declare const GLIDE_MS: {
|
|
10
|
+
readonly move: 460;
|
|
11
|
+
readonly fade: 180;
|
|
12
|
+
readonly shrink: 380;
|
|
13
|
+
};
|
|
14
|
+
export interface Marks {
|
|
15
|
+
/** Selector for every element that can glide. */
|
|
16
|
+
select: string;
|
|
17
|
+
/** Its key across frames. Null: it cannot glide. */
|
|
18
|
+
key(el: Element): string | null;
|
|
19
|
+
/** Selector for lines and other parts that fade in under the marks. */
|
|
20
|
+
fresh?: string;
|
|
21
|
+
/** Keys that keep their width (metro's hub). */
|
|
22
|
+
fixedWidth?(key: string): boolean;
|
|
23
|
+
/** Map units in one screen px for a frame: a transform on a mark is in its map's units. */
|
|
24
|
+
units?(frame: Element): number;
|
|
25
|
+
}
|
|
26
|
+
export interface Box {
|
|
27
|
+
n: Element & ElementCSSInlineStyle;
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
w: number;
|
|
31
|
+
h: number;
|
|
32
|
+
}
|
|
33
|
+
/** Every mark of a frame, with its centre and size on the screen, in screen px. */
|
|
34
|
+
export declare function snapshot(frame: Element | null | undefined, marks: Marks): Map<string, Box>;
|
|
35
|
+
/** The centre of an element on the screen: where a group grows from. */
|
|
36
|
+
export declare function centreOf(n: Element | null): Point | null;
|
|
37
|
+
/**
|
|
38
|
+
* Play the new frame from `before` (a snapshot taken before the renderer committed). Same-DOM
|
|
39
|
+
* renderers (a FLIP) call this; marks that went are already gone. Returns the glide's length in ms.
|
|
40
|
+
*/
|
|
41
|
+
export declare function play(o: {
|
|
42
|
+
before: Map<string, Box>;
|
|
43
|
+
next: Element;
|
|
44
|
+
marks: Marks;
|
|
45
|
+
anchor?: Point | null | undefined;
|
|
46
|
+
anchorKey?: string | null | undefined;
|
|
47
|
+
}): number;
|
|
48
|
+
/**
|
|
49
|
+
* Glide from `old` (a copy of the last frame, laid over the map) to `next` (the live frame).
|
|
50
|
+
* Marks that go shrink into the anchor; the rest of the old frame fades. Returns how long the old
|
|
51
|
+
* frame must stay, in ms.
|
|
52
|
+
*/
|
|
53
|
+
export declare function glide(o: {
|
|
54
|
+
old: Element;
|
|
55
|
+
next: Element;
|
|
56
|
+
marks: Marks;
|
|
57
|
+
anchor?: Point | null | undefined;
|
|
58
|
+
anchorKey?: string | null | undefined;
|
|
59
|
+
}): number;
|
|
60
|
+
/** Fade the new frame in when there is nothing to glide from (reduced motion). */
|
|
61
|
+
export declare function fadeIn(next: Element): void;
|
|
62
|
+
/** The map's own height eases between two frames. */
|
|
63
|
+
export declare function glideHeight(host: HTMLElement, from: number, to: number): void;
|
package/dist/glide.js
ADDED
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The glide between two frames of a map (a depth step, a node opened or folded), from wren's metro
|
|
3
|
+
* map. Marks that stay slide and scale to their new place, marks that appear grow out of the
|
|
4
|
+
* anchor, marks that go shrink into it. Imperative: it measures the DOM after the renderer
|
|
5
|
+
* commits and uses the Web Animations API, so it costs no re-render. Which elements are marks is
|
|
6
|
+
* the renderer's (`Marks`).
|
|
7
|
+
*/
|
|
8
|
+
const EASE = "cubic-bezier(.22,1,.36,1)";
|
|
9
|
+
export const GLIDE_MS = { move: 460, fade: 180, shrink: 380 };
|
|
10
|
+
/** Whether this environment can measure and animate. happy-dom cannot. */
|
|
11
|
+
const canGlide = (n) => typeof n.getBoundingClientRect === "function" && typeof n.animate === "function";
|
|
12
|
+
/** Every mark of a frame, with its centre and size on the screen, in screen px. */
|
|
13
|
+
export function snapshot(frame, marks) {
|
|
14
|
+
const out = new Map();
|
|
15
|
+
if (!frame)
|
|
16
|
+
return out;
|
|
17
|
+
for (const n of frame.querySelectorAll(marks.select)) {
|
|
18
|
+
const k = marks.key(n);
|
|
19
|
+
if (!k || !canGlide(n))
|
|
20
|
+
continue;
|
|
21
|
+
const b = n.getBoundingClientRect();
|
|
22
|
+
out.set(k, { n, x: b.left + b.width / 2, y: b.top + b.height / 2, w: b.width, h: b.height });
|
|
23
|
+
}
|
|
24
|
+
return out;
|
|
25
|
+
}
|
|
26
|
+
/** The centre of an element on the screen: where a group grows from. */
|
|
27
|
+
export function centreOf(n) {
|
|
28
|
+
if (!n || typeof n.getBoundingClientRect !== "function")
|
|
29
|
+
return null;
|
|
30
|
+
const b = n.getBoundingClientRect();
|
|
31
|
+
return [b.left + b.width / 2, b.top + b.height / 2];
|
|
32
|
+
}
|
|
33
|
+
/** SVG: from the screen matrix. Else 1; an HTML renderer under a scale passes its own. */
|
|
34
|
+
function svgUnits(frame) {
|
|
35
|
+
const a = frame.getScreenCTM?.()?.a;
|
|
36
|
+
return a ? 1 / a : 1;
|
|
37
|
+
}
|
|
38
|
+
const origin = (n) => {
|
|
39
|
+
n.style.transformBox = "fill-box";
|
|
40
|
+
n.style.transformOrigin = "center";
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Play the new frame from `before` (a snapshot taken before the renderer committed). Same-DOM
|
|
44
|
+
* renderers (a FLIP) call this; marks that went are already gone. Returns the glide's length in ms.
|
|
45
|
+
*/
|
|
46
|
+
export function play(o) {
|
|
47
|
+
const after = snapshot(o.next, o.marks);
|
|
48
|
+
const k = (o.marks.units ?? svgUnits)(o.next);
|
|
49
|
+
const to = o.anchorKey ? after.get(o.anchorKey) : undefined;
|
|
50
|
+
const anchor = to ? [to.x, to.y] : (o.anchor ?? null);
|
|
51
|
+
if (o.marks.fresh)
|
|
52
|
+
for (const n of o.next.querySelectorAll(o.marks.fresh))
|
|
53
|
+
n.animate?.([{ opacity: 0 }, { opacity: 1 }], {
|
|
54
|
+
duration: 320,
|
|
55
|
+
delay: 80,
|
|
56
|
+
easing: "ease-out",
|
|
57
|
+
fill: "backwards",
|
|
58
|
+
});
|
|
59
|
+
for (const [key, a] of after) {
|
|
60
|
+
const prev = o.before.get(key);
|
|
61
|
+
origin(a.n);
|
|
62
|
+
if (prev) {
|
|
63
|
+
const sx = o.marks.fixedWidth?.(key) ? 1 : prev.w / a.w;
|
|
64
|
+
const sy = prev.h / a.h;
|
|
65
|
+
const moved = Math.abs(prev.x - a.x) > 0.5 || Math.abs(prev.y - a.y) > 0.5;
|
|
66
|
+
if (moved || Math.abs(sy - 1) > 0.02 || Math.abs(sx - 1) > 0.02)
|
|
67
|
+
a.n.animate([
|
|
68
|
+
{
|
|
69
|
+
transform: `translate(${(prev.x - a.x) * k}px,${(prev.y - a.y) * k}px) scale(${Math.abs(sx - 1) > 0.02 ? sx : 1},${Math.abs(sy - 1) > 0.02 ? sy : 1})`,
|
|
70
|
+
},
|
|
71
|
+
{ transform: "none" },
|
|
72
|
+
], { duration: GLIDE_MS.move, easing: EASE });
|
|
73
|
+
}
|
|
74
|
+
else if (a !== to) {
|
|
75
|
+
// It grows out of the anchor, the way a closing mark goes back in.
|
|
76
|
+
const [ax, ay] = anchor ?? [a.x, a.y];
|
|
77
|
+
a.n.animate([
|
|
78
|
+
{ opacity: 0, transform: `translate(${(ax - a.x) * k}px,${(ay - a.y) * k}px) scale(.4)` },
|
|
79
|
+
{ opacity: 1, transform: "none" },
|
|
80
|
+
], { duration: GLIDE_MS.move, delay: 40, easing: EASE, fill: "backwards" });
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
// The mark a group folds into fills in as its kids reach it.
|
|
84
|
+
a.n.animate([
|
|
85
|
+
{ opacity: 0, transform: "scale(.6)" },
|
|
86
|
+
{ opacity: 1, transform: "none" },
|
|
87
|
+
], { duration: GLIDE_MS.move, delay: GLIDE_MS.shrink * 0.4, easing: EASE, fill: "backwards" });
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
return GLIDE_MS.move;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Glide from `old` (a copy of the last frame, laid over the map) to `next` (the live frame).
|
|
94
|
+
* Marks that go shrink into the anchor; the rest of the old frame fades. Returns how long the old
|
|
95
|
+
* frame must stay, in ms.
|
|
96
|
+
*/
|
|
97
|
+
export function glide(o) {
|
|
98
|
+
const before = snapshot(o.old, o.marks);
|
|
99
|
+
const after = snapshot(o.next, o.marks);
|
|
100
|
+
const kOld = (o.marks.units ?? svgUnits)(o.old);
|
|
101
|
+
const to = o.anchorKey ? after.get(o.anchorKey) : undefined;
|
|
102
|
+
const anchor = to ? [to.x, to.y] : (o.anchor ?? null);
|
|
103
|
+
const gone = [...before].filter(([k]) => !after.has(k)).map(([, b]) => b);
|
|
104
|
+
for (const [k, b] of before)
|
|
105
|
+
if (after.has(k))
|
|
106
|
+
b.n.style.opacity = "0";
|
|
107
|
+
const goneSet = new Set(gone.map((b) => b.n));
|
|
108
|
+
for (const c of o.old.children) {
|
|
109
|
+
if (goneSet.has(c) || gone.some((b) => c.contains(b.n)))
|
|
110
|
+
continue;
|
|
111
|
+
c.animate?.([{ opacity: 1 }, { opacity: 0 }], {
|
|
112
|
+
duration: GLIDE_MS.fade,
|
|
113
|
+
easing: "ease-out",
|
|
114
|
+
fill: "forwards",
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
for (const b of gone) {
|
|
118
|
+
const [ax, ay] = anchor ?? [b.x, b.y];
|
|
119
|
+
origin(b.n);
|
|
120
|
+
b.n.animate([
|
|
121
|
+
{ opacity: 1, transform: "none" },
|
|
122
|
+
{
|
|
123
|
+
opacity: 0,
|
|
124
|
+
transform: `translate(${(ax - b.x) * kOld}px,${(ay - b.y) * kOld}px) scale(.4)`,
|
|
125
|
+
},
|
|
126
|
+
], { duration: GLIDE_MS.shrink, easing: EASE, fill: "forwards" });
|
|
127
|
+
}
|
|
128
|
+
play({ before, next: o.next, marks: o.marks, anchor, anchorKey: o.anchorKey });
|
|
129
|
+
return gone.length ? GLIDE_MS.shrink : GLIDE_MS.fade;
|
|
130
|
+
}
|
|
131
|
+
/** Fade the new frame in when there is nothing to glide from (reduced motion). */
|
|
132
|
+
export function fadeIn(next) {
|
|
133
|
+
next.animate?.([{ opacity: 0 }, { opacity: 1 }], { duration: 200 });
|
|
134
|
+
}
|
|
135
|
+
/** The map's own height eases between two frames. */
|
|
136
|
+
export function glideHeight(host, from, to) {
|
|
137
|
+
if (from === to)
|
|
138
|
+
return;
|
|
139
|
+
host.animate?.([{ height: `${from}px` }, { height: `${to}px` }], {
|
|
140
|
+
duration: GLIDE_MS.move,
|
|
141
|
+
easing: EASE,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=glide.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"glide.js","sourceRoot":"","sources":["../src/glide.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,WAAW,CAAC;AAC1B,cAAc,WAAW,CAAC"}
|
package/dist/nest.d.ts
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Nested layout over a fold: each open container lays out its kids once, an open kid's size is its
|
|
3
|
+
* own layout plus padding, and positions add up to the root. The engine is the caller's (ELK,
|
|
4
|
+
* a grid, metro's placement): orrery only orders the calls, sizes the containers and caches.
|
|
5
|
+
*/
|
|
6
|
+
import type { FoldNode } from "./tree.js";
|
|
7
|
+
export interface Size {
|
|
8
|
+
w: number;
|
|
9
|
+
h: number;
|
|
10
|
+
}
|
|
11
|
+
/** A kid as the engine gets it: open kids are sized by their own layout. */
|
|
12
|
+
export interface Kid extends Size {
|
|
13
|
+
id: string;
|
|
14
|
+
open: boolean;
|
|
15
|
+
}
|
|
16
|
+
/** One container's layout, kid positions relative to its content box. `extra`: the engine's own (edge routes). */
|
|
17
|
+
export interface Laid<T = unknown> extends Size {
|
|
18
|
+
at: Readonly<Record<string, readonly [x: number, y: number]>>;
|
|
19
|
+
extra?: T;
|
|
20
|
+
}
|
|
21
|
+
export type Engine<T> = (container: string, kids: readonly Kid[]) => Promise<Laid<T>>;
|
|
22
|
+
/** Room an open container keeps around its kids: a title bar on top. */
|
|
23
|
+
export interface Pad {
|
|
24
|
+
top: number;
|
|
25
|
+
side: number;
|
|
26
|
+
bottom: number;
|
|
27
|
+
}
|
|
28
|
+
/** A box on the canvas, absolute. `parent`: the open container it is drawn in. */
|
|
29
|
+
export interface Placed extends Size {
|
|
30
|
+
id: string;
|
|
31
|
+
x: number;
|
|
32
|
+
y: number;
|
|
33
|
+
open: boolean;
|
|
34
|
+
parent?: string;
|
|
35
|
+
}
|
|
36
|
+
/** An open container's layout, with the absolute origin of its content box. */
|
|
37
|
+
export interface Level<T> {
|
|
38
|
+
id: string;
|
|
39
|
+
x: number;
|
|
40
|
+
y: number;
|
|
41
|
+
laid: Laid<T>;
|
|
42
|
+
}
|
|
43
|
+
export interface Nested<T> extends Size {
|
|
44
|
+
/** Parents before their kids. */
|
|
45
|
+
boxes: Placed[];
|
|
46
|
+
levels: Level<T>[];
|
|
47
|
+
}
|
|
48
|
+
/** Layouts by container and input; a fold re-lays only the containers on the changed path. */
|
|
49
|
+
export type NestCache<T> = Map<string, Laid<T>>;
|
|
50
|
+
export declare function nest<T>(o: {
|
|
51
|
+
nodes: readonly FoldNode[];
|
|
52
|
+
/** The container drawn as the canvas: always laid out, never a box. Its kids name it as parent. */
|
|
53
|
+
root: string;
|
|
54
|
+
/** From `openSet`: the containers that show open. */
|
|
55
|
+
open: ReadonlySet<string>;
|
|
56
|
+
/** A closed box's size. */
|
|
57
|
+
size(id: string): Size;
|
|
58
|
+
pad: Pad;
|
|
59
|
+
engine: Engine<T>;
|
|
60
|
+
cache?: NestCache<T>;
|
|
61
|
+
}): Promise<Nested<T>>;
|
package/dist/nest.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export async function nest(o) {
|
|
2
|
+
const kids = new Map();
|
|
3
|
+
for (const n of o.nodes)
|
|
4
|
+
if (n.parent)
|
|
5
|
+
(kids.get(n.parent) ?? kids.set(n.parent, []).get(n.parent))?.push(n.id);
|
|
6
|
+
const opens = (id) => o.open.has(id) && (kids.get(id)?.length ?? 0) > 0;
|
|
7
|
+
const laid = new Map();
|
|
8
|
+
const lay = async (id) => {
|
|
9
|
+
const list = await Promise.all((kids.get(id) ?? []).map(async (k) => {
|
|
10
|
+
if (!opens(k))
|
|
11
|
+
return { id: k, ...o.size(k), open: false };
|
|
12
|
+
const in_ = await lay(k);
|
|
13
|
+
return {
|
|
14
|
+
id: k,
|
|
15
|
+
w: Math.max(o.size(k).w, in_.w + 2 * o.pad.side),
|
|
16
|
+
h: in_.h + o.pad.top + o.pad.bottom,
|
|
17
|
+
open: true,
|
|
18
|
+
};
|
|
19
|
+
}));
|
|
20
|
+
const key = `${id}\0${JSON.stringify(list)}`;
|
|
21
|
+
const hit = o.cache?.get(key) ?? (await o.engine(id, list));
|
|
22
|
+
o.cache?.set(key, hit);
|
|
23
|
+
laid.set(id, hit);
|
|
24
|
+
return hit;
|
|
25
|
+
};
|
|
26
|
+
const top = await lay(o.root);
|
|
27
|
+
const boxes = [];
|
|
28
|
+
const levels = [];
|
|
29
|
+
const place = (id, x, y, parent) => {
|
|
30
|
+
const l = laid.get(id);
|
|
31
|
+
levels.push({ id, x, y, laid: l });
|
|
32
|
+
for (const k of kids.get(id) ?? []) {
|
|
33
|
+
const [kx, ky] = l.at[k] ?? [0, 0];
|
|
34
|
+
const open = opens(k);
|
|
35
|
+
const inner = open ? laid.get(k) : undefined;
|
|
36
|
+
const s = inner
|
|
37
|
+
? {
|
|
38
|
+
w: Math.max(o.size(k).w, inner.w + 2 * o.pad.side),
|
|
39
|
+
h: inner.h + o.pad.top + o.pad.bottom,
|
|
40
|
+
}
|
|
41
|
+
: o.size(k);
|
|
42
|
+
boxes.push({ id: k, x: x + kx, y: y + ky, ...s, open, ...(parent ? { parent } : {}) });
|
|
43
|
+
if (inner)
|
|
44
|
+
place(k, x + kx + (s.w - inner.w) / 2, y + ky + o.pad.top, k);
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
place(o.root, 0, 0);
|
|
48
|
+
return { w: top.w, h: top.h, boxes, levels };
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=nest.js.map
|
package/dist/nest.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"nest.js","sourceRoot":"","sources":["../src/nest.ts"],"names":[],"mappings":"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"}
|
package/dist/tree.d.ts
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fold tree (from wren's metro map, designs/2026-10-23-metro-fold-tree.md). A node holds
|
|
3
|
+
* marks and nodes, to any depth. The viewer's fold is a depth cut and hand changes on top: a node is open when its depth is
|
|
4
|
+
* under the cut, unless the viewer opened or closed it by hand.
|
|
5
|
+
*/
|
|
6
|
+
export interface FoldNode {
|
|
7
|
+
id: string;
|
|
8
|
+
/** The node it sits in. None: the map itself. */
|
|
9
|
+
parent?: string | undefined;
|
|
10
|
+
/** The name in the breadcrumb. */
|
|
11
|
+
label: string;
|
|
12
|
+
/** Opens by hand only: the depth cut never opens it. */
|
|
13
|
+
hand?: boolean | undefined;
|
|
14
|
+
}
|
|
15
|
+
/** Nodes opened (true) or closed (false) by hand, over the depth cut. */
|
|
16
|
+
export type OpenMap = Readonly<Record<string, boolean>>;
|
|
17
|
+
/** What the viewer folded: the depth cut and the hand changes. */
|
|
18
|
+
export interface Fold {
|
|
19
|
+
depth: number;
|
|
20
|
+
open: OpenMap;
|
|
21
|
+
}
|
|
22
|
+
/** How deep each node sits: 0 for a node in the map itself. */
|
|
23
|
+
export declare function depthsOf(nodes: readonly FoldNode[]): Map<string, number>;
|
|
24
|
+
/** The cut that opens every node. 0 when there is none. */
|
|
25
|
+
export declare function fullDepth(nodes: readonly FoldNode[]): number;
|
|
26
|
+
/** The node and the nodes it sits in, outermost first. */
|
|
27
|
+
export declare function pathTo(nodes: readonly FoldNode[], id: string): FoldNode[];
|
|
28
|
+
/**
|
|
29
|
+
* The nodes that show open: open by the cut or by hand, inside nodes that show open too. A node
|
|
30
|
+
* inside a closed one never counts, so two folds that draw the same map give the same set.
|
|
31
|
+
*/
|
|
32
|
+
export declare function openSet(nodes: readonly FoldNode[], fold: Fold): Set<string>;
|
|
33
|
+
/** A stable key for an open set. */
|
|
34
|
+
export declare const openKey: (open: ReadonlySet<string>) => string;
|
|
35
|
+
/** Open or close one node by hand. Opening opens the nodes it sits in too. */
|
|
36
|
+
export declare function toggle(nodes: readonly FoldNode[], fold: Fold, id: string, on: boolean): Fold;
|
|
37
|
+
/** Open the path to a node and close the rest. */
|
|
38
|
+
export declare function reveal(nodes: readonly FoldNode[], id: string | undefined): Fold;
|
|
39
|
+
/** Step the cut by `d` (pinch in: +1, out: -1). Hand changes clear. */
|
|
40
|
+
export declare function stepDepth(nodes: readonly FoldNode[], depth: number, d: number): Fold;
|
|
41
|
+
/** Every node open, the ones that open by hand too: what a focus shows. */
|
|
42
|
+
export declare const allOpen: (nodes: readonly FoldNode[]) => Set<string>;
|
package/dist/tree.js
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The fold tree (from wren's metro map, designs/2026-10-23-metro-fold-tree.md). A node holds
|
|
3
|
+
* marks and nodes, to any depth. The viewer's fold is a depth cut and hand changes on top: a node is open when its depth is
|
|
4
|
+
* under the cut, unless the viewer opened or closed it by hand.
|
|
5
|
+
*/
|
|
6
|
+
/** How deep each node sits: 0 for a node in the map itself. */
|
|
7
|
+
export function depthsOf(nodes) {
|
|
8
|
+
const by = new Map(nodes.map((n) => [n.id, n]));
|
|
9
|
+
const out = new Map();
|
|
10
|
+
const at = (id, seen) => {
|
|
11
|
+
const known = out.get(id);
|
|
12
|
+
if (known !== undefined)
|
|
13
|
+
return known;
|
|
14
|
+
const p = by.get(id)?.parent;
|
|
15
|
+
// A parent that is not a node, or a loop, ends the walk.
|
|
16
|
+
const d = p && by.has(p) && seen < by.size ? at(p, seen + 1) + 1 : 0;
|
|
17
|
+
out.set(id, d);
|
|
18
|
+
return d;
|
|
19
|
+
};
|
|
20
|
+
for (const n of nodes)
|
|
21
|
+
at(n.id, 0);
|
|
22
|
+
return out;
|
|
23
|
+
}
|
|
24
|
+
/** The cut that opens every node. 0 when there is none. */
|
|
25
|
+
export function fullDepth(nodes) {
|
|
26
|
+
const depth = depthsOf(nodes);
|
|
27
|
+
const cut = nodes.filter((n) => !n.hand).map((n) => (depth.get(n.id) ?? 0) + 1);
|
|
28
|
+
return cut.length ? Math.max(...cut) : 0;
|
|
29
|
+
}
|
|
30
|
+
/** The node and the nodes it sits in, outermost first. */
|
|
31
|
+
export function pathTo(nodes, id) {
|
|
32
|
+
const by = new Map(nodes.map((n) => [n.id, n]));
|
|
33
|
+
const out = [];
|
|
34
|
+
for (let n = by.get(id); n && out.length <= by.size; n = n.parent ? by.get(n.parent) : undefined)
|
|
35
|
+
out.unshift(n);
|
|
36
|
+
return out;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The nodes that show open: open by the cut or by hand, inside nodes that show open too. A node
|
|
40
|
+
* inside a closed one never counts, so two folds that draw the same map give the same set.
|
|
41
|
+
*/
|
|
42
|
+
export function openSet(nodes, fold) {
|
|
43
|
+
const depth = depthsOf(nodes);
|
|
44
|
+
const own = (n) => fold.open[n.id] ?? (!n.hand && (depth.get(n.id) ?? 0) < fold.depth);
|
|
45
|
+
const out = new Set();
|
|
46
|
+
// Outermost first, so a parent is decided before its children.
|
|
47
|
+
for (const n of [...nodes].sort((a, b) => (depth.get(a.id) ?? 0) - (depth.get(b.id) ?? 0)))
|
|
48
|
+
if (own(n) && (!n.parent || out.has(n.parent) || !depth.has(n.parent)))
|
|
49
|
+
out.add(n.id);
|
|
50
|
+
return out;
|
|
51
|
+
}
|
|
52
|
+
/** A stable key for an open set. */
|
|
53
|
+
export const openKey = (open) => [...open].sort().join("|");
|
|
54
|
+
/** Open or close one node by hand. Opening opens the nodes it sits in too. */
|
|
55
|
+
export function toggle(nodes, fold, id, on) {
|
|
56
|
+
const now = openSet(nodes, fold);
|
|
57
|
+
const depth = depthsOf(nodes);
|
|
58
|
+
const next = { ...fold.open };
|
|
59
|
+
const by = new Map(nodes.map((n) => [n.id, n]));
|
|
60
|
+
const set = (k, v) => {
|
|
61
|
+
// A change that equals the cut is no change.
|
|
62
|
+
if (v === (!by.get(k)?.hand && (depth.get(k) ?? 0) < fold.depth))
|
|
63
|
+
delete next[k];
|
|
64
|
+
else
|
|
65
|
+
next[k] = v;
|
|
66
|
+
};
|
|
67
|
+
if (on)
|
|
68
|
+
for (const n of pathTo(nodes, id))
|
|
69
|
+
if (!now.has(n.id))
|
|
70
|
+
set(n.id, true);
|
|
71
|
+
if (!on)
|
|
72
|
+
set(id, false);
|
|
73
|
+
return { depth: fold.depth, open: next };
|
|
74
|
+
}
|
|
75
|
+
/** Open the path to a node and close the rest. */
|
|
76
|
+
export function reveal(nodes, id) {
|
|
77
|
+
return {
|
|
78
|
+
depth: 0,
|
|
79
|
+
open: Object.fromEntries((id ? pathTo(nodes, id) : []).map((n) => [n.id, true])),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
/** Step the cut by `d` (pinch in: +1, out: -1). Hand changes clear. */
|
|
83
|
+
export function stepDepth(nodes, depth, d) {
|
|
84
|
+
const full = fullDepth(nodes);
|
|
85
|
+
return { depth: Math.max(0, Math.min(full, Math.min(depth, full) + d)), open: {} };
|
|
86
|
+
}
|
|
87
|
+
/** Every node open, the ones that open by hand too: what a focus shows. */
|
|
88
|
+
export const allOpen = (nodes) => new Set(nodes.map((n) => n.id));
|
|
89
|
+
//# sourceMappingURL=tree.js.map
|
package/dist/tree.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tree.js","sourceRoot":"","sources":["../src/tree.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,31 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wrenautomation/orrery",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
3
|
+
"version": "0.24.1",
|
|
4
|
+
"description": "The logic of a map you fold: the fold tree, nested layout over any engine, and the glide between frames. No renderer, no framework.",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/wrenautomation/engine.git",
|
|
8
|
+
"directory": "packages/orrery"
|
|
9
|
+
},
|
|
10
|
+
"license": "PolyForm-Strict-1.0.0",
|
|
11
|
+
"files": [
|
|
12
|
+
"dist",
|
|
13
|
+
"!dist/**/*.test.*",
|
|
14
|
+
"README.md",
|
|
15
|
+
"LICENSE.md"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"type": "module",
|
|
21
|
+
"exports": {
|
|
22
|
+
".": {
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"import": "./dist/index.js"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"typecheck": "tsc --noEmit",
|
|
29
|
+
"build": "sh ../../scripts/build.sh"
|
|
30
|
+
}
|
|
6
31
|
}
|