markswift 0.0.0-stage → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +234 -0
- package/LICENSE +21 -0
- package/README.md +592 -2
- package/dist/cli-jdrbzqfa.js +2975 -0
- package/dist/cli-qchh0m4d.js +14041 -0
- package/dist/cli.js +637 -0
- package/dist/compiler/index.js +20 -0
- package/dist/index.js +22 -0
- package/dist/types/analysis/alignment.d.ts +12 -0
- package/dist/types/analysis/bands.d.ts +53 -0
- package/dist/types/analysis/breakpoints.d.ts +22 -0
- package/dist/types/analysis/class-hints.d.ts +21 -0
- package/dist/types/analysis/context.d.ts +49 -0
- package/dist/types/analysis/controls.d.ts +97 -0
- package/dist/types/analysis/flex-row.d.ts +14 -0
- package/dist/types/analysis/flex-sizing.d.ts +5 -0
- package/dist/types/analysis/flow-layout.d.ts +18 -0
- package/dist/types/analysis/generated.d.ts +16 -0
- package/dist/types/analysis/geometry.d.ts +50 -0
- package/dist/types/analysis/grid-tracks.d.ts +44 -0
- package/dist/types/analysis/grid.d.ts +5 -0
- package/dist/types/analysis/infer.d.ts +18 -0
- package/dist/types/analysis/invisible.d.ts +6 -0
- package/dist/types/analysis/layers.d.ts +15 -0
- package/dist/types/analysis/measured-layout.d.ts +6 -0
- package/dist/types/analysis/motion.d.ts +29 -0
- package/dist/types/analysis/naming.d.ts +6 -0
- package/dist/types/analysis/node-base.d.ts +13 -0
- package/dist/types/analysis/size-constraints.d.ts +5 -0
- package/dist/types/analysis/sizing.d.ts +24 -0
- package/dist/types/analysis/style-background.d.ts +24 -0
- package/dist/types/analysis/style-border.d.ts +7 -0
- package/dist/types/analysis/style-effects.d.ts +4 -0
- package/dist/types/analysis/style.d.ts +9 -0
- package/dist/types/analysis/svg/attributes.d.ts +10 -0
- package/dist/types/analysis/svg/build.d.ts +6 -0
- package/dist/types/analysis/svg/context.d.ts +31 -0
- package/dist/types/analysis/svg/geometry.d.ts +11 -0
- package/dist/types/analysis/svg/gradients.d.ts +6 -0
- package/dist/types/analysis/svg/markup.d.ts +23 -0
- package/dist/types/analysis/svg/text.d.ts +7 -0
- package/dist/types/analysis/svg/transforms.d.ts +19 -0
- package/dist/types/analysis/svg/traversal.d.ts +4 -0
- package/dist/types/analysis/table-borders.d.ts +19 -0
- package/dist/types/analysis/table.d.ts +36 -0
- package/dist/types/analysis/text-layout.d.ts +65 -0
- package/dist/types/analysis/text-runs.d.ts +30 -0
- package/dist/types/analysis/text.d.ts +9 -0
- package/dist/types/analysis/tooltips.d.ts +16 -0
- package/dist/types/analysis/typography.d.ts +21 -0
- package/dist/types/analysis/wrapping.d.ts +5 -0
- package/dist/types/api/bands.d.ts +58 -0
- package/dist/types/api/build.d.ts +51 -0
- package/dist/types/api/convert.d.ts +13 -0
- package/dist/types/api/types.d.ts +69 -0
- package/dist/types/browser/capture.d.ts +89 -0
- package/dist/types/browser/conditions.d.ts +25 -0
- package/dist/types/browser/fonts.d.ts +34 -0
- package/dist/types/browser/images.d.ts +3 -0
- package/dist/types/browser/layers.d.ts +132 -0
- package/dist/types/browser/motion.d.ts +44 -0
- package/dist/types/browser/settle.d.ts +27 -0
- package/dist/types/browser/sizing.d.ts +10 -0
- package/dist/types/browser/snapshot.d.ts +155 -0
- package/dist/types/browser/states.d.ts +36 -0
- package/dist/types/cli/args.d.ts +31 -0
- package/dist/types/cli/config.d.ts +78 -0
- package/dist/types/cli/css.d.ts +9 -0
- package/dist/types/cli/manifest.d.ts +20 -0
- package/dist/types/cli/output.d.ts +27 -0
- package/dist/types/cli/run.d.ts +4 -0
- package/dist/types/cli.d.ts +2 -0
- package/dist/types/compiler/index.d.ts +91 -0
- package/dist/types/compiler/naming.d.ts +1 -0
- package/dist/types/css/background.d.ts +16 -0
- package/dist/types/css/blend.d.ts +9 -0
- package/dist/types/css/color.d.ts +14 -0
- package/dist/types/css/counter.d.ts +10 -0
- package/dist/types/css/easing.d.ts +4 -0
- package/dist/types/css/effects.d.ts +18 -0
- package/dist/types/css/grid.d.ts +29 -0
- package/dist/types/css/index.d.ts +15 -0
- package/dist/types/css/media.d.ts +16 -0
- package/dist/types/css/paint.d.ts +33 -0
- package/dist/types/css/radius.d.ts +5 -0
- package/dist/types/css/raster.d.ts +10 -0
- package/dist/types/css/shadows.d.ts +3 -0
- package/dist/types/css/syntax.d.ts +12 -0
- package/dist/types/css/transforms.d.ts +45 -0
- package/dist/types/css/typography.d.ts +8 -0
- package/dist/types/fonts/converter.d.ts +16 -0
- package/dist/types/fonts/metadata.d.ts +6 -0
- package/dist/types/fonts/package.d.ts +12 -0
- package/dist/types/fonts/registration.d.ts +13 -0
- package/dist/types/fonts/source.d.ts +14 -0
- package/dist/types/fonts/types.d.ts +28 -0
- package/dist/types/html/elements.d.ts +8 -0
- package/dist/types/html/index.d.ts +4 -0
- package/dist/types/html/inputs.d.ts +4 -0
- package/dist/types/html/policy.d.ts +3 -0
- package/dist/types/html/types.d.ts +12 -0
- package/dist/types/index.d.ts +8 -0
- package/dist/types/input/identity.d.ts +3 -0
- package/dist/types/input/mime.d.ts +1 -0
- package/dist/types/input/root.d.ts +1 -0
- package/dist/types/input/server.d.ts +11 -0
- package/dist/types/input/source.d.ts +9 -0
- package/dist/types/input/types.d.ts +54 -0
- package/dist/types/model/fonts.d.ts +26 -0
- package/dist/types/model/interactive.d.ts +10 -0
- package/dist/types/model/layout.d.ts +528 -0
- package/dist/types/model/semantic.d.ts +984 -0
- package/dist/types/model/values.d.ts +125 -0
- package/dist/types/model/version.d.ts +4 -0
- package/dist/types/swift/borders.d.ts +13 -0
- package/dist/types/swift/components.d.ts +115 -0
- package/dist/types/swift/context.d.ts +184 -0
- package/dist/types/swift/control-state.d.ts +19 -0
- package/dist/types/swift/controls.d.ts +62 -0
- package/dist/types/swift/diagnostics.d.ts +3 -0
- package/dist/types/swift/effects.d.ts +23 -0
- package/dist/types/swift/emit.d.ts +6 -0
- package/dist/types/swift/format.d.ts +91 -0
- package/dist/types/swift/frames.d.ts +17 -0
- package/dist/types/swift/images.d.ts +25 -0
- package/dist/types/swift/layout.d.ts +5 -0
- package/dist/types/swift/measured-layout.d.ts +4 -0
- package/dist/types/swift/modifiers.d.ts +30 -0
- package/dist/types/swift/motion.d.ts +20 -0
- package/dist/types/swift/nodes.d.ts +4 -0
- package/dist/types/swift/paint.d.ts +22 -0
- package/dist/types/swift/runtime.d.ts +6 -0
- package/dist/types/swift/shadows.d.ts +7 -0
- package/dist/types/swift/states.d.ts +3 -0
- package/dist/types/swift/svg.d.ts +14 -0
- package/dist/types/swift/text-box.d.ts +12 -0
- package/dist/types/swift/text.d.ts +3 -0
- package/dist/types/swift/tokens/emit.d.ts +26 -0
- package/dist/types/swift/tokens/extract.d.ts +21 -0
- package/dist/types/swift/tokens/naming.d.ts +22 -0
- package/dist/types/swift/tokens/slots.d.ts +18 -0
- package/dist/types/swift/tokens/types.d.ts +77 -0
- package/dist/types/swift/tokens.d.ts +6 -0
- package/dist/types/swift/traversal.d.ts +2 -0
- package/dist/types/swift/wrapping.d.ts +4 -0
- package/package.json +66 -4
package/dist/cli.js
ADDED
|
@@ -0,0 +1,637 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
mimeType,
|
|
4
|
+
convert2,
|
|
5
|
+
buildLibrary2
|
|
6
|
+
} from "./cli-jdrbzqfa.js";
|
|
7
|
+
import {
|
|
8
|
+
validateName,
|
|
9
|
+
VERSION,
|
|
10
|
+
validateLibrary2
|
|
11
|
+
} from "./cli-qchh0m4d.js";
|
|
12
|
+
|
|
13
|
+
// src/cli/args.ts
|
|
14
|
+
class UsageError extends Error {
|
|
15
|
+
}
|
|
16
|
+
var HELP = `Markswift — convert rendered HTML and CSS to SwiftUI.
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
markswift convert <file.html|-> --out <directory> [options]
|
|
20
|
+
markswift convert --url <http(s)://...> --out <directory> [options]
|
|
21
|
+
markswift build [markswift.json] [--force]
|
|
22
|
+
Build every component of the config into one Swift package
|
|
23
|
+
(directory "out" of the config; replaces only files it wrote)
|
|
24
|
+
markswift --help | --version
|
|
25
|
+
|
|
26
|
+
Inputs:
|
|
27
|
+
<file.html> HTML document or fragment; relative assets use its directory
|
|
28
|
+
- Read HTML from stdin (inline/data assets only)
|
|
29
|
+
--url <url> Capture a running HTTP or HTTPS page
|
|
30
|
+
--css <file> Optional compiled CSS file
|
|
31
|
+
|
|
32
|
+
Options:
|
|
33
|
+
--out <directory> Required. Must not exist yet, unless --update
|
|
34
|
+
--update Regenerate into an existing --out written by Markswift: rewrites only the files
|
|
35
|
+
listed in its .markswift.json, removes listed files no longer produced, never
|
|
36
|
+
touches other files; refuses (exit 2) when a listed file was edited by hand
|
|
37
|
+
--force With --update: overwrite hand-edited listed files
|
|
38
|
+
--name <Name> Swift view name (default: ConvertedView)
|
|
39
|
+
--root <selector> Capture only this CSS selector
|
|
40
|
+
--width <px> Viewport width (default: 390)
|
|
41
|
+
--height <px> Viewport height (default: 844)
|
|
42
|
+
--theme <value> adaptive, light or dark (default: adaptive). adaptive captures the page in
|
|
43
|
+
light and dark (prefers-color-scheme) and emits one view that follows both
|
|
44
|
+
--dark-class <name>
|
|
45
|
+
Class added to <html> for the dark capture (class-based themes, e.g. dark)
|
|
46
|
+
--mode <value> app or fidelity (default: app)
|
|
47
|
+
--palette <file> JSON map from CSS variables or hex colours to app colours
|
|
48
|
+
(e.g. {"--primary": "Color.accentColor"})
|
|
49
|
+
--breakpoints <auto|W[xH],…> Capture one layout per CSS width band (auto: 390, 834x1210, 1210x834, 1376x1032) and emit one view that switches at the CSS thresholds
|
|
50
|
+
-h, --help Show this help
|
|
51
|
+
-v, --version Show version
|
|
52
|
+
|
|
53
|
+
Requires Node.js 20+ (or Bun 1.4.2+) and Google Chrome; webfonts need python3 with fontTools and brotli. CSS must already be compiled; JavaScript behavior is not translated.
|
|
54
|
+
`;
|
|
55
|
+
var valueFlags = new Set([
|
|
56
|
+
"--out",
|
|
57
|
+
"--url",
|
|
58
|
+
"--css",
|
|
59
|
+
"--name",
|
|
60
|
+
"--root",
|
|
61
|
+
"--width",
|
|
62
|
+
"--height",
|
|
63
|
+
"--theme",
|
|
64
|
+
"--dark-class",
|
|
65
|
+
"--mode",
|
|
66
|
+
"--palette",
|
|
67
|
+
"--breakpoints"
|
|
68
|
+
]);
|
|
69
|
+
function parseArgs(args) {
|
|
70
|
+
if (args.length === 0 || args.length === 1 && ["--help", "-h"].includes(args[0]))
|
|
71
|
+
return { kind: "help" };
|
|
72
|
+
if (args.length === 1 && ["--version", "-v"].includes(args[0]))
|
|
73
|
+
return { kind: "version" };
|
|
74
|
+
if (args[0] === "build")
|
|
75
|
+
return parseBuild(args.slice(1));
|
|
76
|
+
if (args[0] !== "convert")
|
|
77
|
+
throw new UsageError(`Unknown command: ${args[0]}`);
|
|
78
|
+
if (args.slice(1).some((arg) => arg === "--help" || arg === "-h"))
|
|
79
|
+
return { kind: "help" };
|
|
80
|
+
const values = new Map;
|
|
81
|
+
const flags = new Set;
|
|
82
|
+
const positional = [];
|
|
83
|
+
let afterDoubleDash = false;
|
|
84
|
+
for (let i = 1;i < args.length; i++) {
|
|
85
|
+
const arg = args[i];
|
|
86
|
+
if (arg === "--" && !afterDoubleDash) {
|
|
87
|
+
afterDoubleDash = true;
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (!afterDoubleDash && (arg === "--update" || arg === "--force")) {
|
|
91
|
+
if (flags.has(arg))
|
|
92
|
+
throw new UsageError(`Repeated option: ${arg}`);
|
|
93
|
+
flags.add(arg);
|
|
94
|
+
} else if (!afterDoubleDash && valueFlags.has(arg)) {
|
|
95
|
+
if (values.has(arg))
|
|
96
|
+
throw new UsageError(`Repeated option: ${arg}`);
|
|
97
|
+
const value = args[++i];
|
|
98
|
+
if (!value || value.startsWith("--"))
|
|
99
|
+
throw new UsageError(`Missing value for ${arg}`);
|
|
100
|
+
values.set(arg, value);
|
|
101
|
+
} else if (!afterDoubleDash && arg.startsWith("-") && arg !== "-") {
|
|
102
|
+
throw new UsageError(`Unknown option: ${arg}`);
|
|
103
|
+
} else
|
|
104
|
+
positional.push(arg);
|
|
105
|
+
}
|
|
106
|
+
const outDir = values.get("--out");
|
|
107
|
+
if (!outDir)
|
|
108
|
+
throw new UsageError("--out is required");
|
|
109
|
+
if (flags.has("--force") && !flags.has("--update"))
|
|
110
|
+
throw new UsageError("--force only applies with --update");
|
|
111
|
+
const url = values.get("--url");
|
|
112
|
+
if (positional.length + (url ? 1 : 0) !== 1)
|
|
113
|
+
throw new UsageError("Choose exactly one HTML file, -, or --url");
|
|
114
|
+
if (url) {
|
|
115
|
+
try {
|
|
116
|
+
const parsed = new URL(url);
|
|
117
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
118
|
+
throw new Error("unsupported protocol");
|
|
119
|
+
} catch {
|
|
120
|
+
throw new UsageError("--url requires an HTTP or HTTPS URL");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const source = url ? { url } : positional[0] === "-" ? { stdin: true } : { file: positional[0] };
|
|
124
|
+
const width = dimension(values.get("--width"), "--width", 390);
|
|
125
|
+
const height = dimension(values.get("--height"), "--height", 844);
|
|
126
|
+
const theme = values.get("--theme") ?? "adaptive";
|
|
127
|
+
const mode = values.get("--mode") ?? "app";
|
|
128
|
+
const name = values.get("--name");
|
|
129
|
+
if (name !== undefined) {
|
|
130
|
+
try {
|
|
131
|
+
validateName(name);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
throw new UsageError(error instanceof Error ? error.message : String(error));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (theme !== "light" && theme !== "dark" && theme !== "adaptive")
|
|
137
|
+
throw new UsageError("--theme must be adaptive, light or dark");
|
|
138
|
+
const darkClass = values.get("--dark-class");
|
|
139
|
+
if (darkClass !== undefined && !/^\S+$/.test(darkClass))
|
|
140
|
+
throw new UsageError("--dark-class must be a single class name");
|
|
141
|
+
if (mode !== "app" && mode !== "fidelity")
|
|
142
|
+
throw new UsageError("--mode must be app or fidelity");
|
|
143
|
+
const bands = values.get("--breakpoints");
|
|
144
|
+
const breakpoints = bands === undefined ? undefined : bands === "auto" ? true : {
|
|
145
|
+
devices: bands.split(",").map((device) => {
|
|
146
|
+
const m = /^(\d+)(?:x(\d+))?$/.exec(device);
|
|
147
|
+
const size = m && { width: Number(m[1]), height: m[2] === undefined ? height : Number(m[2]) };
|
|
148
|
+
if (!size || !(size.width > 0 && size.height > 0))
|
|
149
|
+
throw new UsageError("--breakpoints must be auto or a comma list of WIDTH or WIDTHxHEIGHT");
|
|
150
|
+
return size;
|
|
151
|
+
})
|
|
152
|
+
};
|
|
153
|
+
return {
|
|
154
|
+
kind: "convert",
|
|
155
|
+
source,
|
|
156
|
+
outDir,
|
|
157
|
+
cssPath: values.get("--css"),
|
|
158
|
+
palettePath: values.get("--palette"),
|
|
159
|
+
update: flags.has("--update"),
|
|
160
|
+
force: flags.has("--force"),
|
|
161
|
+
options: {
|
|
162
|
+
name,
|
|
163
|
+
root: values.get("--root"),
|
|
164
|
+
theme,
|
|
165
|
+
darkClass,
|
|
166
|
+
mode,
|
|
167
|
+
viewport: { width, height },
|
|
168
|
+
...breakpoints ? { breakpoints } : {}
|
|
169
|
+
}
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
function parseBuild(args) {
|
|
173
|
+
if (args.some((arg) => arg === "--help" || arg === "-h"))
|
|
174
|
+
return { kind: "help" };
|
|
175
|
+
let force = false;
|
|
176
|
+
const positional = [];
|
|
177
|
+
for (const arg of args) {
|
|
178
|
+
if (arg === "--force") {
|
|
179
|
+
if (force)
|
|
180
|
+
throw new UsageError("Repeated option: --force");
|
|
181
|
+
force = true;
|
|
182
|
+
} else if (arg.startsWith("-"))
|
|
183
|
+
throw new UsageError(`Unknown option: ${arg}`);
|
|
184
|
+
else
|
|
185
|
+
positional.push(arg);
|
|
186
|
+
}
|
|
187
|
+
if (positional.length > 1)
|
|
188
|
+
throw new UsageError("Choose at most one config file");
|
|
189
|
+
return { kind: "build", configPath: positional[0] ?? "markswift.json", force };
|
|
190
|
+
}
|
|
191
|
+
function dimension(value, flag, fallback) {
|
|
192
|
+
if (value === undefined)
|
|
193
|
+
return fallback;
|
|
194
|
+
const parsed = Number(value);
|
|
195
|
+
if (!/^\d+$/.test(value) || !Number.isSafeInteger(parsed) || parsed <= 0)
|
|
196
|
+
throw new UsageError(`${flag} must be a positive integer`);
|
|
197
|
+
return parsed;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/cli/manifest.ts
|
|
201
|
+
import { createHash } from "node:crypto";
|
|
202
|
+
import { lstat, readFile } from "node:fs/promises";
|
|
203
|
+
import { isAbsolute, join, win32 } from "node:path";
|
|
204
|
+
var MANIFEST = ".markswift.json";
|
|
205
|
+
|
|
206
|
+
class EditedFilesError extends Error {
|
|
207
|
+
files;
|
|
208
|
+
constructor(files, message) {
|
|
209
|
+
super(message);
|
|
210
|
+
this.files = files;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
function unsafePath(name) {
|
|
214
|
+
return !name || isAbsolute(name) || win32.isAbsolute(name) || name.includes("\\") || name.split("/").some((part) => !part || part === "." || part === "..");
|
|
215
|
+
}
|
|
216
|
+
function manifestFor(files, version) {
|
|
217
|
+
const hashes = Object.entries(files).filter(([name]) => name !== MANIFEST).map(([name, contents]) => [name, createHash("sha256").update(contents).digest("hex")]).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
|
|
218
|
+
return { tool: "markswift", version, files: Object.fromEntries(hashes) };
|
|
219
|
+
}
|
|
220
|
+
function serializeManifest(manifest) {
|
|
221
|
+
return `${JSON.stringify(manifest, null, 2)}
|
|
222
|
+
`;
|
|
223
|
+
}
|
|
224
|
+
async function readManifest(dir) {
|
|
225
|
+
let text;
|
|
226
|
+
try {
|
|
227
|
+
text = await readFile(join(dir, MANIFEST), "utf8");
|
|
228
|
+
} catch (error) {
|
|
229
|
+
if (error.code === "ENOENT")
|
|
230
|
+
return;
|
|
231
|
+
throw error;
|
|
232
|
+
}
|
|
233
|
+
let value;
|
|
234
|
+
try {
|
|
235
|
+
value = JSON.parse(text);
|
|
236
|
+
} catch {
|
|
237
|
+
value = undefined;
|
|
238
|
+
}
|
|
239
|
+
const m = value;
|
|
240
|
+
const valid = typeof m === "object" && m !== null && m.tool === "markswift" && typeof m.version === "string" && typeof m.files === "object" && m.files !== null && !Array.isArray(m.files) && Object.entries(m.files).every(([name, hash]) => !unsafePath(name) && typeof hash === "string" && /^[0-9a-f]{64}$/.test(hash));
|
|
241
|
+
if (!valid)
|
|
242
|
+
throw new Error(`${dir}/${MANIFEST} is not a Markswift manifest`);
|
|
243
|
+
return m;
|
|
244
|
+
}
|
|
245
|
+
async function editedFiles(dir, manifest) {
|
|
246
|
+
const edited = [];
|
|
247
|
+
for (const name of Object.keys(manifest.files).sort()) {
|
|
248
|
+
const file = join(dir, name);
|
|
249
|
+
let stats;
|
|
250
|
+
try {
|
|
251
|
+
stats = await lstat(file);
|
|
252
|
+
} catch (error) {
|
|
253
|
+
if (error.code === "ENOENT")
|
|
254
|
+
continue;
|
|
255
|
+
throw error;
|
|
256
|
+
}
|
|
257
|
+
if (!stats.isFile() || createHash("sha256").update(await readFile(file)).digest("hex") !== manifest.files[name])
|
|
258
|
+
edited.push(name);
|
|
259
|
+
}
|
|
260
|
+
return edited;
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// src/cli/run.ts
|
|
264
|
+
import { access as access2, readFile as readFile4 } from "node:fs/promises";
|
|
265
|
+
import { text } from "node:stream/consumers";
|
|
266
|
+
|
|
267
|
+
// src/cli/config.ts
|
|
268
|
+
import { access, readFile as readFile3 } from "node:fs/promises";
|
|
269
|
+
import { dirname as dirname2, resolve as resolve2 } from "node:path";
|
|
270
|
+
import { z } from "zod";
|
|
271
|
+
|
|
272
|
+
// src/cli/css.ts
|
|
273
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
274
|
+
import { dirname, resolve } from "node:path";
|
|
275
|
+
async function inlineRelativeUrls(css, cssPath) {
|
|
276
|
+
const dir = dirname(resolve(cssPath));
|
|
277
|
+
const missing = [];
|
|
278
|
+
const pattern = /url\(\s*(?:"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|([^)]*))\s*\)/g;
|
|
279
|
+
const inlined = [];
|
|
280
|
+
for (const match of css.matchAll(pattern)) {
|
|
281
|
+
const ref = (match[1] ?? match[2] ?? match[3] ?? "").trim();
|
|
282
|
+
if (!ref || ref.startsWith("#") || ref.startsWith("/") || /^[a-z][a-z0-9+.-]*:/i.test(ref)) {
|
|
283
|
+
inlined.push(match[0]);
|
|
284
|
+
continue;
|
|
285
|
+
}
|
|
286
|
+
const name = ref.replace(/[?#].*$/, "");
|
|
287
|
+
let bytes;
|
|
288
|
+
try {
|
|
289
|
+
bytes = await readFile2(resolve(dir, decodeURIComponent(name)));
|
|
290
|
+
} catch {
|
|
291
|
+
missing.push(ref);
|
|
292
|
+
inlined.push(match[0]);
|
|
293
|
+
continue;
|
|
294
|
+
}
|
|
295
|
+
inlined.push(`url("data:${mimeType(name)};base64,${bytes.toString("base64")}")`);
|
|
296
|
+
}
|
|
297
|
+
let i = 0;
|
|
298
|
+
return { css: css.replace(pattern, () => inlined[i++]), missing };
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// src/cli/config.ts
|
|
302
|
+
var Viewport = z.object({ width: z.number().int().positive(), height: z.number().int().positive() }).strict();
|
|
303
|
+
var Shared = {
|
|
304
|
+
root: z.string().min(1).optional(),
|
|
305
|
+
viewport: Viewport.optional(),
|
|
306
|
+
theme: z.enum(["adaptive", "light", "dark"]).optional(),
|
|
307
|
+
darkClass: z.string().regex(/^\S+$/).optional(),
|
|
308
|
+
mode: z.enum(["app", "fidelity"]).optional(),
|
|
309
|
+
css: z.string().min(1).optional(),
|
|
310
|
+
waitFor: z.string().min(1).optional(),
|
|
311
|
+
breakpoints: z.union([
|
|
312
|
+
z.boolean(),
|
|
313
|
+
z.object({ devices: z.array(Viewport).min(1).optional(), max: z.number().int().min(320).optional() }).strict()
|
|
314
|
+
]).optional()
|
|
315
|
+
};
|
|
316
|
+
var BuildConfig = z.object({
|
|
317
|
+
library: z.string(),
|
|
318
|
+
out: z.string().min(1),
|
|
319
|
+
serve: z.string().min(1).optional(),
|
|
320
|
+
timeoutMs: z.number().positive().optional(),
|
|
321
|
+
palette: z.string().min(1).optional(),
|
|
322
|
+
...Shared,
|
|
323
|
+
components: z.array(z.object({ name: z.string(), source: z.string().min(1), ...Shared }).strict()).min(1)
|
|
324
|
+
}).strict();
|
|
325
|
+
var PaletteMapFile = z.record(z.string().regex(/^(--[A-Za-z0-9_-]+|#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?(\/#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?)?)$/, "key must be --name, #rrggbb[aa] or #light/#dark"), z.string().min(1).refine((v) => !/[\r\n\u2028\u2029]/.test(v), "value must be a single-line Swift expression"));
|
|
326
|
+
async function loadPalette(path) {
|
|
327
|
+
const text = await readFile3(path, "utf8").catch(() => {
|
|
328
|
+
return;
|
|
329
|
+
});
|
|
330
|
+
if (text === undefined)
|
|
331
|
+
throw new Error(`palette map not found: ${path}`);
|
|
332
|
+
let json;
|
|
333
|
+
try {
|
|
334
|
+
json = JSON.parse(text);
|
|
335
|
+
} catch (error) {
|
|
336
|
+
throw new UsageError(`${path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
337
|
+
}
|
|
338
|
+
const parsed = PaletteMapFile.safeParse(json);
|
|
339
|
+
if (!parsed.success)
|
|
340
|
+
throw new UsageError(`Invalid palette map ${path}:
|
|
341
|
+
${parsed.error.issues.map((i) => ` ${i.path.join(".") || "(top level)"}: ${i.code === "invalid_key" ? i.issues[0]?.message : i.message}`).join(`
|
|
342
|
+
`)}`);
|
|
343
|
+
return parsed.data;
|
|
344
|
+
}
|
|
345
|
+
async function loadBuildConfig(path) {
|
|
346
|
+
const text = await readFile3(path, "utf8").catch(() => {
|
|
347
|
+
return;
|
|
348
|
+
});
|
|
349
|
+
if (text === undefined)
|
|
350
|
+
throw new Error(`Config file not found: ${path}`);
|
|
351
|
+
let json;
|
|
352
|
+
try {
|
|
353
|
+
json = JSON.parse(text);
|
|
354
|
+
} catch (error) {
|
|
355
|
+
throw new UsageError(`${path} is not valid JSON: ${error instanceof Error ? error.message : String(error)}`);
|
|
356
|
+
}
|
|
357
|
+
const parsed = BuildConfig.safeParse(json);
|
|
358
|
+
if (!parsed.success)
|
|
359
|
+
throw new UsageError(`Invalid config ${path}:
|
|
360
|
+
${parsed.error.issues.map((i) => ` ${i.path.join(".") || "(top level)"}: ${i.message}`).join(`
|
|
361
|
+
`)}`);
|
|
362
|
+
const config = parsed.data;
|
|
363
|
+
try {
|
|
364
|
+
validateLibrary2(config.library, config.components.map((c) => c.name));
|
|
365
|
+
} catch (error) {
|
|
366
|
+
throw new UsageError(`Invalid config ${path}: ${error instanceof Error ? error.message : String(error)}`);
|
|
367
|
+
}
|
|
368
|
+
const base = dirname2(resolve2(path));
|
|
369
|
+
const warnings = [];
|
|
370
|
+
const readCss = async (file, component) => {
|
|
371
|
+
const cssPath = resolve2(base, file);
|
|
372
|
+
const cssText = await readFile3(cssPath, "utf8").catch(() => {
|
|
373
|
+
return;
|
|
374
|
+
});
|
|
375
|
+
if (cssText === undefined)
|
|
376
|
+
throw new Error(`CSS file not found: ${cssPath}`);
|
|
377
|
+
const inlined = await inlineRelativeUrls(cssText, cssPath);
|
|
378
|
+
for (const ref of inlined.missing)
|
|
379
|
+
warnings.push({
|
|
380
|
+
source: "build",
|
|
381
|
+
message: `--css reference not found next to ${cssPath}: ${ref}`,
|
|
382
|
+
...component ? { component } : {}
|
|
383
|
+
});
|
|
384
|
+
return inlined.css;
|
|
385
|
+
};
|
|
386
|
+
const sharedCss = config.css === undefined ? undefined : await readCss(config.css);
|
|
387
|
+
const components = [];
|
|
388
|
+
for (const c of config.components) {
|
|
389
|
+
const isURL = URL.canParse(c.source) && ["http:", "https:"].includes(new URL(c.source).protocol);
|
|
390
|
+
const file = resolve2(base, c.source);
|
|
391
|
+
if (!isURL && !await access(file).then(() => true, () => false))
|
|
392
|
+
throw new Error(`component ${c.name}: HTML file not found: ${file}`);
|
|
393
|
+
const pick = (key) => c[key] ?? config[key];
|
|
394
|
+
components.push({
|
|
395
|
+
name: c.name,
|
|
396
|
+
source: isURL ? { url: c.source } : { file },
|
|
397
|
+
root: pick("root"),
|
|
398
|
+
viewport: pick("viewport"),
|
|
399
|
+
theme: pick("theme"),
|
|
400
|
+
darkClass: pick("darkClass"),
|
|
401
|
+
mode: pick("mode"),
|
|
402
|
+
css: c.css === undefined ? sharedCss : await readCss(c.css, c.name),
|
|
403
|
+
waitFor: pick("waitFor"),
|
|
404
|
+
breakpoints: pick("breakpoints") || undefined
|
|
405
|
+
});
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
out: resolve2(base, config.out),
|
|
409
|
+
options: {
|
|
410
|
+
library: config.library,
|
|
411
|
+
components,
|
|
412
|
+
serveRoot: resolve2(base, config.serve ?? "."),
|
|
413
|
+
timeoutMs: config.timeoutMs,
|
|
414
|
+
palette: config.palette === undefined ? undefined : await loadPalette(resolve2(base, config.palette))
|
|
415
|
+
},
|
|
416
|
+
warnings
|
|
417
|
+
};
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// src/cli/output.ts
|
|
421
|
+
import { lstat as lstat2, mkdir, rename, rm, rmdir, writeFile } from "node:fs/promises";
|
|
422
|
+
import { dirname as dirname3, join as join2, resolve as resolve3 } from "node:path";
|
|
423
|
+
async function ensureVacant(directory) {
|
|
424
|
+
try {
|
|
425
|
+
await lstat2(directory);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
if (error.code === "ENOENT")
|
|
428
|
+
return;
|
|
429
|
+
throw error;
|
|
430
|
+
}
|
|
431
|
+
throw new Error(`Output directory already exists: ${directory} (use --update to regenerate a Markswift output)`);
|
|
432
|
+
}
|
|
433
|
+
function checkPaths(entries) {
|
|
434
|
+
const folded = new Set;
|
|
435
|
+
for (const [name] of entries) {
|
|
436
|
+
if (unsafePath(name))
|
|
437
|
+
throw new Error(`Unsafe output path: ${name}`);
|
|
438
|
+
if (folded.has(name.toLowerCase()))
|
|
439
|
+
throw new Error(`Duplicate output path: ${name}`);
|
|
440
|
+
folded.add(name.toLowerCase());
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
async function updatable(directory, force = false) {
|
|
444
|
+
const output = resolve3(directory);
|
|
445
|
+
try {
|
|
446
|
+
await lstat2(output);
|
|
447
|
+
} catch (error) {
|
|
448
|
+
if (error.code === "ENOENT")
|
|
449
|
+
return;
|
|
450
|
+
throw error;
|
|
451
|
+
}
|
|
452
|
+
const manifest = await readManifest(output);
|
|
453
|
+
if (!manifest)
|
|
454
|
+
throw new Error(`${output} exists but has no ${MANIFEST}; --update only rewrites directories Markswift wrote. Convert into a new --out or move this directory away.`);
|
|
455
|
+
const edited = await editedFiles(output, manifest);
|
|
456
|
+
if (edited.length && !force)
|
|
457
|
+
throw new EditedFilesError(edited, `Refusing to overwrite hand-edited files in ${output}: ${edited.join(", ")}. Move your changes into your own files (Markswift never touches files it did not write) or rerun with --force to discard them.`);
|
|
458
|
+
return manifest;
|
|
459
|
+
}
|
|
460
|
+
async function writeBundle(result, directory, options) {
|
|
461
|
+
const entries = [...Object.entries(result.files), ...Object.entries(result.assets)];
|
|
462
|
+
checkPaths(entries);
|
|
463
|
+
const output = resolve3(directory);
|
|
464
|
+
await ensureVacant(output);
|
|
465
|
+
await mkdir(dirname3(output), { recursive: true });
|
|
466
|
+
try {
|
|
467
|
+
await mkdir(output);
|
|
468
|
+
} catch (error) {
|
|
469
|
+
if (error.code === "EEXIST")
|
|
470
|
+
throw new Error(`Output directory already exists: ${output}`);
|
|
471
|
+
throw error;
|
|
472
|
+
}
|
|
473
|
+
try {
|
|
474
|
+
for (const [name, contents] of entries) {
|
|
475
|
+
const file = join2(output, name);
|
|
476
|
+
await mkdir(dirname3(file), { recursive: true });
|
|
477
|
+
await writeFile(file, contents);
|
|
478
|
+
}
|
|
479
|
+
await writeFile(join2(output, MANIFEST), serializeManifest(manifestFor(Object.fromEntries(entries), options.version)));
|
|
480
|
+
} catch (error) {
|
|
481
|
+
await rm(output, { recursive: true, force: true });
|
|
482
|
+
throw error;
|
|
483
|
+
}
|
|
484
|
+
return entries.map(([name]) => name).sort();
|
|
485
|
+
}
|
|
486
|
+
async function updateBundle(result, directory, options) {
|
|
487
|
+
const entries = [...Object.entries(result.files), ...Object.entries(result.assets)];
|
|
488
|
+
checkPaths(entries);
|
|
489
|
+
const output = resolve3(directory);
|
|
490
|
+
const old = await updatable(output, options.force);
|
|
491
|
+
if (!old)
|
|
492
|
+
return writeBundle(result, output, options);
|
|
493
|
+
const fresh = new Set(entries.map(([name]) => name));
|
|
494
|
+
const stale = Object.keys(old.files).filter((name) => !fresh.has(name));
|
|
495
|
+
const unlisted = [];
|
|
496
|
+
for (const name of [...fresh].sort()) {
|
|
497
|
+
if (Object.hasOwn(old.files, name))
|
|
498
|
+
continue;
|
|
499
|
+
try {
|
|
500
|
+
await lstat2(join2(output, name));
|
|
501
|
+
unlisted.push(name);
|
|
502
|
+
} catch (error) {
|
|
503
|
+
if (error.code !== "ENOENT")
|
|
504
|
+
throw error;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
if (unlisted.length)
|
|
508
|
+
throw new EditedFilesError(unlisted, `Refusing to overwrite files Markswift did not write in ${output}: ${unlisted.join(", ")}. Rename or move them.`);
|
|
509
|
+
for (const [name, contents] of entries) {
|
|
510
|
+
const file = join2(output, name);
|
|
511
|
+
await mkdir(dirname3(file), { recursive: true });
|
|
512
|
+
await writeFile(file, contents);
|
|
513
|
+
}
|
|
514
|
+
for (const name of stale) {
|
|
515
|
+
await rm(join2(output, name), { force: true });
|
|
516
|
+
for (let parent = dirname3(name);parent !== "."; parent = dirname3(parent)) {
|
|
517
|
+
try {
|
|
518
|
+
await rmdir(join2(output, parent));
|
|
519
|
+
} catch (error) {
|
|
520
|
+
const code = error.code;
|
|
521
|
+
if (code === "ENOTEMPTY" || code === "ENOENT")
|
|
522
|
+
break;
|
|
523
|
+
throw error;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
const tmp = join2(output, `${MANIFEST}.tmp`);
|
|
528
|
+
await writeFile(tmp, serializeManifest(manifestFor(Object.fromEntries(entries), options.version)));
|
|
529
|
+
await rename(tmp, join2(output, MANIFEST));
|
|
530
|
+
return [...fresh].sort();
|
|
531
|
+
}
|
|
532
|
+
function formatWarning(w) {
|
|
533
|
+
const loss = w.level && w.feature ? `${w.level} ${w.feature}: ` : "";
|
|
534
|
+
return `${w.component ? `[${w.component}] ` : ""}${loss}${w.message}`;
|
|
535
|
+
}
|
|
536
|
+
function formatDiagnostics(report) {
|
|
537
|
+
const groups = new Map;
|
|
538
|
+
for (const node of report) {
|
|
539
|
+
for (const d of node.diagnostics) {
|
|
540
|
+
const key = `${d.level}\x00${d.feature}\x00${d.message}`;
|
|
541
|
+
const group = groups.get(key) ?? { d, ids: [] };
|
|
542
|
+
group.ids.push(node.id || node.kind);
|
|
543
|
+
groups.set(key, group);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
return [...groups.values()].sort((a, b) => Number(b.d.level === "unmapped") - Number(a.d.level === "unmapped") || a.d.feature.localeCompare(b.d.feature)).map(({ d, ids }) => `${d.level} ${d.feature}: ${d.message} [${ids.length} node${ids.length === 1 ? "" : "s"}: ${ids.slice(0, 5).join(", ")}${ids.length > 5 ? ", …" : ""}]`);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
// src/cli/run.ts
|
|
550
|
+
async function runBuild(command) {
|
|
551
|
+
const config = await loadBuildConfig(command.configPath);
|
|
552
|
+
await updatable(config.out, command.force);
|
|
553
|
+
const result = await buildLibrary2(config.options);
|
|
554
|
+
const files = await updateBundle(result, config.out, { version: VERSION, force: command.force });
|
|
555
|
+
process.stdout.write(`Wrote ${files.length} files for ${config.options.library} (${config.options.components.length} components) to ${config.out}
|
|
556
|
+
`);
|
|
557
|
+
for (const warning of [...config.warnings, ...result.warnings])
|
|
558
|
+
process.stderr.write(`Warning: ${formatWarning(warning)}
|
|
559
|
+
`);
|
|
560
|
+
for (const [component, report] of Object.entries(result.diagnostics))
|
|
561
|
+
for (const line of formatDiagnostics(report))
|
|
562
|
+
process.stderr.write(`Note [${component}] ${line}
|
|
563
|
+
`);
|
|
564
|
+
}
|
|
565
|
+
async function runConversion(command) {
|
|
566
|
+
if (command.update)
|
|
567
|
+
await updatable(command.outDir, command.force);
|
|
568
|
+
else
|
|
569
|
+
await ensureVacant(command.outDir);
|
|
570
|
+
if ("file" in command.source && !await access2(command.source.file).then(() => true, () => false)) {
|
|
571
|
+
throw new Error(`HTML file not found: ${command.source.file}`);
|
|
572
|
+
}
|
|
573
|
+
const source = "stdin" in command.source ? { html: await text(process.stdin) } : command.source;
|
|
574
|
+
let css;
|
|
575
|
+
if (command.cssPath) {
|
|
576
|
+
const cssText = await readFile4(command.cssPath, "utf8").catch(() => {
|
|
577
|
+
return;
|
|
578
|
+
});
|
|
579
|
+
if (cssText === undefined)
|
|
580
|
+
throw new Error(`CSS file not found: ${command.cssPath} (--css is optional)`);
|
|
581
|
+
const inlined = await inlineRelativeUrls(cssText, command.cssPath);
|
|
582
|
+
css = inlined.css;
|
|
583
|
+
for (const ref of inlined.missing)
|
|
584
|
+
process.stderr.write(`Warning: --css reference not found next to ${command.cssPath}: ${ref}
|
|
585
|
+
`);
|
|
586
|
+
}
|
|
587
|
+
const palette = command.palettePath === undefined ? undefined : await loadPalette(command.palettePath);
|
|
588
|
+
const result = await convert2(source, { ...command.options, css, palette });
|
|
589
|
+
const files = command.update ? await updateBundle(result, command.outDir, { version: VERSION, force: command.force }) : await writeBundle(result, command.outDir, { version: VERSION });
|
|
590
|
+
process.stdout.write(command.update ? `Updated ${files.length} files for ${result.name} in ${command.outDir}
|
|
591
|
+
` : `Wrote ${files.length} files for ${result.name} to ${command.outDir}
|
|
592
|
+
`);
|
|
593
|
+
for (const warning of result.warnings)
|
|
594
|
+
process.stderr.write(`Warning: ${formatWarning(warning)}
|
|
595
|
+
`);
|
|
596
|
+
for (const line of formatDiagnostics(result.diagnostics))
|
|
597
|
+
process.stderr.write(`Note ${line}
|
|
598
|
+
`);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
// src/cli.ts
|
|
602
|
+
async function main() {
|
|
603
|
+
try {
|
|
604
|
+
const command = parseArgs(process.argv.slice(2));
|
|
605
|
+
if (command.kind === "help") {
|
|
606
|
+
process.stdout.write(HELP);
|
|
607
|
+
return 0;
|
|
608
|
+
}
|
|
609
|
+
if (command.kind === "version") {
|
|
610
|
+
process.stdout.write(`${VERSION}
|
|
611
|
+
`);
|
|
612
|
+
return 0;
|
|
613
|
+
}
|
|
614
|
+
if (command.kind === "build")
|
|
615
|
+
await runBuild(command);
|
|
616
|
+
else
|
|
617
|
+
await runConversion(command);
|
|
618
|
+
return 0;
|
|
619
|
+
} catch (error) {
|
|
620
|
+
if (error instanceof UsageError) {
|
|
621
|
+
const help = process.argv[2] === "build" ? "build" : "convert";
|
|
622
|
+
process.stderr.write(`${error.message}
|
|
623
|
+
Run markswift ${help} --help for usage.
|
|
624
|
+
`);
|
|
625
|
+
return 2;
|
|
626
|
+
}
|
|
627
|
+
if (error instanceof EditedFilesError) {
|
|
628
|
+
process.stderr.write(`${error.message}
|
|
629
|
+
`);
|
|
630
|
+
return 2;
|
|
631
|
+
}
|
|
632
|
+
process.stderr.write(`${error instanceof Error ? error.message : String(error)}
|
|
633
|
+
`);
|
|
634
|
+
return 1;
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
process.exitCode = await main();
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BAND_MIN2,
|
|
3
|
+
BAND_CAP2,
|
|
4
|
+
DEFAULT_BAND_DEVICES2,
|
|
5
|
+
planBands2,
|
|
6
|
+
compileLayout2,
|
|
7
|
+
needsProbe2,
|
|
8
|
+
validateLibrary2,
|
|
9
|
+
compileLayouts2
|
|
10
|
+
} from "../cli-qchh0m4d.js";
|
|
11
|
+
export {
|
|
12
|
+
BAND_CAP2 as BAND_CAP,
|
|
13
|
+
BAND_MIN2 as BAND_MIN,
|
|
14
|
+
DEFAULT_BAND_DEVICES2 as DEFAULT_BAND_DEVICES,
|
|
15
|
+
compileLayout2 as compileLayout,
|
|
16
|
+
compileLayouts2 as compileLayouts,
|
|
17
|
+
needsProbe2 as needsProbe,
|
|
18
|
+
planBands2 as planBands,
|
|
19
|
+
validateLibrary2 as validateLibrary
|
|
20
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
convert2,
|
|
3
|
+
convertHtml2,
|
|
4
|
+
buildLibrary2
|
|
5
|
+
} from "./cli-jdrbzqfa.js";
|
|
6
|
+
import {
|
|
7
|
+
HTML_ELEMENTS2,
|
|
8
|
+
INPUT_CONTROLS2,
|
|
9
|
+
classifyElement2,
|
|
10
|
+
compileLayout2,
|
|
11
|
+
compileLayouts2
|
|
12
|
+
} from "./cli-qchh0m4d.js";
|
|
13
|
+
export {
|
|
14
|
+
HTML_ELEMENTS2 as HTML_ELEMENTS,
|
|
15
|
+
INPUT_CONTROLS2 as INPUT_CONTROLS,
|
|
16
|
+
buildLibrary2 as buildLibrary,
|
|
17
|
+
classifyElement2 as classifyElement,
|
|
18
|
+
compileLayout2 as compileLayout,
|
|
19
|
+
compileLayouts2 as compileLayouts,
|
|
20
|
+
convert2 as convert,
|
|
21
|
+
convertHtml2 as convertHtml
|
|
22
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** CSS flex alignment and distribution mapped to semantic axes, in visual terms (start = left/top). */
|
|
2
|
+
import type { LayoutNode } from "../model/layout.ts";
|
|
3
|
+
import type { CrossAlign, Distribution } from "../model/semantic.ts";
|
|
4
|
+
export declare function crossAlign(n: LayoutNode): CrossAlign;
|
|
5
|
+
/** A flex item's own `align-self`, resolved like `align-items`; undefined for `auto` (follow the container). */
|
|
6
|
+
export declare function alignSelfOf(child: LayoutNode, container: LayoutNode): CrossAlign | undefined;
|
|
7
|
+
/**
|
|
8
|
+
* Main-axis packing in *visual* terms (children are ordered by measured position: start =
|
|
9
|
+
* left/top). flex-start/flex-end follow the flex direction, start/end the writing direction,
|
|
10
|
+
* left/right are physical (start in a column) — all verified against Chrome.
|
|
11
|
+
*/
|
|
12
|
+
export declare function distributionOf(n: LayoutNode): Distribution;
|