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
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { SemNode, TextNode } from "../model/semantic.ts";
|
|
2
|
+
import type { EmitContext, EmitMode } from "./context.ts";
|
|
3
|
+
/**
|
|
4
|
+
* App: the line-box surplus the analysis moved into one edge's padding (text off its box's centre,
|
|
5
|
+
* `lineBoxGrowth.padded`). Emitted as a `minHeight` floor of the measured box aligned away from
|
|
6
|
+
* that edge instead, so growing text absorbs it like CSS does rather than adding it on top.
|
|
7
|
+
*/
|
|
8
|
+
export declare function surplusFloor(n: SemNode, ctx: Pick<EmitContext, "mode">): TextNode["lineBoxGrowth"];
|
|
9
|
+
/**
|
|
10
|
+
* App: how far a floored box's captured content overflows its content box, [top, bottom] — an icon taller than
|
|
11
|
+
* its badge, a line box taller than an `h-5` minus padding. CSS paints that overflow without growing the box;
|
|
12
|
+
* the padding gives it back (`boxModifiers`), so the floor grows only by what native text adds.
|
|
13
|
+
*/
|
|
14
|
+
export declare function floorOverflow(n: SemNode, ctx: EmitContext): [number, number];
|
|
15
|
+
export declare function frameModifier(n: SemNode, ctx: EmitContext): string[];
|
|
16
|
+
/** Where a node sits inside its fill frame: follows its own alignment semantics. */
|
|
17
|
+
export declare function fillAlignment(n: SemNode, mode: EmitMode): string;
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { Paint } from "../model/values.ts";
|
|
2
|
+
import type { SemNode, ImageNode, TextRun } from "../model/semantic.ts";
|
|
3
|
+
import type { EmitContext } from "./context.ts";
|
|
4
|
+
type ImagePaint = Extract<Paint, {
|
|
5
|
+
kind: "image";
|
|
6
|
+
}>;
|
|
7
|
+
/**
|
|
8
|
+
* An image sized and placed like a CSS background layer: a box-filling `Color.clear` carries
|
|
9
|
+
* the image as an overlay so cover/contain overflow is aligned to the box and clipped by the
|
|
10
|
+
* caller. Vector sources are scaled here (cover/contain/explicit size against the viewBox);
|
|
11
|
+
* raster ones let SwiftUI scale. Repeats tile through `MarkswiftTile` whenever the tile size is
|
|
12
|
+
* known (vector viewBox, explicit size, or an embedded raster's header size resolved by the
|
|
13
|
+
* analysis); a remote raster at its unknown intrinsic size uses `.tile`.
|
|
14
|
+
* `undefined` when the source cannot be shown (noted on the node).
|
|
15
|
+
*/
|
|
16
|
+
export declare function imageLayer(p: ImagePaint, n: SemNode, prop: string, ctx: EmitContext): string[] | undefined;
|
|
17
|
+
/** A generated image inside text (`TextRun.image`) as a view at its CSS size; `undefined` (noted) when it has no pixels. */
|
|
18
|
+
export declare function inlineImageView(img: NonNullable<TextRun["image"]>, n: SemNode, ctx: EmitContext): string[] | undefined;
|
|
19
|
+
/**
|
|
20
|
+
* `<img>`: the source at the IR's content mode. A source that cannot produce pixels falls back to
|
|
21
|
+
* the alt text in the measured box, where the browser draws it too; `alt` is also the
|
|
22
|
+
* accessibility label, and `alt=""` marks the image decorative.
|
|
23
|
+
*/
|
|
24
|
+
export declare function imageNodeExpr(n: ImageNode, ctx: EmitContext): string[];
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { BandEdge, StackNode, GridNode } from "../model/semantic.ts";
|
|
2
|
+
import type { EmitContext } from "./context.ts";
|
|
3
|
+
export declare function containerExpr(n: StackNode | GridNode, ctx: EmitContext): string[];
|
|
4
|
+
/** `MarkswiftBands(edges: […])`: inclusive edges are `.atLeast`, exclusive (`max-width`) ones `.above`. */
|
|
5
|
+
export declare function bandsOpen(edges: BandEdge[]): string;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
/** Emit measured content coordinates before the owning node's padding and paint. */
|
|
2
|
+
import type { GridNode, StackNode } from "../model/semantic.ts";
|
|
3
|
+
import type { EmitContext } from "./context.ts";
|
|
4
|
+
export declare function measuredLayoutExpr(n: GridNode | StackNode, ctx: EmitContext): string[];
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { SemNode, Overlay } from "../model/semantic.ts";
|
|
2
|
+
import type { EmitContext } from "./context.ts";
|
|
3
|
+
export declare function modifiers(n: SemNode, ctx: EmitContext): string[];
|
|
4
|
+
/**
|
|
5
|
+
* A node's view: its core lines plus modifiers. An element the design made activatable
|
|
6
|
+
* (`SemBase.action` on a non-control) becomes a Button whose label is the painted box — the box is
|
|
7
|
+
* the hit and press area — with placement and accessibility outside, like a native button. One that
|
|
8
|
+
* contains another control keeps its view and gains a tap gesture instead.
|
|
9
|
+
*/
|
|
10
|
+
export declare function decorate(n: SemNode, core: string[], ctx: EmitContext): string[];
|
|
11
|
+
/** App: a pass-through overlay (`pointer-events: none`), after the Button/tap gesture, so nothing inside answers a tap. */
|
|
12
|
+
export declare function hitTestingModifier(n: SemNode, ctx: EmitContext): string[];
|
|
13
|
+
/**
|
|
14
|
+
* The node's box: padding, frame, paint, borders, shadows, overlays, effects. A pressable control emits these inside its label, so they are its hit and press area.
|
|
15
|
+
* A node with a pressed/focused look paints the union style of `pairStyle`, its colours as ternaries; app output
|
|
16
|
+
* animates them with their CSS transitions: one `MarkswiftStateAnimation` at the end when every field shares its
|
|
17
|
+
* timing, else one after each field's paint (the nearest one wins for what it wraps).
|
|
18
|
+
* App: a scroll box's padding scrolls with its content (layout.ts); everything else here belongs to its ScrollView.
|
|
19
|
+
*/
|
|
20
|
+
export declare function boxModifiers(n: SemNode, ctx: EmitContext): string[];
|
|
21
|
+
/** The box's padding. App: a padded line-box surplus is a `minHeight` floor instead (`surplusFloor`); content CSS let
|
|
22
|
+
* overflow the floored box gives its overflow back (`floorOverflow`). */
|
|
23
|
+
export declare function paddingModifiers(n: SemNode, ctx: EmitContext): string[];
|
|
24
|
+
/**
|
|
25
|
+
* Where the finished box sits and what it is: transform, self-alignment, visibility, a `<label>`'s
|
|
26
|
+
* tap, accessibility. Nothing that paints or sizes the box: a button's box modifiers go inside its
|
|
27
|
+
* label, and these stay outside it.
|
|
28
|
+
*/
|
|
29
|
+
export declare function placementModifiers(n: SemNode, ctx: EmitContext): string[];
|
|
30
|
+
export declare function overlayModifier(o: Overlay, mode: "background" | "overlay", ctx: EmitContext): string[];
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/** IR `Motion` → the `MarkswiftAnimation` modifier (runtime/motion.swift). App output only. */
|
|
2
|
+
import type { Easing, KeyframeMotion, Motion, SemNode, StateField, StateTransition, TransitionSpec } from "../model/semantic.ts";
|
|
3
|
+
import type { EmitMode } from "./context.ts";
|
|
4
|
+
/** App output plays the node's animations; `motion.base` names the channels the modifier drives. */
|
|
5
|
+
export declare const animated: (n: SemNode, mode: EmitMode) => boolean;
|
|
6
|
+
export declare function easeExpr(e: Easing): string;
|
|
7
|
+
/** `MarkswiftTiming(…)` in seconds; defaults omitted. */
|
|
8
|
+
export declare function timingExpr(k: KeyframeMotion): string;
|
|
9
|
+
/** `.modifier(MarkswiftAnimation(…))`: `reduced` only when it differs, `base` only for non-identity channels, `anchor` only off-centre. */
|
|
10
|
+
export declare function animationModifier(motion: Motion): string;
|
|
11
|
+
/** A transition's SwiftUI `Animation` (`nil` = jump): bezier as `.timingCurve`, steps()/linear() via `MarkswiftEasing`. */
|
|
12
|
+
export declare function animationExpr(spec?: TransitionSpec): string;
|
|
13
|
+
/** `.modifier(MarkswiftStateAnimation(…))` for one paint field of a pressed/focused look. */
|
|
14
|
+
export declare function stateAnimation(cond: string, t: StateTransition | undefined, field: StateField): string;
|
|
15
|
+
/** The `Animation` of the longest exit among a layer's roots (what closing it waits for); undefined when none exits. */
|
|
16
|
+
export declare function longestExit(roots: SemNode[]): string | undefined;
|
|
17
|
+
/** `.transition(…)` of a layer root (066): in-flow content's reveal and/or an overlay root's exit; none when neither. */
|
|
18
|
+
export declare function transitionModifiers(n: SemNode): string[];
|
|
19
|
+
/** An in-flow layer's toggle: `state.toggle()` inside the reveal's close or open curve, when it has one. */
|
|
20
|
+
export declare function revealToggle(state: string, reveal: Motion["reveal"]): string;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { SemNode } from "../model/semantic.ts";
|
|
2
|
+
import type { EmitContext } from "./context.ts";
|
|
3
|
+
/** Lines for a node: container → property reference; leaf → inline expression (with modifiers); holder → band chain. */
|
|
4
|
+
export declare function refOf(n: SemNode, ctx: EmitContext): string[];
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { BlendMode, Paint } from "../model/values.ts";
|
|
2
|
+
import type { Style, SemNode } from "../model/semantic.ts";
|
|
3
|
+
import type { ColorCtx, EmitContext } from "./context.ts";
|
|
4
|
+
export declare function shapeExpr(st: Style, w: number, h: number): string;
|
|
5
|
+
/** A CSS paint as a SwiftUI style. `ctx`/`id` resolve its colours; see `colorExpr`. */
|
|
6
|
+
export declare function paintExpr(p: Paint, w: number, h: number, ctx: ColorCtx, id?: string): string;
|
|
7
|
+
/**
|
|
8
|
+
* One background layer: shape styles go through `.background(_:in:)`, images through a clipped
|
|
9
|
+
* view. `background-clip: padding-box | content-box` (`Paint.inset`) pads the painted shape
|
|
10
|
+
* inside the box; the corner radius shrinks with it, close to how Chrome insets the corners.
|
|
11
|
+
*/
|
|
12
|
+
export declare function fillModifier(p: Paint, n: SemNode, shape: string, ctx: EmitContext): string[];
|
|
13
|
+
export declare function swiftBlend(mode: BlendMode): string | undefined;
|
|
14
|
+
/**
|
|
15
|
+
* Background layers where at least one carries a `background-blend-mode`. A chain of
|
|
16
|
+
* `.background` modifiers gives each layer its own compositing group, so a blend would reach
|
|
17
|
+
* past the sibling layers to whatever is behind the node; CSS blends strictly within the
|
|
18
|
+
* node's own background. One `ZStack` (bottom-most first, as the IR stores them) inside a
|
|
19
|
+
* single `.background`, with the blend on the upper layer and a `.compositingGroup()` around
|
|
20
|
+
* the result, is that scope exactly.
|
|
21
|
+
*/
|
|
22
|
+
export declare function blendedFillModifier(fills: Paint[], n: SemNode, shape: string, ctx: EmitContext): string[];
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `MarkswiftRuntime.swift`. `controls.swift` opens with `import UniformTypeIdentifiers`, which
|
|
3
|
+
* Swift accepts anywhere at file scope, so every part is appended at top level. `core.swift` keeps a
|
|
4
|
+
* version-free header; the shipped runtime names the version that generated it.
|
|
5
|
+
*/
|
|
6
|
+
export declare const SWIFT_RUNTIME: string;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/** CSS box shadows paint the box silhouette, independently of its children and other shadows. */
|
|
2
|
+
import type { SemNode } from "../model/semantic.ts";
|
|
3
|
+
import type { EmitContext } from "./context.ts";
|
|
4
|
+
/** Outer shadows sit behind the border box; their interior stays clear even with no fill. */
|
|
5
|
+
export declare function outerShadowModifiers(n: SemNode, ctx: EmitContext): string[];
|
|
6
|
+
/** Inset shadows are below content and above fills, clipped to the inner border edge. */
|
|
7
|
+
export declare function insetShadowModifiers(n: SemNode, ctx: EmitContext): string[];
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SvgNode → SwiftUI: one `Shape` struct per unique (path, viewBox), layered fills/strokes
|
|
3
|
+
* in a ZStack (gradients, dash arrays, clips and masks are per-layer modifiers); progress
|
|
4
|
+
* rings become trimmed `Circle`s; `<text>` runs are `Text`s positioned where the browser
|
|
5
|
+
* laid them out.
|
|
6
|
+
*/
|
|
7
|
+
import type { SvgNode } from "../model/semantic.ts";
|
|
8
|
+
import type { ColorCtx } from "./context.ts";
|
|
9
|
+
/**
|
|
10
|
+
* Expression lines for an SVG node; registers any new Shape structs in `shapes` (name → source).
|
|
11
|
+
* `id` is the node the colours belong to, for the token lookup: an `<svg>` node of the IR. An
|
|
12
|
+
* SVG inlined through an `<img>` has no enumerated colours, so it passes none and stays literal.
|
|
13
|
+
*/
|
|
14
|
+
export declare function svgExpr(n: SvgNode, shapes: Record<string, string>, ctx: ColorCtx, id?: string): string[];
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/** Alignment shared by native text and its surrounding CSS size constraints. */
|
|
2
|
+
import type { TextNode } from "../model/semantic.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Natural line height of a system face, as a multiple of `font-size`: the height SwiftUI gives
|
|
5
|
+
* one `Text` line before `.lineSpacing`. Measured on the verifier's simulator, it equals
|
|
6
|
+
* `CTFontGetAscent + CTFontGetDescent + CTFontGetLeading` at every size from 10 to 60 pt, for
|
|
7
|
+
* every weight and all four `Font.Design` values: Apple keeps the system faces metric-compatible
|
|
8
|
+
* with SF Pro's `hhea` (1950 + 494) / 2048.
|
|
9
|
+
*/
|
|
10
|
+
export declare const SYSTEM_LINE_HEIGHT = 1.193359;
|
|
11
|
+
export declare function clipsFirstLine(n: TextNode): boolean;
|
|
12
|
+
export declare function textBoxAlignment(n: TextNode, top?: boolean): string;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { ColorToken, PaletteMap, TokenSet } from "./types.ts";
|
|
2
|
+
import type { Color } from "../../model/values.ts";
|
|
3
|
+
import type { ConversionWarning } from "../../model/semantic.ts";
|
|
4
|
+
/**
|
|
5
|
+
* `<View>+Tokens.swift`: one `static let` per token in `extension <View> { enum Palette }`, so
|
|
6
|
+
* two views never collide and a token can never shadow SwiftUI's `Color.primary`. Static tokens
|
|
7
|
+
* emit a single colour; semantic tokens emit the pair via the runtime's
|
|
8
|
+
* `Color.markswift(light:dark:)`. `adaptive` says whether the set was paired from a light and a
|
|
9
|
+
* dark capture, which the header states. Without `owner` the palette is module-level: one
|
|
10
|
+
* `Palette` shared by every view of a generated library (`markswift build`). A `palette` map
|
|
11
|
+
* replaces a matched token's value with the app's own Swift colour expression, verbatim.
|
|
12
|
+
*/
|
|
13
|
+
export declare function emitDesignTokens(set: TokenSet, owner: string | undefined, options?: {
|
|
14
|
+
adaptive?: boolean;
|
|
15
|
+
visibility?: "internal" | "public";
|
|
16
|
+
palette?: PaletteMap;
|
|
17
|
+
}): string;
|
|
18
|
+
/** `#rrggbb`, plus `aa` when not opaque; lowercase. */
|
|
19
|
+
export declare function tokenHex(c: Color): string;
|
|
20
|
+
/** The map entry that replaces `t`'s value; the first of {@link paletteKeys} present wins. */
|
|
21
|
+
export declare function paletteEntry(t: ColorToken, map: PaletteMap): {
|
|
22
|
+
key: string;
|
|
23
|
+
swift: string;
|
|
24
|
+
} | undefined;
|
|
25
|
+
/** Unmapped tokens (with the key each could use) and map keys that matched no token; `[]` when the map is exact. */
|
|
26
|
+
export declare function paletteWarnings(set: TokenSet, map: PaletteMap): ConversionWarning[];
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Screen } from "../../model/semantic.ts";
|
|
2
|
+
import type { ScreenTokens } from "../context.ts";
|
|
3
|
+
import type { TokenSet } from "./types.ts";
|
|
4
|
+
/**
|
|
5
|
+
* Reduce every screen of a run — both themes — to a token set.
|
|
6
|
+
*
|
|
7
|
+
* A slot whose light and dark colours differ becomes a semantic token — even when it is the
|
|
8
|
+
* only slot painting that pair, so an adaptive view never keeps a light literal for it; a colour
|
|
9
|
+
* identical in both themes and used at two or more slots becomes a static token; a single-use
|
|
10
|
+
* identical colour stays inline. Names come from the CSS custom property the IR recorded when one can be
|
|
11
|
+
* attributed unambiguously, otherwise from role and colour (`fillBlue`, `textPrimary`), with a
|
|
12
|
+
* numeric suffix on collision. Deterministic: the same screens produce the same names.
|
|
13
|
+
*/
|
|
14
|
+
export declare function extractTokens(screens: Screen[]): TokenSet;
|
|
15
|
+
/**
|
|
16
|
+
* The token lookup for one screen: binds the screen group and theme, so the emitters ask only
|
|
17
|
+
* "which token does this node's colour name here?". A miss — an unenumerated colour, or one
|
|
18
|
+
* two tokens claim on the same node — leaves the literal in place. A call site that names the
|
|
19
|
+
* slot gets that slot's token only. A breakpoint view passes its band screens' bases, lowest band first.
|
|
20
|
+
*/
|
|
21
|
+
export declare function screenTokens(set: TokenSet, screen: Screen, bases?: string[]): ScreenTokens;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Color } from "../../model/values.ts";
|
|
2
|
+
import type { Usage } from "./types.ts";
|
|
3
|
+
/** `surface-raised` → `surfaceRaised`; always a valid, non-keyword Swift member name. */
|
|
4
|
+
export declare function camelize(name: string): string;
|
|
5
|
+
/**
|
|
6
|
+
* Human-ish colour label: hue name when the colour reads as coloured, grey-ramp tone when it
|
|
7
|
+
* reads as neutral. "Coloured" is HSV saturation, not raw chroma — a near-black `#0B0F14` has
|
|
8
|
+
* a blue hue on paper and none to the eye, and a slate `#8A93A0` is a grey in a design system.
|
|
9
|
+
*/
|
|
10
|
+
export declare function tone(c: Color): string;
|
|
11
|
+
/**
|
|
12
|
+
* Assign token hints to colour groups. `Style.tokens` is a per-node *set*. A role-qualified
|
|
13
|
+
* class hint (`bg:white`, `from:sky-500`) names the slots of its utility ({@link HINT_SLOTS}),
|
|
14
|
+
* and only when they paint one colour on that node. A custom property is subtler — the IR knows
|
|
15
|
+
* a node referenced `var(--card)` but not which slot it painted. A colour custom property is by
|
|
16
|
+
* definition constant, so the slot it names is the one that resolves to a single colour across
|
|
17
|
+
* every node referencing the property, on most of those nodes: `--muted` is one colour at `run0`
|
|
18
|
+
* everywhere it appears, while `--fd` (a font family) sits on nodes whose text runs are five
|
|
19
|
+
* different colours and so names nothing. Hints without a consistent slot are dropped rather
|
|
20
|
+
* than guessed, and so is a colour two names claim equally.
|
|
21
|
+
*/
|
|
22
|
+
export declare function assignHints(groups: Map<string, Usage[]>): Map<string, string>;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { Screen } from "../../model/semantic.ts";
|
|
2
|
+
import type { Usage } from "./types.ts";
|
|
3
|
+
/** `home-light` + theme `light` → `home`; a name without the suffix is its own base. */
|
|
4
|
+
export declare function screenBase(screen: Screen): string;
|
|
5
|
+
/**
|
|
6
|
+
* Pair the themes slot by slot. Screens are grouped by {@link screenBase}; inside a group the
|
|
7
|
+
* light screen drives document order and the dark screen supplies the second value. Only the
|
|
8
|
+
* light screen is emitted, so a slot the dark screen alone paints makes no usage at all; a light
|
|
9
|
+
* slot without a dark counterpart stays unpaired — it can still make a static token, never a
|
|
10
|
+
* semantic one. {@link themeDrift} reports both.
|
|
11
|
+
*/
|
|
12
|
+
export declare function pairUsages(screens: Screen[]): Usage[];
|
|
13
|
+
/**
|
|
14
|
+
* Node ids where the dark screen differs from the light one in more than colour: a node or a
|
|
15
|
+
* colour slot present in one theme only, or a frame that moved or resized by more than 0.5px.
|
|
16
|
+
* An adaptive view is the light layout with paired colours, so these keep their light values.
|
|
17
|
+
*/
|
|
18
|
+
export declare function themeDrift(light: Screen, dark: Screen): string[];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Color } from "../../model/values.ts";
|
|
2
|
+
import type { Theme } from "../../model/layout.ts";
|
|
3
|
+
/** What a colour paints; drives the fallback name and the token-hint consistency check. */
|
|
4
|
+
export type TokenRole = "canvas" | "fill" | "border" | "text" | "shadow" | "icon";
|
|
5
|
+
/** One extracted colour token. `light === dark` for `kind: "static"`. */
|
|
6
|
+
export interface ColorToken {
|
|
7
|
+
name: string;
|
|
8
|
+
kind: "semantic" | "static";
|
|
9
|
+
light: Color;
|
|
10
|
+
dark: Color;
|
|
11
|
+
/** Dominant role of its usages — the fallback name is derived from it. */
|
|
12
|
+
role: TokenRole;
|
|
13
|
+
/** CSS custom property the name came from, when the IR carried a usable hint. */
|
|
14
|
+
hint?: string;
|
|
15
|
+
/** Node ids that paint with this token, in document order, deduplicated. */
|
|
16
|
+
uses: string[];
|
|
17
|
+
/** Colour slots this token covers (a light/dark pair counts once). */
|
|
18
|
+
slots: number;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* App colours for captured tokens. Keys: a CSS custom property (`--primary`), a hex colour for a
|
|
22
|
+
* static token (`#e2e8f0`, `#e2e8f080`), or `#light/#dark` for an adaptive one. Values: a Swift
|
|
23
|
+
* `Color` expression (`Color.accentColor`, `Color("Card")`), emitted verbatim.
|
|
24
|
+
*/
|
|
25
|
+
export type PaletteMap = Readonly<Record<string, string>>;
|
|
26
|
+
/**
|
|
27
|
+
* Where the view emitter resolves a literal: `base\0theme\0nodeId\0r,g,b,a` → token name, and
|
|
28
|
+
* the same key plus `\0slot` for call sites that know the slot.
|
|
29
|
+
*
|
|
30
|
+
* Most call sites that paint a colour do not know which named slot of the node they are
|
|
31
|
+
* painting (a `.shadow(color:)` line only has the `Shadow`), so the key is the colour value
|
|
32
|
+
* the node paints in the theme being emitted. Keyed per node, because one value can be two
|
|
33
|
+
* tokens in one screen — the same light colour with two different dark counterparts. A key
|
|
34
|
+
* two tokens claim is absent from the index: ambiguity keeps the literal instead of guessing
|
|
35
|
+
* the other theme; only the slot-qualified key resolves it.
|
|
36
|
+
*/
|
|
37
|
+
export type TokenIndex = Map<string, string>;
|
|
38
|
+
/** Result of {@link extractTokens}. */
|
|
39
|
+
export interface TokenSet {
|
|
40
|
+
/** Tokens in first-use order: semantic and static interleaved as the design uses them. */
|
|
41
|
+
tokens: ColorToken[];
|
|
42
|
+
/** Colour slots left inline: single-use colours that are identical in both themes. */
|
|
43
|
+
inlineSlots: number;
|
|
44
|
+
/** Resolution table for the emitters; see {@link screenTokens}. */
|
|
45
|
+
index: TokenIndex;
|
|
46
|
+
}
|
|
47
|
+
/** A single colour slot of one node in one theme. */
|
|
48
|
+
export interface Slot {
|
|
49
|
+
/** Stable within a node: `fill0`, `border.top`, `run1.dec`, `path3.stroke`. */
|
|
50
|
+
key: string;
|
|
51
|
+
role: TokenRole;
|
|
52
|
+
color: Color;
|
|
53
|
+
/**
|
|
54
|
+
* Which CSS gradient stop a gradient slot is — what a `from-`/`via-`/`to-` utility names:
|
|
55
|
+
* the first, the last, and the middle one only when it is the only middle one.
|
|
56
|
+
*/
|
|
57
|
+
stop?: "from" | "via" | "to";
|
|
58
|
+
}
|
|
59
|
+
/** A colour slot after pairing the two themes. */
|
|
60
|
+
export interface Usage {
|
|
61
|
+
/** Screen group ({@link screenBase}) this slot belongs to. */
|
|
62
|
+
base: string;
|
|
63
|
+
/** Theme whose screen supplied {@link Usage.light} — a dark-only group has no light screen. */
|
|
64
|
+
theme: Theme;
|
|
65
|
+
nodeId: string;
|
|
66
|
+
slot: string;
|
|
67
|
+
role: TokenRole;
|
|
68
|
+
light: Color;
|
|
69
|
+
/** Unset when the node/slot exists in one theme only. */
|
|
70
|
+
dark?: Color;
|
|
71
|
+
/** Token hints of the node: custom-property names and role-qualified class hints. */
|
|
72
|
+
hints: string[];
|
|
73
|
+
/** {@link Slot.stop} */
|
|
74
|
+
stop?: "from" | "via" | "to";
|
|
75
|
+
/** Document-order index of the first theme that carried this slot. */
|
|
76
|
+
order: number;
|
|
77
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
/** Design-token API: extraction and Swift serialization have independent modules. */
|
|
2
|
+
export type { TokenRole, ColorToken, TokenIndex, TokenSet, PaletteMap } from "./tokens/types.ts";
|
|
3
|
+
export { pairUsages, screenBase, themeDrift } from "./tokens/slots.ts";
|
|
4
|
+
export { camelize } from "./tokens/naming.ts";
|
|
5
|
+
export { extractTokens, screenTokens } from "./tokens/extract.ts";
|
|
6
|
+
export { emitDesignTokens, paletteWarnings, tokenHex } from "./tokens/emit.ts";
|
package/package.json
CHANGED
|
@@ -1,6 +1,68 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "markswift",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "2.0.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"repository": {
|
|
6
|
+
"type": "git",
|
|
7
|
+
"url": "git+https://github.com/raelsei/markswift.git"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"bin": {
|
|
11
|
+
"markswift": "./dist/cli.js"
|
|
12
|
+
},
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "rm -rf dist && bun build src/cli.ts src/index.ts src/compiler/index.ts --target=node --packages=external --splitting --outdir dist && tsc -p tsconfig.build.json",
|
|
15
|
+
"prepack": "bun run build",
|
|
16
|
+
"typecheck": "tsc --noEmit -p .",
|
|
17
|
+
"lint": "biome ci .",
|
|
18
|
+
"format": "biome format --write .",
|
|
19
|
+
"test": "bun test tests/unit tests/architecture tests/integration tests/conformance playground/tests",
|
|
20
|
+
"quality": "bun run tools/quality.ts",
|
|
21
|
+
"pilot": "bun run tests/conformance/pilot-run.ts",
|
|
22
|
+
"test:browser": "MARKSWIFT_BROWSER_TESTS=1 bun test --timeout 60000 tests/integration tests/conformance",
|
|
23
|
+
"test:native": "MARKSWIFT_SWIFT_TESTS=1 bun test --timeout 60000 tests/integration/swift.test.ts tests/unit/swift/wrapping.test.ts tests/unit/swift/tracks.test.ts tests/unit/swift/table.test.ts tests/unit/swift/counter.test.ts tests/integration/generated-content.test.ts tests/unit/swift/flex-row.test.ts tests/unit/swift/measured-layout.test.ts tests/unit/swift/line-height.test.ts tests/unit/swift/tooltips.test.ts tests/unit/swift/disclosure.test.ts tests/unit/swift/presentation.test.ts tests/unit/swift/menus.test.ts tests/unit/swift/bands.test.ts tests/unit/swift/bands-emit.test.ts tests/unit/swift/motion-runtime.test.ts tests/unit/swift/overlay-motion.test.ts tests/integration/motion-timing.test.ts tests/conformance/pilot-run.test.ts tests/integration/perf.test.ts",
|
|
24
|
+
"test:unit": "bun test tests/unit",
|
|
25
|
+
"test:architecture": "bun test tests/architecture",
|
|
26
|
+
"test:conformance": "bun test tests/conformance",
|
|
27
|
+
"test:ios-behaviour": "bun run tools/verify/ios-behaviour.ts"
|
|
28
|
+
},
|
|
29
|
+
"dependencies": {
|
|
30
|
+
"domhandler": "^5.0.3",
|
|
31
|
+
"htmlparser2": "^9.1.0",
|
|
32
|
+
"playwright-core": "^1.63.0",
|
|
33
|
+
"svgpath": "^2.6.0",
|
|
34
|
+
"zod": "^4.1.0"
|
|
35
|
+
},
|
|
36
|
+
"devDependencies": {
|
|
37
|
+
"@biomejs/biome": "2.5.15",
|
|
38
|
+
"@types/bun": "^1.4.1",
|
|
39
|
+
"@types/pngjs": "^6.0.5",
|
|
40
|
+
"pixelmatch": "^7.1.0",
|
|
41
|
+
"pngjs": "^7.0.0",
|
|
42
|
+
"sharp": "0.34.5",
|
|
43
|
+
"ssim.js": "^3.5.0",
|
|
44
|
+
"typescript": "^5.9.0"
|
|
45
|
+
},
|
|
46
|
+
"exports": {
|
|
47
|
+
".": {
|
|
48
|
+
"types": "./dist/types/index.d.ts",
|
|
49
|
+
"default": "./dist/index.js"
|
|
50
|
+
},
|
|
51
|
+
"./compiler": {
|
|
52
|
+
"types": "./dist/types/compiler/index.d.ts",
|
|
53
|
+
"default": "./dist/compiler/index.js"
|
|
54
|
+
},
|
|
55
|
+
"./package.json": "./package.json"
|
|
56
|
+
},
|
|
57
|
+
"types": "./dist/types/index.d.ts",
|
|
58
|
+
"files": [
|
|
59
|
+
"dist",
|
|
60
|
+
"README.md",
|
|
61
|
+
"CHANGELOG.md"
|
|
62
|
+
],
|
|
63
|
+
"engines": {
|
|
64
|
+
"node": ">=20",
|
|
65
|
+
"bun": ">=1.4.2"
|
|
66
|
+
},
|
|
67
|
+
"description": "Measured HTML and CSS to native SwiftUI components"
|
|
68
|
+
}
|