machinalayout 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +15 -0
- package/dist/chunk-2ZQ2RFFI.js +400 -0
- package/dist/chunk-33CKBEJH.js +186 -0
- package/dist/{chunk-TR24ERZT.js → chunk-SVWYWI7I.js} +3 -10
- package/dist/chunk-VREK57S3.js +13 -0
- package/dist/{chunk-HU6XYOH7.js → chunk-ZVDE7PX4.js} +106 -17
- package/dist/debugOverlay-pJpj0n5H.d.ts +125 -0
- package/dist/deus/index.d.ts +14 -0
- package/dist/deus/index.js +26 -0
- package/dist/handoff/index.d.ts +44 -0
- package/dist/handoff/index.js +83 -0
- package/dist/index.d.ts +46 -5
- package/dist/index.js +168 -3
- package/dist/inspect/index.d.ts +8 -0
- package/dist/inspect/index.js +97 -0
- package/dist/react/index.d.ts +10 -2
- package/dist/react/index.js +4 -2
- package/dist/react-native/index.d.ts +1 -1
- package/dist/react-native/index.js +2 -1
- package/dist/screenCatalog-ZjonGiOi.d.ts +46 -0
- package/dist/{types-BudfpzZX.d.ts → types-B90jb3RW.d.ts} +1 -1
- package/dist/types-DLYAhNXw.d.ts +32 -0
- package/dist/vue/index.d.ts +1 -1
- package/dist/vue/index.js +2 -1
- package/docs/deusmachina.md +108 -0
- package/docs/error-codes.md +11 -0
- package/docs/inspection-and-handoff.md +126 -0
- package/docs/react-adapter.md +23 -0
- package/docs/screen-catalog-and-viewports.md +124 -0
- package/docs/stack-geometry-helpers.md +115 -0
- package/package.json +127 -115
package/README.md
CHANGED
|
@@ -55,6 +55,8 @@ Machina adapters ask you to learn one layout model: Machina records. The framewo
|
|
|
55
55
|
- React Native: `machinalayout/text/react-native`
|
|
56
56
|
- Vue DOM: `machinalayout/text/vue`
|
|
57
57
|
|
|
58
|
+
Inspection and handoff utilities are available at `machinalayout/inspect` and `machinalayout/handoff`.
|
|
59
|
+
|
|
58
60
|
Subpath imports are preferred for adapters/renderers. Root imports remain valid during `0.x` compatibility windows.
|
|
59
61
|
|
|
60
62
|
Framework peers are adapter-specific (`react`/`react-dom`, `react-native`, `vue`) based on the subpaths you use.
|
|
@@ -79,6 +81,7 @@ Normal users should not need to learn each framework's layout/template box-drawi
|
|
|
79
81
|
- bounded `z`
|
|
80
82
|
- named layers
|
|
81
83
|
- layout interpolation helpers
|
|
84
|
+
- stack geometry/content query helpers
|
|
82
85
|
|
|
83
86
|
### React
|
|
84
87
|
|
|
@@ -116,6 +119,17 @@ Normal users should not need to learn each framework's layout/template box-drawi
|
|
|
116
119
|
|
|
117
120
|
Named layers organize paint order over the existing bounded `z` system. Layers are not portals.
|
|
118
121
|
|
|
122
|
+
|
|
123
|
+
## Docs index
|
|
124
|
+
|
|
125
|
+
- [Row model](docs/row-model.md)
|
|
126
|
+
- [Frames and stack](docs/frames-and-stack.md)
|
|
127
|
+
- [Grid arrange](docs/grid-arrange.md)
|
|
128
|
+
- [Stack geometry helpers](docs/stack-geometry-helpers.md)
|
|
129
|
+
- [Screen catalog and viewport matrix](docs/screen-catalog-and-viewports.md)
|
|
130
|
+
- [Inspection and handoff bundles](docs/inspection-and-handoff.md)
|
|
131
|
+
- [Error codes](docs/error-codes.md)
|
|
132
|
+
|
|
119
133
|
## Tiny `LayoutRow[]` example
|
|
120
134
|
|
|
121
135
|
```ts
|
|
@@ -383,4 +397,5 @@ This repo uses Biome.
|
|
|
383
397
|
- [Z-order and containment](docs/z-order-and-containment.md)
|
|
384
398
|
- [Error code reference](docs/error-codes.md)
|
|
385
399
|
- [MachinaDispatch runtime guide](docs/machina-dispatch.md)
|
|
400
|
+
- [DeusMachina behavioral kernel](docs/deusmachina.md)
|
|
386
401
|
- Dispatch sample: [`samples/dispatch-counter`](samples/dispatch-counter/README.md) (uses `machinalayout/dispatch`)
|
|
@@ -0,0 +1,400 @@
|
|
|
1
|
+
// src/deus/types.ts
|
|
2
|
+
var DeusMachinaError = class extends Error {
|
|
3
|
+
constructor(code, message) {
|
|
4
|
+
super(message);
|
|
5
|
+
this.code = code;
|
|
6
|
+
this.name = "DeusMachinaError";
|
|
7
|
+
}
|
|
8
|
+
code;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// src/deus/utility.ts
|
|
12
|
+
function finite(value, code, label) {
|
|
13
|
+
if (!Number.isFinite(value)) throw new DeusMachinaError(code, `${label} must be finite`);
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
function judgeUtility(context, candidates, options = {}) {
|
|
17
|
+
if (options.hysteresis !== void 0 && (!Number.isFinite(options.hysteresis) || options.hysteresis < 0)) {
|
|
18
|
+
throw new DeusMachinaError("InvalidHysteresis", "hysteresis must be finite and >= 0");
|
|
19
|
+
}
|
|
20
|
+
const results = candidates.map((candidate, index) => {
|
|
21
|
+
const eligible = candidate.when?.(context) ?? true;
|
|
22
|
+
const score = eligible ? finite(
|
|
23
|
+
typeof candidate.score === "function" ? candidate.score(context) : candidate.score,
|
|
24
|
+
"InvalidUtilityScore",
|
|
25
|
+
`utility score for ${candidate.key}`
|
|
26
|
+
) : 0;
|
|
27
|
+
const reason = typeof candidate.reason === "function" ? candidate.reason(context) : candidate.reason;
|
|
28
|
+
return {
|
|
29
|
+
key: candidate.key,
|
|
30
|
+
eligible,
|
|
31
|
+
score,
|
|
32
|
+
index,
|
|
33
|
+
...reason !== void 0 ? { reason } : null
|
|
34
|
+
};
|
|
35
|
+
});
|
|
36
|
+
let selected = results.filter((r) => r.eligible).reduce(
|
|
37
|
+
(best, r) => best === null || r.score > best.score ? r : best,
|
|
38
|
+
null
|
|
39
|
+
);
|
|
40
|
+
if (selected && options.previousKey !== void 0 && options.hysteresis !== void 0) {
|
|
41
|
+
const previous = results.find((r) => r.key === options.previousKey && r.eligible);
|
|
42
|
+
if (previous && selected.key !== previous.key && selected.score - previous.score < options.hysteresis)
|
|
43
|
+
selected = previous;
|
|
44
|
+
}
|
|
45
|
+
return { selected, candidates: results };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// src/deus/machine.ts
|
|
49
|
+
function assertValidDeusPath(path, label) {
|
|
50
|
+
if (!Array.isArray(path) || path.length === 0) {
|
|
51
|
+
throw new DeusMachinaError("InvalidDeusPath", `${label} must be a non-empty path`);
|
|
52
|
+
}
|
|
53
|
+
path.forEach((segment, index) => {
|
|
54
|
+
if (typeof segment !== "string" || segment.length === 0 || segment.trim().length === 0) {
|
|
55
|
+
throw new DeusMachinaError(
|
|
56
|
+
"InvalidDeusPath",
|
|
57
|
+
`${label} segment ${index} must be a non-empty string`
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
function formatDeusPath(path) {
|
|
63
|
+
assertValidDeusPath(path, "path");
|
|
64
|
+
return path.join("/");
|
|
65
|
+
}
|
|
66
|
+
function sameDeusPath(a, b) {
|
|
67
|
+
assertValidDeusPath(a, "left path");
|
|
68
|
+
assertValidDeusPath(b, "right path");
|
|
69
|
+
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
70
|
+
}
|
|
71
|
+
function isDeusAncestorPath(ancestor, path) {
|
|
72
|
+
assertValidDeusPath(ancestor, "ancestor path");
|
|
73
|
+
assertValidDeusPath(path, "path");
|
|
74
|
+
return ancestor.length <= path.length && ancestor.every((v, i) => v === path[i]);
|
|
75
|
+
}
|
|
76
|
+
function finite2(value, code, label) {
|
|
77
|
+
if (!Number.isFinite(value)) throw new DeusMachinaError(code, `${label} must be finite`);
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
function pathKey(path) {
|
|
81
|
+
return formatDeusPath(path);
|
|
82
|
+
}
|
|
83
|
+
function validateReason(reason, code, label) {
|
|
84
|
+
if (reason !== void 0 && typeof reason !== "string" && typeof reason !== "function") {
|
|
85
|
+
throw new DeusMachinaError(code, `${label} reason must be a string or function`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
function defineDeusMachine(machine) {
|
|
89
|
+
if (!machine || typeof machine !== "object") {
|
|
90
|
+
throw new DeusMachinaError("InvalidDeusMachine", "machine must be an object");
|
|
91
|
+
}
|
|
92
|
+
assertValidDeusPath(machine.initial, "initial");
|
|
93
|
+
if (!Array.isArray(machine.states)) {
|
|
94
|
+
throw new DeusMachinaError("InvalidDeusMachine", "states must be an array");
|
|
95
|
+
}
|
|
96
|
+
if (!Array.isArray(machine.transitions)) {
|
|
97
|
+
throw new DeusMachinaError("InvalidDeusMachine", "transitions must be an array");
|
|
98
|
+
}
|
|
99
|
+
const stateKeys = /* @__PURE__ */ new Set();
|
|
100
|
+
const states = machine.states.map((s) => {
|
|
101
|
+
assertValidDeusPath(s.path, "state path");
|
|
102
|
+
const key = pathKey(s.path);
|
|
103
|
+
if (stateKeys.has(key))
|
|
104
|
+
throw new DeusMachinaError("DuplicateDeusStatePath", `duplicate state path ${key}`);
|
|
105
|
+
stateKeys.add(key);
|
|
106
|
+
return { ...s, path: [...s.path] };
|
|
107
|
+
});
|
|
108
|
+
if (!stateKeys.has(pathKey(machine.initial)))
|
|
109
|
+
throw new DeusMachinaError("UnknownDeusStatePath", "initial path must exist");
|
|
110
|
+
const transitionKeys = /* @__PURE__ */ new Set();
|
|
111
|
+
const transitions = machine.transitions.map((t) => {
|
|
112
|
+
if (typeof t.key !== "string" || t.key.length === 0 || t.key.trim().length === 0)
|
|
113
|
+
throw new DeusMachinaError("InvalidDeusTransition", "transition keys must be non-empty");
|
|
114
|
+
if (transitionKeys.has(t.key))
|
|
115
|
+
throw new DeusMachinaError("DuplicateDeusTransitionKey", `duplicate transition key ${t.key}`);
|
|
116
|
+
transitionKeys.add(t.key);
|
|
117
|
+
assertValidDeusPath(t.from, `transition ${t.key} from`);
|
|
118
|
+
if (!stateKeys.has(pathKey(t.from)))
|
|
119
|
+
throw new DeusMachinaError(
|
|
120
|
+
"UnknownDeusStatePath",
|
|
121
|
+
`transition ${t.key} from path must exist`
|
|
122
|
+
);
|
|
123
|
+
if (Array.isArray(t.to)) {
|
|
124
|
+
assertValidDeusPath(t.to, `transition ${t.key} to`);
|
|
125
|
+
if (!stateKeys.has(pathKey(t.to)))
|
|
126
|
+
throw new DeusMachinaError(
|
|
127
|
+
"UnknownDeusStatePath",
|
|
128
|
+
`transition ${t.key} to path must exist`
|
|
129
|
+
);
|
|
130
|
+
} else if (t.to !== void 0 && typeof t.to !== "function") {
|
|
131
|
+
throw new DeusMachinaError(
|
|
132
|
+
"InvalidDeusTransition",
|
|
133
|
+
`transition ${t.key} to must be a path or function`
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
if (typeof t.score === "number")
|
|
137
|
+
finite2(t.score, "InvalidDeusTransition", `transition ${t.key} score`);
|
|
138
|
+
validateReason(t.reason, "InvalidDeusTransition", `transition ${t.key}`);
|
|
139
|
+
if (t.hysteresis !== void 0) {
|
|
140
|
+
if (typeof t.hysteresis.previous !== "function")
|
|
141
|
+
throw new DeusMachinaError(
|
|
142
|
+
"InvalidHysteresis",
|
|
143
|
+
`transition ${t.key} hysteresis.previous must be a function`
|
|
144
|
+
);
|
|
145
|
+
finite2(t.hysteresis.margin, "InvalidHysteresis", `transition ${t.key} hysteresis margin`);
|
|
146
|
+
if (t.hysteresis.margin < 0)
|
|
147
|
+
throw new DeusMachinaError(
|
|
148
|
+
"InvalidHysteresis",
|
|
149
|
+
`transition ${t.key} hysteresis margin must be >= 0`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
const utilityKeys = /* @__PURE__ */ new Set();
|
|
153
|
+
for (const u of t.utility ?? []) {
|
|
154
|
+
if (typeof u.key !== "string" || u.key.length === 0 || u.key.trim().length === 0)
|
|
155
|
+
throw new DeusMachinaError(
|
|
156
|
+
"InvalidDeusTransition",
|
|
157
|
+
`transition ${t.key} utility key must be non-empty`
|
|
158
|
+
);
|
|
159
|
+
if (utilityKeys.has(u.key))
|
|
160
|
+
throw new DeusMachinaError("DuplicateUtilityKey", `duplicate utility key ${u.key}`);
|
|
161
|
+
utilityKeys.add(u.key);
|
|
162
|
+
if (typeof u.score !== "number" && typeof u.score !== "function")
|
|
163
|
+
throw new DeusMachinaError(
|
|
164
|
+
"InvalidUtilityScore",
|
|
165
|
+
`utility score for ${u.key} must be a number or function`
|
|
166
|
+
);
|
|
167
|
+
if (typeof u.score === "number")
|
|
168
|
+
finite2(u.score, "InvalidUtilityScore", `utility score for ${u.key}`);
|
|
169
|
+
validateReason(u.reason, "InvalidDeusTransition", `utility ${u.key}`);
|
|
170
|
+
}
|
|
171
|
+
return { ...t, from: [...t.from], to: Array.isArray(t.to) ? [...t.to] : t.to };
|
|
172
|
+
});
|
|
173
|
+
return { initial: [...machine.initial], states, transitions };
|
|
174
|
+
}
|
|
175
|
+
function createDeusSnapshot(machine, board) {
|
|
176
|
+
return { state: [...machine.initial], board, stepIndex: 0 };
|
|
177
|
+
}
|
|
178
|
+
function stepDeusMachine(machine, snapshot, event) {
|
|
179
|
+
const stateBefore = [...snapshot.state];
|
|
180
|
+
assertValidDeusPath(stateBefore, "snapshot state");
|
|
181
|
+
const stateMap = new Map(machine.states.map((s) => [pathKey(s.path), s]));
|
|
182
|
+
const orderedFrom = stateBefore.map((_, i) => stateBefore.slice(0, stateBefore.length - i));
|
|
183
|
+
const candidates = orderedFrom.flatMap(
|
|
184
|
+
(from) => machine.transitions.map((t) => ({ t })).filter(({ t }) => sameDeusPath(t.from, from))
|
|
185
|
+
);
|
|
186
|
+
const traces = [];
|
|
187
|
+
let selected;
|
|
188
|
+
candidates.forEach(({ t }, index) => {
|
|
189
|
+
const eventMatches = t.event === void 0 || t.event === event.type;
|
|
190
|
+
let eligible = eventMatches && (t.when?.(snapshot.board, event) ?? true);
|
|
191
|
+
let utility;
|
|
192
|
+
let utilityKey;
|
|
193
|
+
let score = eligible ? t.score === void 0 ? 1 : finite2(
|
|
194
|
+
typeof t.score === "function" ? t.score(snapshot.board, event) : t.score,
|
|
195
|
+
"InvalidDeusTransition",
|
|
196
|
+
`transition ${t.key} score`
|
|
197
|
+
) : 0;
|
|
198
|
+
if (eligible && t.utility) {
|
|
199
|
+
utility = judgeUtility(
|
|
200
|
+
{ board: snapshot.board, event },
|
|
201
|
+
t.utility.map((u) => ({
|
|
202
|
+
key: u.key,
|
|
203
|
+
when: (ctx) => u.when?.(ctx.board, ctx.event) ?? true,
|
|
204
|
+
score: (ctx) => typeof u.score === "function" ? u.score(ctx.board, ctx.event) : u.score,
|
|
205
|
+
reason: typeof u.reason === "function" ? (ctx) => {
|
|
206
|
+
const reason2 = u.reason;
|
|
207
|
+
return typeof reason2 === "function" ? reason2(ctx.board, ctx.event) : "";
|
|
208
|
+
} : u.reason
|
|
209
|
+
})),
|
|
210
|
+
t.hysteresis ? { previousKey: t.hysteresis.previous(snapshot.board), hysteresis: t.hysteresis.margin } : void 0
|
|
211
|
+
);
|
|
212
|
+
if (!utility.selected) eligible = false;
|
|
213
|
+
else {
|
|
214
|
+
utilityKey = utility.selected.key;
|
|
215
|
+
if (t.score === void 0) score = utility.selected.score;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
const to = eligible && t.to ? typeof t.to === "function" ? [...t.to(snapshot.board, event)] : [...t.to] : void 0;
|
|
219
|
+
if (to) {
|
|
220
|
+
assertValidDeusPath(to, `transition ${t.key} to`);
|
|
221
|
+
if (!stateMap.has(pathKey(to)))
|
|
222
|
+
throw new DeusMachinaError(
|
|
223
|
+
"UnknownDeusStatePath",
|
|
224
|
+
`transition ${t.key} to path must exist`
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
const reason = typeof t.reason === "function" ? t.reason(snapshot.board, event) : t.reason;
|
|
228
|
+
const trace = {
|
|
229
|
+
key: t.key,
|
|
230
|
+
from: [...t.from],
|
|
231
|
+
...to ? { to } : null,
|
|
232
|
+
event: t.event,
|
|
233
|
+
eligible,
|
|
234
|
+
score: eligible ? score : 0,
|
|
235
|
+
index,
|
|
236
|
+
...reason !== void 0 ? { reason } : null,
|
|
237
|
+
...utility ? { utility } : null
|
|
238
|
+
};
|
|
239
|
+
traces.push(trace);
|
|
240
|
+
if (eligible && (!selected || trace.score > selected.trace.score))
|
|
241
|
+
selected = { trace, t, utilityKey };
|
|
242
|
+
});
|
|
243
|
+
if (!selected)
|
|
244
|
+
return {
|
|
245
|
+
snapshot: { state: stateBefore, board: snapshot.board, stepIndex: snapshot.stepIndex + 1 },
|
|
246
|
+
trace: { stateBefore, stateAfter: stateBefore, event: event.type, transitions: traces }
|
|
247
|
+
};
|
|
248
|
+
const target = selected.trace.to ?? stateBefore;
|
|
249
|
+
const common = stateBefore.findIndex((v, i) => target[i] !== v);
|
|
250
|
+
const prefix = common === -1 ? Math.min(stateBefore.length, target.length) : common;
|
|
251
|
+
for (let i = stateBefore.length; i > prefix; i--)
|
|
252
|
+
stateMap.get(pathKey(stateBefore.slice(0, i)))?.onExit?.(snapshot.board, event);
|
|
253
|
+
if (selected.utilityKey)
|
|
254
|
+
selected.t.utility?.find((u) => u.key === selected?.utilityKey)?.do?.(snapshot.board, event);
|
|
255
|
+
selected.t.do?.(snapshot.board, event);
|
|
256
|
+
for (let i = prefix + 1; i <= target.length; i++)
|
|
257
|
+
stateMap.get(pathKey(target.slice(0, i)))?.onEnter?.(snapshot.board, event);
|
|
258
|
+
return {
|
|
259
|
+
snapshot: { state: [...target], board: snapshot.board, stepIndex: snapshot.stepIndex + 1 },
|
|
260
|
+
trace: {
|
|
261
|
+
stateBefore,
|
|
262
|
+
stateAfter: [...target],
|
|
263
|
+
event: event.type,
|
|
264
|
+
selectedTransition: selected.trace,
|
|
265
|
+
transitions: traces
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
function formatDeusStepTrace(trace) {
|
|
270
|
+
const selected = trace.selectedTransition ? trace.selectedTransition.key : "none";
|
|
271
|
+
return `${formatDeusPath(trace.stateBefore)} --${trace.event}/${selected}--> ${formatDeusPath(trace.stateAfter)}`;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// src/deus/debugOverlay.ts
|
|
275
|
+
var collapsed = ["debugOverlay", "collapsed"];
|
|
276
|
+
var overlay = ["debugOverlay", "nonInteractiveOverlay"];
|
|
277
|
+
var panel = ["debugOverlay", "interactivePanel"];
|
|
278
|
+
function createMachinaDebugOverlayMachine() {
|
|
279
|
+
return defineDeusMachine({
|
|
280
|
+
initial: collapsed,
|
|
281
|
+
states: [
|
|
282
|
+
{
|
|
283
|
+
path: collapsed,
|
|
284
|
+
onEnter: (b) => {
|
|
285
|
+
b.mode = "collapsed";
|
|
286
|
+
b.selectedNodeId = void 0;
|
|
287
|
+
}
|
|
288
|
+
},
|
|
289
|
+
{
|
|
290
|
+
path: overlay,
|
|
291
|
+
onEnter: (b) => {
|
|
292
|
+
b.mode = "nonInteractiveOverlay";
|
|
293
|
+
}
|
|
294
|
+
},
|
|
295
|
+
{
|
|
296
|
+
path: panel,
|
|
297
|
+
onEnter: (b) => {
|
|
298
|
+
b.mode = "interactivePanel";
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
],
|
|
302
|
+
transitions: [
|
|
303
|
+
{ key: "collapsed.showOverlay", from: collapsed, event: "showOverlay", to: overlay },
|
|
304
|
+
{
|
|
305
|
+
key: "overlay.openPanel",
|
|
306
|
+
from: overlay,
|
|
307
|
+
event: "openPanel",
|
|
308
|
+
to: panel,
|
|
309
|
+
do: (b, e) => {
|
|
310
|
+
if (e.type === "openPanel") b.selectedNodeId = e.nodeId;
|
|
311
|
+
}
|
|
312
|
+
},
|
|
313
|
+
{ key: "panel.showOverlay", from: panel, event: "showOverlay", to: overlay },
|
|
314
|
+
{ key: "overlay.collapse", from: overlay, event: "collapse", to: collapsed },
|
|
315
|
+
{ key: "panel.collapse", from: panel, event: "collapse", to: collapsed },
|
|
316
|
+
{
|
|
317
|
+
key: "overlay.toggleLabels",
|
|
318
|
+
from: overlay,
|
|
319
|
+
event: "toggleLabels",
|
|
320
|
+
do: (b) => {
|
|
321
|
+
b.labels = !b.labels;
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
key: "panel.toggleLabels",
|
|
326
|
+
from: panel,
|
|
327
|
+
event: "toggleLabels",
|
|
328
|
+
do: (b) => {
|
|
329
|
+
b.labels = !b.labels;
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
key: "overlay.toggleBorders",
|
|
334
|
+
from: overlay,
|
|
335
|
+
event: "toggleBorders",
|
|
336
|
+
do: (b) => {
|
|
337
|
+
b.borders = !b.borders;
|
|
338
|
+
}
|
|
339
|
+
},
|
|
340
|
+
{
|
|
341
|
+
key: "panel.toggleBorders",
|
|
342
|
+
from: panel,
|
|
343
|
+
event: "toggleBorders",
|
|
344
|
+
do: (b) => {
|
|
345
|
+
b.borders = !b.borders;
|
|
346
|
+
}
|
|
347
|
+
},
|
|
348
|
+
{
|
|
349
|
+
key: "panel.selectNode",
|
|
350
|
+
from: panel,
|
|
351
|
+
event: "selectNode",
|
|
352
|
+
do: (b, e) => {
|
|
353
|
+
if (e.type === "selectNode") b.selectedNodeId = e.nodeId;
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
]
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
function getMachinaDebugOverlayBehavior(board) {
|
|
360
|
+
if (board.mode === "collapsed")
|
|
361
|
+
return {
|
|
362
|
+
visible: false,
|
|
363
|
+
pointerEvents: "none",
|
|
364
|
+
consumesLayoutSpace: false,
|
|
365
|
+
showPanel: false,
|
|
366
|
+
showLabels: false,
|
|
367
|
+
showBorders: false
|
|
368
|
+
};
|
|
369
|
+
if (board.mode === "nonInteractiveOverlay")
|
|
370
|
+
return {
|
|
371
|
+
visible: true,
|
|
372
|
+
pointerEvents: "none",
|
|
373
|
+
consumesLayoutSpace: false,
|
|
374
|
+
showPanel: false,
|
|
375
|
+
showLabels: board.labels,
|
|
376
|
+
showBorders: board.borders
|
|
377
|
+
};
|
|
378
|
+
return {
|
|
379
|
+
visible: true,
|
|
380
|
+
pointerEvents: "auto",
|
|
381
|
+
consumesLayoutSpace: true,
|
|
382
|
+
showPanel: true,
|
|
383
|
+
showLabels: board.labels,
|
|
384
|
+
showBorders: board.borders
|
|
385
|
+
};
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
export {
|
|
389
|
+
DeusMachinaError,
|
|
390
|
+
judgeUtility,
|
|
391
|
+
formatDeusPath,
|
|
392
|
+
sameDeusPath,
|
|
393
|
+
isDeusAncestorPath,
|
|
394
|
+
defineDeusMachine,
|
|
395
|
+
createDeusSnapshot,
|
|
396
|
+
stepDeusMachine,
|
|
397
|
+
formatDeusStepTrace,
|
|
398
|
+
createMachinaDebugOverlayMachine,
|
|
399
|
+
getMachinaDebugOverlayBehavior
|
|
400
|
+
};
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import {
|
|
2
|
+
MachinaLayoutError
|
|
3
|
+
} from "./chunk-VREK57S3.js";
|
|
4
|
+
|
|
5
|
+
// src/screenCatalog.ts
|
|
6
|
+
var STANDARD_VIEWPORTS = [
|
|
7
|
+
{ key: "desktop", width: 1440, height: 900, label: "Desktop", tags: ["desktop"] },
|
|
8
|
+
{ key: "tablet", width: 1024, height: 768, label: "Tablet", tags: ["tablet"] },
|
|
9
|
+
{ key: "phone", width: 390, height: 844, label: "Phone", tags: ["phone", "mobile"] }
|
|
10
|
+
];
|
|
11
|
+
function isPositiveFiniteNumber(value) {
|
|
12
|
+
return typeof value === "number" && Number.isFinite(value) && value > 0;
|
|
13
|
+
}
|
|
14
|
+
function validateStringArray(value, code, field) {
|
|
15
|
+
if (value === void 0) return;
|
|
16
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
|
|
17
|
+
throw new MachinaLayoutError(code, `${field} must be an array of strings`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
function copyViewport(viewport) {
|
|
21
|
+
return {
|
|
22
|
+
...viewport,
|
|
23
|
+
tags: viewport.tags === void 0 ? void 0 : [...viewport.tags]
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
function copyScreen(screen) {
|
|
27
|
+
return {
|
|
28
|
+
...screen,
|
|
29
|
+
viewports: screen.viewports === void 0 ? void 0 : [...screen.viewports],
|
|
30
|
+
tags: screen.tags === void 0 ? void 0 : [...screen.tags]
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
function defineMachinaViewports(viewports) {
|
|
34
|
+
const seen = /* @__PURE__ */ new Set();
|
|
35
|
+
return viewports.map((viewport) => {
|
|
36
|
+
if (typeof viewport.key !== "string" || viewport.key.trim() === "") {
|
|
37
|
+
throw new MachinaLayoutError("InvalidViewport", "viewport key must be a non-empty string");
|
|
38
|
+
}
|
|
39
|
+
if (seen.has(viewport.key)) {
|
|
40
|
+
throw new MachinaLayoutError(
|
|
41
|
+
"DuplicateViewportKey",
|
|
42
|
+
`duplicate viewport key: ${viewport.key}`
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
seen.add(viewport.key);
|
|
46
|
+
if (!isPositiveFiniteNumber(viewport.width) || !isPositiveFiniteNumber(viewport.height)) {
|
|
47
|
+
throw new MachinaLayoutError(
|
|
48
|
+
"InvalidViewport",
|
|
49
|
+
`viewport ${viewport.key} width and height must be finite positive numbers`
|
|
50
|
+
);
|
|
51
|
+
}
|
|
52
|
+
if (viewport.deviceScaleFactor !== void 0 && !isPositiveFiniteNumber(viewport.deviceScaleFactor)) {
|
|
53
|
+
throw new MachinaLayoutError(
|
|
54
|
+
"InvalidViewport",
|
|
55
|
+
`viewport ${viewport.key} deviceScaleFactor must be a finite positive number`
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (viewport.label !== void 0 && typeof viewport.label !== "string") {
|
|
59
|
+
throw new MachinaLayoutError(
|
|
60
|
+
"InvalidViewport",
|
|
61
|
+
`viewport ${viewport.key} label must be a string`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
validateStringArray(viewport.tags, "InvalidViewport", `viewport ${viewport.key} tags`);
|
|
65
|
+
return copyViewport(viewport);
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
function createViewportMatrix(preset = "standard-responsive") {
|
|
69
|
+
if (preset === "desktop-only") return defineMachinaViewports([STANDARD_VIEWPORTS[0]]);
|
|
70
|
+
if (preset === "mobile-first")
|
|
71
|
+
return defineMachinaViewports([
|
|
72
|
+
STANDARD_VIEWPORTS[2],
|
|
73
|
+
STANDARD_VIEWPORTS[1],
|
|
74
|
+
STANDARD_VIEWPORTS[0]
|
|
75
|
+
]);
|
|
76
|
+
return defineMachinaViewports(STANDARD_VIEWPORTS);
|
|
77
|
+
}
|
|
78
|
+
function defineMachinaScreens(screens) {
|
|
79
|
+
const catalog = { screens: {}, order: [] };
|
|
80
|
+
for (const screen of screens) {
|
|
81
|
+
if (typeof screen.key !== "string" || screen.key.trim() === "") {
|
|
82
|
+
throw new MachinaLayoutError("InvalidScreen", "screen key must be a non-empty string");
|
|
83
|
+
}
|
|
84
|
+
if (catalog.screens[screen.key] !== void 0) {
|
|
85
|
+
throw new MachinaLayoutError("DuplicateScreenKey", `duplicate screen key: ${screen.key}`);
|
|
86
|
+
}
|
|
87
|
+
if (typeof screen.route !== "string" || screen.route.trim() === "") {
|
|
88
|
+
throw new MachinaLayoutError(
|
|
89
|
+
"InvalidScreen",
|
|
90
|
+
`screen ${screen.key} route must be a non-empty string`
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
if (screen.fixture !== void 0 && typeof screen.fixture !== "string") {
|
|
94
|
+
throw new MachinaLayoutError(
|
|
95
|
+
"InvalidScreen",
|
|
96
|
+
`screen ${screen.key} fixture must be a string`
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
validateStringArray(screen.viewports, "InvalidScreen", `screen ${screen.key} viewports`);
|
|
100
|
+
validateStringArray(screen.tags, "InvalidScreen", `screen ${screen.key} tags`);
|
|
101
|
+
if (screen.metadata !== void 0 && (typeof screen.metadata !== "object" || screen.metadata === null || Array.isArray(screen.metadata))) {
|
|
102
|
+
throw new MachinaLayoutError(
|
|
103
|
+
"InvalidScreen",
|
|
104
|
+
`screen ${screen.key} metadata must be an object`
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
catalog.screens[screen.key] = copyScreen(screen);
|
|
108
|
+
catalog.order.push(screen.key);
|
|
109
|
+
}
|
|
110
|
+
return catalog;
|
|
111
|
+
}
|
|
112
|
+
function slugMachinaArtifactName(input) {
|
|
113
|
+
const slug = input.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
|
|
114
|
+
return slug === "" ? "artifact" : slug;
|
|
115
|
+
}
|
|
116
|
+
function getMachinaViewport(viewports, key) {
|
|
117
|
+
const viewport = viewports.find((candidate) => candidate.key === key);
|
|
118
|
+
if (!viewport) throw new MachinaLayoutError("UnknownViewportKey", `unknown viewport key: ${key}`);
|
|
119
|
+
return viewport;
|
|
120
|
+
}
|
|
121
|
+
function orderedUnique(values) {
|
|
122
|
+
const result = [];
|
|
123
|
+
for (const value of values ?? []) {
|
|
124
|
+
if (!result.includes(value)) result.push(value);
|
|
125
|
+
}
|
|
126
|
+
return result;
|
|
127
|
+
}
|
|
128
|
+
function expandScreenViewportTasks(catalog, viewports, options = {}) {
|
|
129
|
+
const viewportKeys = viewports.map((viewport) => viewport.key);
|
|
130
|
+
const viewportKeySet = new Set(viewportKeys);
|
|
131
|
+
for (const key of options.screenKeys ?? []) {
|
|
132
|
+
if (catalog.screens[key] === void 0)
|
|
133
|
+
throw new MachinaLayoutError("UnknownScreenKey", `unknown screen key: ${key}`);
|
|
134
|
+
}
|
|
135
|
+
for (const key of options.viewportKeys ?? []) {
|
|
136
|
+
if (!viewportKeySet.has(key))
|
|
137
|
+
throw new MachinaLayoutError("UnknownViewportKey", `unknown viewport key: ${key}`);
|
|
138
|
+
}
|
|
139
|
+
const requestedScreens = options.screenKeys === void 0 ? void 0 : new Set(options.screenKeys);
|
|
140
|
+
const requestedViewports = options.viewportKeys === void 0 ? void 0 : new Set(options.viewportKeys);
|
|
141
|
+
const tasks = [];
|
|
142
|
+
for (const screenKey of catalog.order) {
|
|
143
|
+
if (requestedScreens && !requestedScreens.has(screenKey)) continue;
|
|
144
|
+
const screen = catalog.screens[screenKey];
|
|
145
|
+
if (!screen)
|
|
146
|
+
throw new MachinaLayoutError(
|
|
147
|
+
"UnknownScreenKey",
|
|
148
|
+
`unknown screen key in catalog order: ${screenKey}`
|
|
149
|
+
);
|
|
150
|
+
const screenViewportSet = screen.viewports === void 0 ? void 0 : new Set(screen.viewports);
|
|
151
|
+
for (const key of screen.viewports ?? []) {
|
|
152
|
+
if (!viewportKeySet.has(key))
|
|
153
|
+
throw new MachinaLayoutError(
|
|
154
|
+
"UnknownViewportKey",
|
|
155
|
+
`screen ${screen.key} references unknown viewport key: ${key}`
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
for (const viewport of viewports) {
|
|
159
|
+
if (screenViewportSet && !screenViewportSet.has(viewport.key)) continue;
|
|
160
|
+
if (requestedViewports && !requestedViewports.has(viewport.key)) continue;
|
|
161
|
+
const tags = orderedUnique([...screen.tags ?? [], ...viewport.tags ?? []]);
|
|
162
|
+
if ((options.tags ?? []).some((tag) => !tags.includes(tag))) continue;
|
|
163
|
+
tasks.push({
|
|
164
|
+
key: `${screen.key}__${viewport.key}`,
|
|
165
|
+
screenKey: screen.key,
|
|
166
|
+
viewportKey: viewport.key,
|
|
167
|
+
route: screen.route,
|
|
168
|
+
fixture: screen.fixture,
|
|
169
|
+
viewport,
|
|
170
|
+
screen,
|
|
171
|
+
tags,
|
|
172
|
+
artifactBaseName: `${slugMachinaArtifactName(screen.key)}__${slugMachinaArtifactName(viewport.key)}`
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return tasks;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
export {
|
|
180
|
+
defineMachinaViewports,
|
|
181
|
+
createViewportMatrix,
|
|
182
|
+
defineMachinaScreens,
|
|
183
|
+
slugMachinaArtifactName,
|
|
184
|
+
getMachinaViewport,
|
|
185
|
+
expandScreenViewportTasks
|
|
186
|
+
};
|
|
@@ -1,12 +1,6 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
constructor(code, message) {
|
|
5
|
-
super(message);
|
|
6
|
-
this.name = "MachinaLayoutError";
|
|
7
|
-
this.code = code;
|
|
8
|
-
}
|
|
9
|
-
};
|
|
1
|
+
import {
|
|
2
|
+
MachinaLayoutError
|
|
3
|
+
} from "./chunk-VREK57S3.js";
|
|
10
4
|
|
|
11
5
|
// src/toResolvedTree.ts
|
|
12
6
|
function toResolvedTree(document) {
|
|
@@ -61,6 +55,5 @@ function toResolvedTree(document) {
|
|
|
61
55
|
}
|
|
62
56
|
|
|
63
57
|
export {
|
|
64
|
-
MachinaLayoutError,
|
|
65
58
|
toResolvedTree
|
|
66
59
|
};
|