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.
Files changed (146) hide show
  1. package/CHANGELOG.md +234 -0
  2. package/LICENSE +21 -0
  3. package/README.md +592 -2
  4. package/dist/cli-jdrbzqfa.js +2975 -0
  5. package/dist/cli-qchh0m4d.js +14041 -0
  6. package/dist/cli.js +637 -0
  7. package/dist/compiler/index.js +20 -0
  8. package/dist/index.js +22 -0
  9. package/dist/types/analysis/alignment.d.ts +12 -0
  10. package/dist/types/analysis/bands.d.ts +53 -0
  11. package/dist/types/analysis/breakpoints.d.ts +22 -0
  12. package/dist/types/analysis/class-hints.d.ts +21 -0
  13. package/dist/types/analysis/context.d.ts +49 -0
  14. package/dist/types/analysis/controls.d.ts +97 -0
  15. package/dist/types/analysis/flex-row.d.ts +14 -0
  16. package/dist/types/analysis/flex-sizing.d.ts +5 -0
  17. package/dist/types/analysis/flow-layout.d.ts +18 -0
  18. package/dist/types/analysis/generated.d.ts +16 -0
  19. package/dist/types/analysis/geometry.d.ts +50 -0
  20. package/dist/types/analysis/grid-tracks.d.ts +44 -0
  21. package/dist/types/analysis/grid.d.ts +5 -0
  22. package/dist/types/analysis/infer.d.ts +18 -0
  23. package/dist/types/analysis/invisible.d.ts +6 -0
  24. package/dist/types/analysis/layers.d.ts +15 -0
  25. package/dist/types/analysis/measured-layout.d.ts +6 -0
  26. package/dist/types/analysis/motion.d.ts +29 -0
  27. package/dist/types/analysis/naming.d.ts +6 -0
  28. package/dist/types/analysis/node-base.d.ts +13 -0
  29. package/dist/types/analysis/size-constraints.d.ts +5 -0
  30. package/dist/types/analysis/sizing.d.ts +24 -0
  31. package/dist/types/analysis/style-background.d.ts +24 -0
  32. package/dist/types/analysis/style-border.d.ts +7 -0
  33. package/dist/types/analysis/style-effects.d.ts +4 -0
  34. package/dist/types/analysis/style.d.ts +9 -0
  35. package/dist/types/analysis/svg/attributes.d.ts +10 -0
  36. package/dist/types/analysis/svg/build.d.ts +6 -0
  37. package/dist/types/analysis/svg/context.d.ts +31 -0
  38. package/dist/types/analysis/svg/geometry.d.ts +11 -0
  39. package/dist/types/analysis/svg/gradients.d.ts +6 -0
  40. package/dist/types/analysis/svg/markup.d.ts +23 -0
  41. package/dist/types/analysis/svg/text.d.ts +7 -0
  42. package/dist/types/analysis/svg/transforms.d.ts +19 -0
  43. package/dist/types/analysis/svg/traversal.d.ts +4 -0
  44. package/dist/types/analysis/table-borders.d.ts +19 -0
  45. package/dist/types/analysis/table.d.ts +36 -0
  46. package/dist/types/analysis/text-layout.d.ts +65 -0
  47. package/dist/types/analysis/text-runs.d.ts +30 -0
  48. package/dist/types/analysis/text.d.ts +9 -0
  49. package/dist/types/analysis/tooltips.d.ts +16 -0
  50. package/dist/types/analysis/typography.d.ts +21 -0
  51. package/dist/types/analysis/wrapping.d.ts +5 -0
  52. package/dist/types/api/bands.d.ts +58 -0
  53. package/dist/types/api/build.d.ts +51 -0
  54. package/dist/types/api/convert.d.ts +13 -0
  55. package/dist/types/api/types.d.ts +69 -0
  56. package/dist/types/browser/capture.d.ts +89 -0
  57. package/dist/types/browser/conditions.d.ts +25 -0
  58. package/dist/types/browser/fonts.d.ts +34 -0
  59. package/dist/types/browser/images.d.ts +3 -0
  60. package/dist/types/browser/layers.d.ts +132 -0
  61. package/dist/types/browser/motion.d.ts +44 -0
  62. package/dist/types/browser/settle.d.ts +27 -0
  63. package/dist/types/browser/sizing.d.ts +10 -0
  64. package/dist/types/browser/snapshot.d.ts +155 -0
  65. package/dist/types/browser/states.d.ts +36 -0
  66. package/dist/types/cli/args.d.ts +31 -0
  67. package/dist/types/cli/config.d.ts +78 -0
  68. package/dist/types/cli/css.d.ts +9 -0
  69. package/dist/types/cli/manifest.d.ts +20 -0
  70. package/dist/types/cli/output.d.ts +27 -0
  71. package/dist/types/cli/run.d.ts +4 -0
  72. package/dist/types/cli.d.ts +2 -0
  73. package/dist/types/compiler/index.d.ts +91 -0
  74. package/dist/types/compiler/naming.d.ts +1 -0
  75. package/dist/types/css/background.d.ts +16 -0
  76. package/dist/types/css/blend.d.ts +9 -0
  77. package/dist/types/css/color.d.ts +14 -0
  78. package/dist/types/css/counter.d.ts +10 -0
  79. package/dist/types/css/easing.d.ts +4 -0
  80. package/dist/types/css/effects.d.ts +18 -0
  81. package/dist/types/css/grid.d.ts +29 -0
  82. package/dist/types/css/index.d.ts +15 -0
  83. package/dist/types/css/media.d.ts +16 -0
  84. package/dist/types/css/paint.d.ts +33 -0
  85. package/dist/types/css/radius.d.ts +5 -0
  86. package/dist/types/css/raster.d.ts +10 -0
  87. package/dist/types/css/shadows.d.ts +3 -0
  88. package/dist/types/css/syntax.d.ts +12 -0
  89. package/dist/types/css/transforms.d.ts +45 -0
  90. package/dist/types/css/typography.d.ts +8 -0
  91. package/dist/types/fonts/converter.d.ts +16 -0
  92. package/dist/types/fonts/metadata.d.ts +6 -0
  93. package/dist/types/fonts/package.d.ts +12 -0
  94. package/dist/types/fonts/registration.d.ts +13 -0
  95. package/dist/types/fonts/source.d.ts +14 -0
  96. package/dist/types/fonts/types.d.ts +28 -0
  97. package/dist/types/html/elements.d.ts +8 -0
  98. package/dist/types/html/index.d.ts +4 -0
  99. package/dist/types/html/inputs.d.ts +4 -0
  100. package/dist/types/html/policy.d.ts +3 -0
  101. package/dist/types/html/types.d.ts +12 -0
  102. package/dist/types/index.d.ts +8 -0
  103. package/dist/types/input/identity.d.ts +3 -0
  104. package/dist/types/input/mime.d.ts +1 -0
  105. package/dist/types/input/root.d.ts +1 -0
  106. package/dist/types/input/server.d.ts +11 -0
  107. package/dist/types/input/source.d.ts +9 -0
  108. package/dist/types/input/types.d.ts +54 -0
  109. package/dist/types/model/fonts.d.ts +26 -0
  110. package/dist/types/model/interactive.d.ts +10 -0
  111. package/dist/types/model/layout.d.ts +528 -0
  112. package/dist/types/model/semantic.d.ts +984 -0
  113. package/dist/types/model/values.d.ts +125 -0
  114. package/dist/types/model/version.d.ts +4 -0
  115. package/dist/types/swift/borders.d.ts +13 -0
  116. package/dist/types/swift/components.d.ts +115 -0
  117. package/dist/types/swift/context.d.ts +184 -0
  118. package/dist/types/swift/control-state.d.ts +19 -0
  119. package/dist/types/swift/controls.d.ts +62 -0
  120. package/dist/types/swift/diagnostics.d.ts +3 -0
  121. package/dist/types/swift/effects.d.ts +23 -0
  122. package/dist/types/swift/emit.d.ts +6 -0
  123. package/dist/types/swift/format.d.ts +91 -0
  124. package/dist/types/swift/frames.d.ts +17 -0
  125. package/dist/types/swift/images.d.ts +25 -0
  126. package/dist/types/swift/layout.d.ts +5 -0
  127. package/dist/types/swift/measured-layout.d.ts +4 -0
  128. package/dist/types/swift/modifiers.d.ts +30 -0
  129. package/dist/types/swift/motion.d.ts +20 -0
  130. package/dist/types/swift/nodes.d.ts +4 -0
  131. package/dist/types/swift/paint.d.ts +22 -0
  132. package/dist/types/swift/runtime.d.ts +6 -0
  133. package/dist/types/swift/shadows.d.ts +7 -0
  134. package/dist/types/swift/states.d.ts +3 -0
  135. package/dist/types/swift/svg.d.ts +14 -0
  136. package/dist/types/swift/text-box.d.ts +12 -0
  137. package/dist/types/swift/text.d.ts +3 -0
  138. package/dist/types/swift/tokens/emit.d.ts +26 -0
  139. package/dist/types/swift/tokens/extract.d.ts +21 -0
  140. package/dist/types/swift/tokens/naming.d.ts +22 -0
  141. package/dist/types/swift/tokens/slots.d.ts +18 -0
  142. package/dist/types/swift/tokens/types.d.ts +77 -0
  143. package/dist/types/swift/tokens.d.ts +6 -0
  144. package/dist/types/swift/traversal.d.ts +2 -0
  145. package/dist/types/swift/wrapping.d.ts +4 -0
  146. package/package.json +66 -4
@@ -0,0 +1,125 @@
1
+ /** IR value types: what the parsers in css/ produce and the emitters consume. No parsing here. */
2
+ import type { SvgNode } from "./semantic.ts";
3
+ /** sRGB color, channels 0..1 (wide-gamut inputs are gamut-clipped). */
4
+ export interface Color {
5
+ r: number;
6
+ g: number;
7
+ b: number;
8
+ a: number;
9
+ }
10
+ /**
11
+ * Every CSS `<blend-mode>` keyword (`mix-blend-mode`, `background-blend-mode`). Each one is a
12
+ * separable or non-separable blend function the CSS compositing spec takes verbatim from the
13
+ * PDF blend model, which is also what CoreGraphics — and therefore SwiftUI's `BlendMode` —
14
+ * implements, so the set maps one to one onto the platform.
15
+ */
16
+ export declare const BLEND_MODES: {
17
+ readonly normal: true;
18
+ readonly multiply: true;
19
+ readonly screen: true;
20
+ readonly overlay: true;
21
+ readonly darken: true;
22
+ readonly lighten: true;
23
+ readonly "color-dodge": true;
24
+ readonly "color-burn": true;
25
+ readonly "hard-light": true;
26
+ readonly "soft-light": true;
27
+ readonly difference: true;
28
+ readonly exclusion: true;
29
+ readonly hue: true;
30
+ readonly saturation: true;
31
+ readonly color: true;
32
+ readonly luminosity: true;
33
+ readonly "plus-lighter": true;
34
+ readonly "plus-darker": true;
35
+ };
36
+ export type BlendMode = keyof typeof BLEND_MODES;
37
+ export interface GradientStop {
38
+ color: Color;
39
+ /** 0..1 along the gradient line, undefined = evenly distributed. */
40
+ pos?: number;
41
+ /** Absolute px along the gradient line; `resolvePaint` turns it into `pos` once the box is known. */
42
+ px?: number;
43
+ }
44
+ /** Px from the node's border-box edge to a fill's painting area (top, right, bottom, left). */
45
+ export type PaintInset = {
46
+ inset?: [number, number, number, number];
47
+ /** `background-blend-mode` for this layer, against the layers below it. Absent = `normal`. */
48
+ blend?: BlendMode;
49
+ };
50
+ /**
51
+ * One `background-image` layer (or a solid `background-color`). Gradient variants carry
52
+ * `repeating: true` for `repeating-*-gradient`; `center` is a 0..1 fraction of the box;
53
+ * `space` is the colour space of an explicit `in <space>` clause. A linear `corner`
54
+ * (`to right top` = [1, -1]) keeps the square-box `angle` until `resolvePaint` knows the box.
55
+ * Image layers carry the resolved `background-size`/`position`/`repeat` (px, box-relative)
56
+ * when the producer knows the box; absent = fill the box. `inset` (any fill) is set for
57
+ * `background-clip: padding-box | content-box`.
58
+ */
59
+ export type Paint = ({
60
+ kind: "solid";
61
+ color: Color;
62
+ } & PaintInset) | ({
63
+ kind: "linear";
64
+ angle: number;
65
+ corner?: [x: 1 | -1, y: 1 | -1];
66
+ stops: GradientStop[];
67
+ repeating?: true;
68
+ space?: string;
69
+ } & PaintInset) | ({
70
+ kind: "radial";
71
+ stops: GradientStop[];
72
+ center: [number, number];
73
+ ellipse: boolean;
74
+ repeating?: true;
75
+ space?: string;
76
+ } & PaintInset) | ({
77
+ kind: "conic";
78
+ stops: GradientStop[];
79
+ from: number;
80
+ center: [number, number];
81
+ repeating?: true;
82
+ space?: string;
83
+ } & PaintInset) | ({
84
+ kind: "image";
85
+ url: string;
86
+ size?: BackgroundSize;
87
+ position?: [number, number];
88
+ repeat?: BackgroundRepeat;
89
+ /** A `data:image/svg+xml` source, parsed during analysis. */
90
+ vector?: SvgNode;
91
+ } & PaintInset) | {
92
+ kind: "unsupported";
93
+ raw: string;
94
+ };
95
+ /** `background-repeat` per layer; `space`/`round` collapse to `repeat`. */
96
+ export type BackgroundRepeat = "no-repeat" | "repeat" | "repeat-x" | "repeat-y";
97
+ /** Resolved `background-size` layer: explicit px box or the `cover`/`contain` keywords. */
98
+ export type BackgroundSize = {
99
+ w: number;
100
+ h: number;
101
+ } | "cover" | "contain";
102
+ export interface Shadow {
103
+ color: Color;
104
+ x: number;
105
+ y: number;
106
+ blur: number;
107
+ spread: number;
108
+ inset: boolean;
109
+ }
110
+ /**
111
+ * 2D affine transform decomposed as `translate(tx, ty) · rotate(rotate) · skewX(skewX) ·
112
+ * skewY(skewY) · scale(scaleX, scaleY)`. Angles in degrees; `skewX`/`skewY` are present
113
+ * only when the matrix has shear (a pure Y shear reports `skewY`, any other shear `skewX`).
114
+ * `flattened` marks a 3D matrix reduced to its x/y projection (depth and perspective lost).
115
+ */
116
+ export interface Transform2D {
117
+ rotate: number;
118
+ scaleX: number;
119
+ scaleY: number;
120
+ tx: number;
121
+ ty: number;
122
+ skewX?: number;
123
+ skewY?: number;
124
+ flattened?: true;
125
+ }
@@ -0,0 +1,4 @@
1
+ /** Markswift package version: `--version`, generated-file headers and output manifests. */
2
+ export declare const VERSION: string;
3
+ /** First words of every generated file's header comment. */
4
+ export declare const GENERATED_BY: string;
@@ -0,0 +1,13 @@
1
+ import type { SemNode } from "../model/semantic.ts";
2
+ import type { ColorCtx } from "./context.ts";
3
+ /**
4
+ * Per-edge borders. An edge whose two corners are both square is a bar aligned to that edge —
5
+ * the cheapest thing that is also exact. An edge that runs into a rounded corner is not: CSS
6
+ * curves the border itself around the corner and hands it over to the neighbouring side at the
7
+ * line from the border-box corner to the padding-box corner, so an edge whose neighbour has no
8
+ * border keeps the whole arc. Clipping a straight bar to the box shape instead *deletes* that
9
+ * arc — and with it the only thing separating two `input-otp` slots. Those edges are emitted as
10
+ * `MarkswiftBorderSides`, which builds exactly that region; one expression per colour/style/width
11
+ * group, so at most four per node however large the design gets.
12
+ */
13
+ export declare function borderSideModifiers(n: SemNode, ctx: ColorCtx): string[];
@@ -0,0 +1,115 @@
1
+ /**
2
+ * Repeated siblings → one nested subview plus a static data array iterated with `ForEach` (app
3
+ * mode). The emitter is the equality test: every node is emitted once with a placeholder line per
4
+ * child it asks `ctx.renderNode` for, and siblings whose emitted Swift differs only in design
5
+ * literals (strings, colours, shapes, control state/action names) form a run. The subview is the
6
+ * first member's own emitted code with the differing literals read from its row, so the pixels are
7
+ * the unrolled view's by construction. Names come from the markup (`componentNames`).
8
+ */
9
+ import type { GridNode, Screen, SemNode, StackNode, TextRun } from "../model/semantic.ts";
10
+ import type { ControlMembers, EmitContext, EmitOptions } from "./context.ts";
11
+ /**
12
+ * What a slot column becomes. `field`: a `Row` field; `literal`: kept as emitted; `binding`/`pass`:
13
+ * the screen's state/action handed through; `collapse`: per-row actions as one `(Row) -> Void`;
14
+ * `move`: per-row pressed/focused state declared in the subview; `counter`: a list marker derived
15
+ * from the row's `ordinal`.
16
+ */
17
+ type Role = "field" | "literal" | "binding" | "pass" | "collapse" | "move" | "counter";
18
+ type Counter = Omit<NonNullable<TextRun["counter"]>, "value">;
19
+ /** A counter-marker column of a row type: the shared counter, and each run's first value and step. */
20
+ interface CounterColumn {
21
+ counter: Counter;
22
+ ords: Map<Run, {
23
+ first: number;
24
+ step: number;
25
+ }>;
26
+ }
27
+ interface Run {
28
+ /** The stack or grid holding the members. */
29
+ parent: StackNode | GridNode;
30
+ /** The run's members, in order. */
31
+ members: SemNode[];
32
+ /**
33
+ * Per slot column (DFS order over a member): marker, each member's value and slot node, and the
34
+ * first member's text before the slot on its line (a colour's role).
35
+ */
36
+ columns: Array<{
37
+ marker: string;
38
+ values: string[];
39
+ nodes: SemNode[];
40
+ at: string;
41
+ }>;
42
+ /**
43
+ * First column whose value vector equals this one's (`$` stripped): columns sharing one field or
44
+ * parameter. Design-literal columns are classed and given their role over every run of the type.
45
+ */
46
+ cls: number[];
47
+ roles: Role[];
48
+ forEach: boolean;
49
+ /** Runs with equal signatures share one row type and subview. */
50
+ sig: string;
51
+ }
52
+ export interface Extraction {
53
+ /** Lines for a run member or an annotated text, undefined for any other node. */
54
+ call(node: SemNode): string[] | undefined;
55
+ /** Every node inside a run member (all members). */
56
+ absorbed: Set<SemNode>;
57
+ /** The screen's members replacing `ctx.control.members`, in declaration (= `init` parameter) order. */
58
+ members: ScreenMember[];
59
+ /** Screen `@State` renamed to its annotated field: old → new, applied to the screen body. */
60
+ rename: Record<string, string>;
61
+ /** `extension <View> { … }` body lines; empty without runs. */
62
+ extension: string[];
63
+ /** One `#Preview` per extracted subview. */
64
+ previews: string[];
65
+ }
66
+ /** Names of one row type: `<base>`, `<base>View`, its closure variable, fields and pass-through bindings by column. */
67
+ interface TypeNames {
68
+ type: string;
69
+ view: string;
70
+ row: string;
71
+ fields: Map<number, string>;
72
+ pass: Map<number, string>;
73
+ /** The subview's row-index parameter of a counter marker (`ordinal`). */
74
+ ordinal?: string;
75
+ }
76
+ /**
77
+ * Every name extraction mints, from the markup (`nameHint`, `field`) and nearby headings; never from
78
+ * paths. Types/arrays share one record with the screen's own members; fields are per type.
79
+ */
80
+ export declare function componentNames(screen: Screen, viewName: string, ctx: EmitContext, types: Array<{
81
+ run: Run;
82
+ group: Run[];
83
+ fieldCols: number[];
84
+ counter?: CounterColumn;
85
+ }>, runType: number[], screenFields: SemNode[]): {
86
+ types: TypeNames[];
87
+ data: string[];
88
+ fields: Map<SemNode, string>;
89
+ actions: Map<Run, Map<number, string>>;
90
+ };
91
+ /** An `init` parameter of the screen: a stored property with its design default (`state`: seeds a private `@State`). */
92
+ export interface Param {
93
+ name: string;
94
+ type: string;
95
+ value: string;
96
+ state?: true;
97
+ }
98
+ /** A screen member: leading lines (docs, private decls), then the stored property behind `param`, if any. */
99
+ export interface ScreenMember {
100
+ decl: string[];
101
+ param?: Param;
102
+ }
103
+ /** `public ` for a generated library's API, nothing for app output. */
104
+ export declare const access: (v: EmitOptions["visibility"]) => "" | "public ";
105
+ /** A control member as a screen member: an action callback is an `init` parameter, an open state an `init`-seeded `@State`. */
106
+ export declare function asMember(m: ControlMembers["members"][number]): ScreenMember;
107
+ /**
108
+ * Repeated sibling elements of an app-mode screen as nested row types, data arrays and subviews,
109
+ * plus the screen's `data-markswift-field` texts and controls as `init` parameters; undefined when
110
+ * the screen has neither. Strict equality: optional children, variants and `:last-child` edges
111
+ * break a run (post-1.0 "loose extraction"); grids and measured, wrapping or distributed stacks get
112
+ * the shared subview called once per member (`Self.<array>[i]`, not a parameter) instead of a `ForEach`.
113
+ */
114
+ export declare function extractComponents(screen: Screen, ctx: EmitContext, viewName: string, opts: Pick<EmitOptions, "visibility" | "registerFonts">): Extraction | undefined;
115
+ export {};
@@ -0,0 +1,184 @@
1
+ import type { Color } from "../model/values.ts";
2
+ import type { Confidence, Diagnostic, Screen, SemNode, StateField, StateTransition, Style } from "../model/semantic.ts";
3
+ import type { FontMetrics } from "../model/fonts.ts";
4
+ export interface SwiftOutput {
5
+ /** <Name>View.generated.swift */
6
+ view: string;
7
+ /** Shapes referenced by this screen: name → Swift struct source. */
8
+ shapes: Record<string, string>;
9
+ /** Raster files referenced by this screen: bundle path (`Images/<hash>.<ext>`) → bytes. */
10
+ images: Record<string, Uint8Array>;
11
+ /** Nodes whose confidence is not `mapped`, with what each lost. */
12
+ report: Array<{
13
+ id: string;
14
+ kind: string;
15
+ confidence: Confidence;
16
+ diagnostics: Diagnostic[];
17
+ }>;
18
+ /** App mode: layer id (in-flow or presentation) → its `init` label; absent when nothing opens. */
19
+ openStates?: Record<string, string>;
20
+ }
21
+ /**
22
+ * `fidelity` reproduces the measured browser layout for pixel verification: clipped fixed root
23
+ * frame, text pinned to its measured line box, geometry-derived cross alignment. `app` is what
24
+ * ships to Xcode: ScrollView, native text metrics, alignment from the IR.
25
+ */
26
+ export type EmitMode = "app" | "fidelity";
27
+ export interface EmitOptions {
28
+ /** Packaged metrics for this conversion, keyed by lowercase CSS family. */
29
+ fontMetrics?: FontMetrics;
30
+ mode: EmitMode;
31
+ /** Design tokens of the run, bound to this screen; absent = every colour stays a literal. */
32
+ tokens?: ScreenTokens;
33
+ /** The light screen of a light/dark pair: the view must look right in both colour schemes. */
34
+ adaptive?: boolean;
35
+ /** Repeated-sibling extraction (app mode). `false` only for the pixel-identity check; default on. */
36
+ extract?: boolean;
37
+ /** `public` for a generated library (plan 033's `build`); `convert` stays internal. Default `internal`. */
38
+ visibility?: "internal" | "public";
39
+ /** Previews call `BundledFonts.register()` first: the library package bundles fonts (plan 033's `build`). */
40
+ registerFonts?: boolean;
41
+ /** Command the view header names for regeneration. Default `convert`. */
42
+ regenerate?: "convert" | "build";
43
+ }
44
+ /** Everything the recursive emitters need; built once per screen. */
45
+ export interface EmitContext extends ColorCtx {
46
+ /** Recursive child lines; supplied by the screen emitter. */
47
+ renderNode: (node: SemNode) => string[];
48
+ fontMetrics: FontMetrics;
49
+ /** Container node → Swift property name. */
50
+ nameOf: Map<SemNode, string>;
51
+ /**
52
+ * Items of native rows, grids and content-sized containers: CSS sizes them no narrower than
53
+ * their content (`min-width: auto` of flex/grid items, shrink-to-fit), so their fill frames
54
+ * may grow to it. Every other fill-width box takes its container's width; see `frameModifier`.
55
+ */
56
+ contentMinWidth: Set<SemNode>;
57
+ /**
58
+ * App mode: children whose size a runtime Layout allocates (MarkswiftTracks cells, MarkswiftFlexRow items).
59
+ * "width": accept the proposed width; "both": also the proposed height (grid stretch); "height": a justified grid
60
+ * cell (MarkswiftTracks) takes the proposed height and fits its content's width.
61
+ */
62
+ allocated: Map<SemNode, "width" | "height" | "both">;
63
+ /** Breakpoint screens: containers whose subtree holds a band holder, emitted as `func v_x(_ band: Int)`. */
64
+ bandParam: Set<SemNode>;
65
+ /** Breakpoint screens: the app-mode scrolling root that hosts `MarkswiftBands` inside its `ScrollView`. */
66
+ bandsInside?: SemNode;
67
+ /** Shape structs referenced so far, name → source. */
68
+ shapes: Record<string, string>;
69
+ /** Raster files referenced so far, path → bytes. */
70
+ images: Record<string, Uint8Array>;
71
+ screen: Screen;
72
+ mode: EmitMode;
73
+ /** `@State` names and action callbacks of this screen's controls. */
74
+ control: ControlMembers;
75
+ /** This screen's colour → token lookup; see `colorExpr`. */
76
+ tokens?: ScreenTokens;
77
+ adaptive?: boolean;
78
+ }
79
+ /**
80
+ * Resolves a colour one node paints to a design-token name. Implemented by `tokens/extract.ts`
81
+ * (`screenTokens`), which binds the screen and theme being emitted; declared here so the
82
+ * emitters depend on the lookup and not on the extractor.
83
+ */
84
+ export interface ScreenTokens {
85
+ /** Token for the colour `id` paints (at `slot`, when the call site knows it), or undefined when the colour must stay a literal. */
86
+ name(c: Color, id: string | undefined, slot?: string): string | undefined;
87
+ /** True when the colour `id` paints is opaque in every theme this view renders (a semantic token checks its dark value too). */
88
+ opaque(c: Color, id: string | undefined): boolean;
89
+ }
90
+ /** How one node paints in an interaction state, on top of its resting look (`states.ts` `pairStyle`). */
91
+ export interface StatePaint {
92
+ /** The owner's member name (`isSavePressed`, `isEmailFocused`): the ternaries' condition. */
93
+ cond: string;
94
+ /** The style the box is emitted with: rest geometry plus anything only the state paints. */
95
+ style: Style;
96
+ /** Keyed by `colorArgs(c)` of the colour the emitter will be asked for, or by a run slot `run<i>`. */
97
+ colors: Map<string, {
98
+ rest: Color;
99
+ state: Color;
100
+ }>;
101
+ opacity?: {
102
+ rest: number;
103
+ state: number;
104
+ };
105
+ scale?: {
106
+ rest: [number, number];
107
+ state: [number, number];
108
+ };
109
+ /** The fields the look changes. */
110
+ fields: StateField[];
111
+ /** Pressed/focused: the look's CSS transitions (absent field = jump). */
112
+ transitions?: StateTransition;
113
+ }
114
+ /**
115
+ * The part of an emitter's context a colour or a font needs. `EmitContext` and `ControlDeps` both
116
+ * satisfy it.
117
+ */
118
+ export interface ColorCtx {
119
+ /** Absent when the run emits no token file, or for a node whose colours are all literals. */
120
+ tokens?: ScreenTokens;
121
+ /** Which output is being written; `fontExpr` emits a different font expression per mode. */
122
+ mode: EmitMode;
123
+ /** App mode: `@ScaledMetric` font sizes used so far, property name → declaration; emitted once per view. */
124
+ scaledFonts?: Map<string, string>;
125
+ /** Node id → its interaction-state paint; `colorExpr` turns a paired colour into a ternary. */
126
+ statePaint?: Map<string, StatePaint>;
127
+ }
128
+ /** The view-level members a screen's controls need, in emission order. */
129
+ export interface ControlMembers {
130
+ /** `@State` / callback declarations to put before `var body`. Empty when the screen has none. */
131
+ decls: string[];
132
+ /**
133
+ * Every member in emission order; `value` = design state (`@State private`), `action` =
134
+ * `on<Name>` callback, `ephemeral` = UI state such as pressed/focused (plan 029), `open` = a
135
+ * layer's shown state (in-flow or drawn presentation), an `init`-seeded `@State` (`seed`). `decls` is
136
+ * `members.flatMap(m => m.decl)`.
137
+ */
138
+ members: Array<{
139
+ name: string;
140
+ kind: "value" | "action" | "ephemeral" | "open";
141
+ type: string;
142
+ decl: string[];
143
+ seed?: string;
144
+ }>;
145
+ stateOf: Map<SemNode, string>;
146
+ /** `Action.key` → callback member. */
147
+ actionOf: Map<string, string>;
148
+ /** Toggle/radio id (native or ARIA toggle), or app-mode text field id → the Swift statement a tap on its `<label>` runs. */
149
+ activate: Map<string, string>;
150
+ /** Grouped radio → its tag in the group's `String` selection (its `stateOf` name). */
151
+ radioTag: Map<SemNode, string>;
152
+ /**
153
+ * ARIA toggle drawn as a Button (`role=checkbox|switch|radio…`) → the statements its tap runs
154
+ * (change the state, then the action), the condition `.isSelected` follows, and the one
155
+ * `aria-checked="mixed"` follows (`Bool?` state, `nil` = mixed).
156
+ */
157
+ toggleOf: Map<SemNode, {
158
+ tap: string[];
159
+ on: string;
160
+ mixed?: string;
161
+ }>;
162
+ /** A trigger Button of a layer → its shown state and what its tap runs (in-flow: toggle; presentation: set true; then
163
+ * the action). Long-press presentation triggers have none. */
164
+ openOf: Map<SemNode, {
165
+ state: string;
166
+ tap: string[];
167
+ }>;
168
+ /** Layer id → its shown state (in-flow: the `if` around content with that `shownBy`; presentation: the binding). */
169
+ openStates: Map<string, string>;
170
+ /** A presentation's own closing button → what its tap runs (its action, then the state goes false). */
171
+ closeOf: Map<SemNode, string[]>;
172
+ /** Presentation layer id → its `on<Layer>Dismissed` callback (Escape or an outside tap closed it). */
173
+ dismissedOf: Map<string, string>;
174
+ /** Native menu toggle/radio item id → its `@State` member (plan 061). */
175
+ menuState: Map<string, string>;
176
+ /** Single-run TextNode id → the member its text shows (a Radix select's value follows its Picker). */
177
+ textState: Map<string, string>;
178
+ /** Node id → its interaction-state paint (pressed under the nearest Button, focused on a text field). */
179
+ statePaint: Map<string, StatePaint>;
180
+ /** Pressable owner → its `@MarkswiftPressed` member. */
181
+ pressOf: Map<SemNode, string>;
182
+ /** Text field → its `@MarkswiftFocused` member. */
183
+ focusOf: Map<SemNode, string>;
184
+ }
@@ -0,0 +1,19 @@
1
+ import type { ControlNode, MenuItem, SemNode } from "../model/semantic.ts";
2
+ import type { ControlMembers, EmitMode } from "./context.ts";
3
+ /**
4
+ * Every control's state property and every distinct action callback for one screen. Names come
5
+ * from the node ids, so regenerating an unchanged design produces the same members. `byId`
6
+ * (breakpoint screens): one element is a node per band alternative; its first node owns the
7
+ * value, toggle, pressed and focused members, every other node of that element reuses them.
8
+ */
9
+ export declare function controlMembers(root: SemNode, mode: EmitMode, byId?: boolean): ControlMembers;
10
+ /** A menu's items with its sections opened, in order. */
11
+ export declare const flatItems: (items: MenuItem[]) => MenuItem[];
12
+ /** The control's value as a number, clamped into its range (the range's start when it has none). */
13
+ export declare function numericValue(n: ControlNode): number;
14
+ /**
15
+ * An `<input type=date|time|datetime-local|month|week>` value as the wall-clock date the design
16
+ * wrote (`Date.markswift`, runtime/controls.swift). HTML sanitises an invalid value to empty, and
17
+ * an empty field has no date to show, so both start at `.now`.
18
+ */
19
+ export declare function dateSeed(value: string | undefined): string;
@@ -0,0 +1,62 @@
1
+ /**
2
+ * `ControlNode` → SwiftUI. The node's box (fill, border, radius, padding, frame) is painted by
3
+ * the shared modifier chain in `modifiers.ts`; this file emits only the interactive core that goes
4
+ * inside it, plus the view members that core needs: one `@State` per stateful control and one
5
+ * callback property per distinct `onclick` handler.
6
+ *
7
+ * Mode difference — live controls. `app` emits the real thing (`TextField`, `Slider`, `Picker`,
8
+ * …) in the CSS look: text fields are `.plain` (the CSS box is their chrome), `<select>` is a
9
+ * `Menu` labelled by its selection over an inline `Picker`. `ImageRenderer`, which the verifier
10
+ * draws with, cannot rasterise UIKit-backed controls and paints a yellow placeholder instead, so
11
+ * `fidelity` emits what the browser painted: the field's text, the slider's track, the picker's
12
+ * selection. Everything that is pure SwiftUI (`Button`, `Toggle` with a drawn style,
13
+ * `ProgressView`/`Gauge` with a drawn style, `DisclosureGroup` with `MarkswiftDisclosureStyle`)
14
+ * is identical in both modes.
15
+ */
16
+ import type { Theme } from "../model/layout.ts";
17
+ import type { FontMetrics } from "../model/fonts.ts";
18
+ import type { ControlNode, MenuItem, MenuSpec, SemNode } from "../model/semantic.ts";
19
+ import type { ColorCtx, ControlMembers, EmitMode, ScreenTokens } from "./context.ts";
20
+ /** What the emitter must be able to do with a control's children and label. */
21
+ export interface ControlDeps extends ColorCtx {
22
+ /** Emits a child node the way the surrounding view would (containers → their property). */
23
+ child: (n: SemNode) => string[];
24
+ /** Control → `@State` property name; built by `controlMembers`. */
25
+ stateOf: Map<SemNode, string>;
26
+ /** `Action.key` → callback member; built by `controlMembers`. */
27
+ actionOf: Map<string, string>;
28
+ /** Grouped radio → its tag in the group's selection; built by `controlMembers`. */
29
+ radioTag: Map<SemNode, string>;
30
+ mode: EmitMode;
31
+ /** The screen's colour scheme (`adaptive`: both): `<progress>`/`<meter>`/`<input type=range>` are painted by the browser, whose palette follows it. */
32
+ theme: Theme | "adaptive";
33
+ /** This screen's colour → token lookup, and the control node its colours belong to. */
34
+ tokens?: ScreenTokens;
35
+ id?: string;
36
+ /** The node's box modifiers, emitted inside a pressable control's label. */
37
+ box?: string[];
38
+ /** Pressable owner → its `@MarkswiftPressed` member; built by `controlMembers`. */
39
+ pressOf?: Map<SemNode, string>;
40
+ /** ARIA toggle Button → what its tap runs; built by `controlMembers`. */
41
+ toggleOf?: ControlMembers["toggleOf"];
42
+ /** Layer trigger → what its tap runs; built by `controlMembers`. */
43
+ openOf?: ControlMembers["openOf"];
44
+ /** A presentation's closing button → what its tap runs; built by `controlMembers`. */
45
+ closeOf?: ControlMembers["closeOf"];
46
+ /** Native menu toggle/radio item → its `@State` member; built by `controlMembers`. */
47
+ menuState?: ControlMembers["menuState"];
48
+ /** Text field → its `@MarkswiftFocused` member; built by `controlMembers`. */
49
+ focusOf?: Map<SemNode, string>;
50
+ /** Packaged face metrics: a field in a bundled face spaces its lines from that face's natural line. */
51
+ fontMetrics?: FontMetrics;
52
+ }
53
+ /** Editable text controls: their own text is laid out in the CSS line box. */
54
+ export declare const FIELDS: Record<string, true>;
55
+ export declare function controlExpr(n: ControlNode, deps: ControlDeps): string[];
56
+ /**
57
+ * A captured menu's items as native menu content (plan 061): buttons on the screen's action members, checkbox items as
58
+ * Toggles, a radio run as an inline Picker, separators, labelled sections.
59
+ */
60
+ export declare function menuExpr(items: MenuItem[], deps: Pick<ControlDeps, "actionOf" | "menuState">): string[];
61
+ /** A menu trigger: its Button's label and style around a native `Menu` of the captured items. */
62
+ export declare function menuButton(menu: MenuSpec, label: string[], buttonStyle: string, deps: Pick<ControlDeps, "actionOf" | "menuState">): string[];
@@ -0,0 +1,3 @@
1
+ import { type SemNode, type DiagnosticLevel } from "../model/semantic.ts";
2
+ /** Records an emit-time loss on the node and regrades it; idempotent because both modes emit the same tree. */
3
+ export declare function note(n: SemNode, level: DiagnosticLevel, feature: string, message: string): void;
@@ -0,0 +1,23 @@
1
+ import type { Paint, Transform2D } from "../model/values.ts";
2
+ import type { SemNode, Filter, ClipShape } from "../model/semantic.ts";
3
+ import type { ColorCtx, EmitContext } from "./context.ts";
4
+ /**
5
+ * Skew has no SwiftUI modifier, so a sheared transform is emitted whole: the `Transform2D`
6
+ * composition `translate · rotate · skewX · skewY · scale`, conjugated by the CSS
7
+ * transform-origin (`T(o) · M · T(−o)`, `o` relative to the node's top-left, default centre)
8
+ * because `.transformEffect` is applied about the view's own origin. CGAffineTransform's
9
+ * `(a, b, c, d, tx, ty)` is the CSS `matrix()` layout, so the components map 1:1.
10
+ */
11
+ export declare function affineModifier(t: Transform2D & {
12
+ origin?: [number, number];
13
+ }, w: number, h: number): string;
14
+ /** One CSS `filter` function → SwiftUI modifier(s); lossy cases are noted on the node. */
15
+ export declare function filterModifier(f: Filter, n: SemNode, ctx: ColorCtx): string[];
16
+ /**
17
+ * `clip-path` basic shapes. Circle/ellipse are placed with `.size().offset()` in measured px
18
+ * (a centred full circle is just `Circle()`); inset masks with a padded rounded rectangle so it
19
+ * follows the box; polygons become a generated `Shape` in fractions of the measured box.
20
+ */
21
+ export declare function clipPathModifier(c: ClipShape, n: SemNode, ctx: EmitContext): string;
22
+ /** `mask-image`: the paint's alpha masks the node (gradients directly, images as a layer). */
23
+ export declare function maskModifier(p: Paint, n: SemNode, ctx: EmitContext): string[];
@@ -0,0 +1,6 @@
1
+ /** Assemble a screen from cohesive Swift emission modules. */
2
+ import type { Screen } from "../model/semantic.ts";
3
+ import type { EmitOptions, SwiftOutput } from "./context.ts";
4
+ export type { EmitMode, EmitOptions, SwiftOutput } from "./context.ts";
5
+ export { SWIFT_RUNTIME } from "./runtime.ts";
6
+ export declare function emitSwift(screen: Screen, viewName: string, opts: EmitOptions): SwiftOutput;
@@ -0,0 +1,91 @@
1
+ /**
2
+ * Swift source helpers shared by the emitters (views, SVG shapes, harness registry).
3
+ */
4
+ import type { Color } from "../model/values.ts";
5
+ import type { FontSpec } from "../model/semantic.ts";
6
+ import type { ColorCtx } from "./context.ts";
7
+ /** `Color.markswift(r, g, b[, a])` argument list; alpha omitted when opaque. */
8
+ export declare function colorArgs(c: Color): string;
9
+ /**
10
+ * The one place a colour becomes Swift: `Palette.surfaceRaised` when this node's colour is a
11
+ * token of the screen, else the literal `Color.markswift(…)`.
12
+ *
13
+ * `id` is the node painting the colour; the lookup is (screen, theme, node, colour value)
14
+ * because most call sites paint a colour without knowing which named slot of the node it is.
15
+ * Those that do (text runs) pass `slot` (`run2`, `run2.dec`), which still resolves when one
16
+ * light colour is two tokens on the node. Omitting `id` — an SVG inlined through an `<img>`, a
17
+ * control's own value — yields the literal: a colour the extractor never enumerated cannot
18
+ * claim a token.
19
+ *
20
+ * A colour the node's pressed/focused look changes (`ctx.statePaint`, `states.ts`) becomes
21
+ * `(isSavePressed ? state : rest)`, each side resolved the same way.
22
+ */
23
+ export declare function colorExpr(c: Color, ctx: ColorCtx, id?: string, slot?: string): string;
24
+ /** Swift `Double` literal: 3 decimals, always with a fraction so type inference stays `Double`. */
25
+ export declare function num(v: number): string;
26
+ /**
27
+ * Swift string literal. Swift accepts only `\0 \\ \t \n \r \" \' \u{…}` escapes and no lone
28
+ * surrogates, so JSON escaping (`\b`, `\f`, `\u000b`, `\ud800`) does not compile. Control
29
+ * characters (C0, DEL, C1) and U+2028/U+2029 become `\u{hex}`; a lone surrogate becomes U+FFFD.
30
+ */
31
+ export declare function str(s: string): string;
32
+ export declare function indent(lines: string[], n: number): string[];
33
+ /** `HorizontalAlignment` of a start/center/end value (anything else is the leading edge). */
34
+ export declare function horizontalAlignment(a: string | undefined): string;
35
+ /** `VerticalAlignment` of a start/center/end value (anything else is the top edge). */
36
+ export declare function verticalAlignment(a: string | undefined): string;
37
+ /** `Alignment` combining a horizontal and a vertical start/center/end value. */
38
+ export declare function alignment(h: string | undefined, v: string | undefined): string;
39
+ /** Dash array of a CSS `dashed` (3w on, 2w off) or `dotted` (round dots 2w apart) line. */
40
+ export declare function dashPattern(style: "dashed" | "dotted", width: number): string;
41
+ /** `StrokeStyle` for a CSS line style; styles other than dashed/dotted stroke solid. */
42
+ export declare function strokeStyle(width: number, style: string): string;
43
+ /** `EdgeInsets` of CSS-ordered (top, right, bottom, left) insets. */
44
+ export declare function edgeInsets([top, right, bottom, left]: readonly number[]): string;
45
+ /** A DOM id or design string as the body of a Swift identifier (may be empty). */
46
+ export declare function identifier(s: string): string;
47
+ /** `base` the first time, then `base_r2`, `base_r3`, … — counted in `seen`. */
48
+ export declare function uniqueName(base: string, seen: Record<string, number>): string;
49
+ /** Design words → PascalCase ASCII (`openMenu` → `OpenMenu`, `save-btn` → `SaveBtn`, `Gönder` → `Gonder`); at most 4 words, "" when none survive. */
50
+ export declare function pascalWords(s: string): string;
51
+ /**
52
+ * K1 member name: `prefix + PascalCase(words) + suffix` (`fallback` when no word survives); repeats become `…2`, `…3`.
53
+ * Without a prefix the name starts lower-case (`item title` → `itemTitle`). Registered in `seen`.
54
+ */
55
+ export declare function memberName(prefix: string, words: string, seen: Record<string, number>, fallback: string, suffix?: string): string;
56
+ /**
57
+ * Registers a `Shape` struct once under `name`; `body` gives the lines of `path(in:)`.
58
+ * `nonisolated`: Shape's requirements are nonisolated; under a MainActor default-isolation
59
+ * build (SE-0466) an unannotated struct would fail to conform in Swift 6 mode.
60
+ */
61
+ export declare function shapeStruct(name: string, shapes: Record<string, string>, body: () => string[]): string;
62
+ /** The `Font.TextStyle` whose default size is nearest `size`: what an app-mode font scales with. */
63
+ export declare function nearestTextStyle(size: number): string;
64
+ /**
65
+ * A system font's point size. App mode names an `@ScaledMetric` property (registered in
66
+ * `ctx.scaledFonts`, declared once per view) so the text follows Dynamic Type; fidelity, and
67
+ * `fixed` text placed in absolute coordinates, keep the literal.
68
+ */
69
+ export declare function fontSizeExpr(f: FontSpec, ctx: ColorCtx, fixed?: boolean): string;
70
+ /**
71
+ * Swift `Font` expression for a run's font. A system family is its design plus the nearest
72
+ * named weight in both modes, sized by `fontSizeExpr`, plus `.width(…)` (SF's nearest width
73
+ * instance, `SF_WIDTH`) for a captured `font-stretch`; a bundled family differs by mode:
74
+ *
75
+ * - **app** — `.custom(family, size:, relativeTo:)` plus an explicit `.weight(…)`: the shipped
76
+ * app must scale with Dynamic Type, and the weight is stated rather than inherited because a
77
+ * variable file's default instance is whatever its `fvar` says (Bricolage Grotesque defaults
78
+ * to 800, not 400).
79
+ * - **fidelity** — `.markswiftVariable(family, size:, weight:, opsz:)`: the variable axes the browser
80
+ * actually rendered, so an SSIM against the reference measures the design and not a weight
81
+ * quantisation. Nothing in this mode is shipped. A family without a `wght` axis is a set of
82
+ * static faces: a variation request would land on its default (regular) face, so the named
83
+ * weight selects the face instead, at a fixed size.
84
+ * - **captured axes, both modes** — a bundled family with `axes` (`font-variation-settings` tags,
85
+ * or `font-stretch` as `wdth`, beyond wght/opsz) is `.markswiftVariable(…, axes:)` in app mode
86
+ * too, sized by `fontSizeExpr` so Dynamic Type still applies: the shipped app sets the
87
+ * variation at runtime (docs/decisions.md "Fonts", 2.0).
88
+ */
89
+ export declare function fontExpr(f: FontSpec, ctx: ColorCtx, fixed?: boolean): string;
90
+ /** FNV-1a 32-bit, hex — stable names for generated shapes. */
91
+ export declare function hash(s: string): string;