@mildastudio/milda 0.1.0 → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -13
- package/dist/index.d.mts +94 -5
- package/dist/index.d.ts +94 -5
- package/dist/index.js +609 -43
- package/dist/index.mjs +608 -43
- package/package.json +5 -3
package/dist/index.mjs
CHANGED
|
@@ -7,6 +7,7 @@ var TOKEN_VALUE_KIND = {
|
|
|
7
7
|
shadow: "shadow",
|
|
8
8
|
typography: "typography",
|
|
9
9
|
fontFamily: "fontFamily",
|
|
10
|
+
fontWeight: "fontWeight",
|
|
10
11
|
duration: "duration",
|
|
11
12
|
opacity: "opacity",
|
|
12
13
|
blur: "blur",
|
|
@@ -536,6 +537,7 @@ var composites = [
|
|
|
536
537
|
{ atom: "presentation", as: "P", params: { surface: "inline" } }
|
|
537
538
|
],
|
|
538
539
|
composition: [{ kind: "wire", from: "T.change", to: "P.toggle" }],
|
|
540
|
+
emits: [{ name: "toggle" }],
|
|
539
541
|
note: "\u2248 toggle(expanded) + presentation(inline); exposes expanded"
|
|
540
542
|
},
|
|
541
543
|
{
|
|
@@ -573,6 +575,7 @@ var composites = [
|
|
|
573
575
|
kind: "composite",
|
|
574
576
|
name: "stepping-nav",
|
|
575
577
|
builtFrom: ["navigation"],
|
|
578
|
+
emits: [{ name: "stepChange" }, { name: "complete" }],
|
|
576
579
|
note: "\u2248 navigation(ordered) + constraint; ordered progress through steps; exposes active, complete"
|
|
577
580
|
},
|
|
578
581
|
{
|
|
@@ -926,7 +929,11 @@ var archetypes = [
|
|
|
926
929
|
}
|
|
927
930
|
],
|
|
928
931
|
composes: ["selection.single", "presentation", "browse", "dismiss", "masking"],
|
|
929
|
-
|
|
932
|
+
// Behavior is the shared framework-agnostic calendar core (proposal 0029,
|
|
933
|
+
// targets/behavior/calendar.ts); node.calendar picks single|range + weekStartsOn
|
|
934
|
+
// + disabledWeekdays. The `day` grid cell carries calendar states (today,
|
|
935
|
+
// outsideMonth, selected, rangeStart, rangeEnd, inRange) rendered as data-* attrs.
|
|
936
|
+
realizationNote: "web calendar-grid (single|range via node.calendar) \xB7 iOS wheel (momentum+detent+haptic) \xB7 Android dialog"
|
|
930
937
|
},
|
|
931
938
|
{
|
|
932
939
|
kind: "archetype",
|
|
@@ -958,7 +965,10 @@ var archetypes = [
|
|
|
958
965
|
}
|
|
959
966
|
],
|
|
960
967
|
composes: ["selection.single", "presentation", "browse", "dismiss", "masking"],
|
|
961
|
-
|
|
968
|
+
// Behavior is the shared framework-agnostic time core (proposal 0029,
|
|
969
|
+
// targets/behavior/timepicker.ts); node.timePicker picks the slot step + 12/24h.
|
|
970
|
+
// Web realization = a listbox of time slots; iOS wheel is a per-platform view.
|
|
971
|
+
realizationNote: "web time-slot listbox (step via node.timePicker) \xB7 iOS wheel \xB7 Android dialog"
|
|
962
972
|
},
|
|
963
973
|
{
|
|
964
974
|
kind: "archetype",
|
|
@@ -981,11 +991,25 @@ var archetypes = [
|
|
|
981
991
|
children: [
|
|
982
992
|
{ name: "area", role: "container" },
|
|
983
993
|
{ name: "hue", role: "container" },
|
|
984
|
-
{ name: "
|
|
994
|
+
{ name: "alpha", role: "container" },
|
|
995
|
+
{ name: "eyedropper", role: "control" },
|
|
996
|
+
{ name: "input", role: "input" },
|
|
997
|
+
{
|
|
998
|
+
name: "swatches",
|
|
999
|
+
role: "container",
|
|
1000
|
+
children: [{ name: "swatch", role: "item" }]
|
|
1001
|
+
}
|
|
985
1002
|
]
|
|
986
1003
|
}
|
|
987
1004
|
],
|
|
988
|
-
composes: ["range", "value-entry", "presentation", "dismiss"]
|
|
1005
|
+
composes: ["range", "value-entry", "presentation", "dismiss"],
|
|
1006
|
+
// Behavior is the shared framework-agnostic color core (proposal 0029/0035,
|
|
1007
|
+
// targets/behavior/color.ts): HSV<->RGB<->hex + OKLCH math and the area/hue/alpha
|
|
1008
|
+
// geometry, so every target computes an identical picker. node.colorPicker (0035)
|
|
1009
|
+
// picks the working space (srgb|oklch), the editable format tabs (hex/rgb/hsl/hsb),
|
|
1010
|
+
// opacity, eyedropper, preset swatches, and popover-vs-inline layout. The `alpha`,
|
|
1011
|
+
// `eyedropper` and `swatches` parts render only when their config flag is on.
|
|
1012
|
+
realizationNote: "web: SV/OKLCH area + hue + optional alpha, format tabs, eyedropper, swatches, popover|inline \xB7 native color input fallback"
|
|
989
1013
|
},
|
|
990
1014
|
{
|
|
991
1015
|
kind: "archetype",
|
|
@@ -1462,10 +1486,11 @@ var archetypes = [
|
|
|
1462
1486
|
},
|
|
1463
1487
|
{
|
|
1464
1488
|
kind: "archetype",
|
|
1465
|
-
name: "
|
|
1489
|
+
name: "Media",
|
|
1466
1490
|
group: "Media",
|
|
1467
|
-
contract: "
|
|
1491
|
+
contract: "image/video/audio/embed w/ alt + fit (proposal 0028)",
|
|
1468
1492
|
produces: { level: "phrase", interactive: false },
|
|
1493
|
+
anatomy: [{ name: "media", role: "content" }],
|
|
1469
1494
|
composes: []
|
|
1470
1495
|
},
|
|
1471
1496
|
{
|
|
@@ -1495,6 +1520,14 @@ var facets = [
|
|
|
1495
1520
|
covers: "edge: color + width (hairline|thin|thick) + style",
|
|
1496
1521
|
divergence: "iOS often overlay-stroke"
|
|
1497
1522
|
},
|
|
1523
|
+
{ name: "border.top", covers: "top edge (physical side)" },
|
|
1524
|
+
{ name: "border.right", covers: "right edge (physical side)" },
|
|
1525
|
+
{ name: "border.bottom", covers: "bottom edge (physical side)" },
|
|
1526
|
+
{ name: "border.left", covers: "left edge (physical side)" },
|
|
1527
|
+
{ name: "border.inline-start", covers: "leading edge (logical, writing-direction)" },
|
|
1528
|
+
{ name: "border.inline-end", covers: "trailing edge (logical, writing-direction)" },
|
|
1529
|
+
{ name: "border.block-start", covers: "block-start edge (logical)" },
|
|
1530
|
+
{ name: "border.block-end", covers: "block-end edge (logical)" },
|
|
1498
1531
|
{ name: "corner", covers: "corner radius (token role)" },
|
|
1499
1532
|
{ name: "inset", covers: "internal padding (block/inline)" },
|
|
1500
1533
|
{ name: "gap", covers: "spacing between children" },
|
|
@@ -1509,6 +1542,17 @@ var facets = [
|
|
|
1509
1542
|
divergence: "web -webkit-line-clamp \xB7 .lineLimit \xB7 maxLines+overflow"
|
|
1510
1543
|
},
|
|
1511
1544
|
{ name: "textWrap", covers: "wrap | nowrap | break" },
|
|
1545
|
+
{
|
|
1546
|
+
name: "weight",
|
|
1547
|
+
covers: "font-weight (typography axis \xA73, split out of the `text` role bundle)",
|
|
1548
|
+
divergence: "web font-weight \xB7 .fontWeight \xB7 TextStyle.fontWeight"
|
|
1549
|
+
},
|
|
1550
|
+
{
|
|
1551
|
+
name: "font",
|
|
1552
|
+
covers: "font-family (typography axis \xA73)",
|
|
1553
|
+
divergence: "web font-family \xB7 UIFont \xB7 Typeface"
|
|
1554
|
+
},
|
|
1555
|
+
{ name: "slant", covers: "font-style: normal | italic | oblique (typography axis \xA73)" },
|
|
1512
1556
|
{
|
|
1513
1557
|
name: "elevation",
|
|
1514
1558
|
covers: "depth",
|
|
@@ -1619,6 +1663,9 @@ var preludeV0 = {
|
|
|
1619
1663
|
nonBehaviors
|
|
1620
1664
|
};
|
|
1621
1665
|
var atomsByName = Object.fromEntries(atoms.map((a) => [a.name, a]));
|
|
1666
|
+
var compositesByName = Object.fromEntries(
|
|
1667
|
+
composites.map((c) => [c.name, c])
|
|
1668
|
+
);
|
|
1622
1669
|
var archetypesByName = Object.fromEntries(
|
|
1623
1670
|
archetypes.map((a) => [a.name, a])
|
|
1624
1671
|
);
|
|
@@ -1694,6 +1741,8 @@ function facetTokenRefs(facets2) {
|
|
|
1694
1741
|
if ("token" in v) ids.push(v.token);
|
|
1695
1742
|
else if ("inset" in v) {
|
|
1696
1743
|
for (const edge of Object.values(v.inset)) if (typeof edge === "string") ids.push(edge);
|
|
1744
|
+
} else if ("border" in v) {
|
|
1745
|
+
if (v.border.color) ids.push(v.border.color);
|
|
1697
1746
|
}
|
|
1698
1747
|
}
|
|
1699
1748
|
return ids;
|
|
@@ -1726,12 +1775,13 @@ function validate(doc, prelude) {
|
|
|
1726
1775
|
for (const tok of layer.tokens) {
|
|
1727
1776
|
if (tok.value.kind === "alias") {
|
|
1728
1777
|
const where = `token ${tok.name} (${tok.id})`;
|
|
1729
|
-
if (!groupIds.has(tok.value.by)) {
|
|
1778
|
+
if (tok.value.by !== "" && !groupIds.has(tok.value.by)) {
|
|
1730
1779
|
issues.push({
|
|
1731
1780
|
severity: "error",
|
|
1732
1781
|
code: "alias.unknownGroup",
|
|
1733
1782
|
message: `alias by unknown context group "${tok.value.by}"`,
|
|
1734
|
-
where
|
|
1783
|
+
where,
|
|
1784
|
+
id: tok.id
|
|
1735
1785
|
});
|
|
1736
1786
|
}
|
|
1737
1787
|
for (const refId of Object.values(tok.value.refs)) {
|
|
@@ -1740,7 +1790,8 @@ function validate(doc, prelude) {
|
|
|
1740
1790
|
severity: "error",
|
|
1741
1791
|
code: "alias.danglingRef",
|
|
1742
1792
|
message: `alias ref points at missing token "${refId}"`,
|
|
1743
|
-
where
|
|
1793
|
+
where,
|
|
1794
|
+
id: tok.id
|
|
1744
1795
|
});
|
|
1745
1796
|
}
|
|
1746
1797
|
}
|
|
@@ -1749,12 +1800,13 @@ function validate(doc, prelude) {
|
|
|
1749
1800
|
}
|
|
1750
1801
|
for (const comp of doc.components) {
|
|
1751
1802
|
const where = `component ${comp.name} (${comp.id})`;
|
|
1752
|
-
if (!archetypeNames.has(comp.archetype)) {
|
|
1803
|
+
if (comp.archetype !== "" && !archetypeNames.has(comp.archetype)) {
|
|
1753
1804
|
issues.push({
|
|
1754
1805
|
severity: "error",
|
|
1755
1806
|
code: "component.unknownArchetype",
|
|
1756
1807
|
message: `unknown archetype "${comp.archetype}"`,
|
|
1757
|
-
where
|
|
1808
|
+
where,
|
|
1809
|
+
id: comp.id
|
|
1758
1810
|
});
|
|
1759
1811
|
}
|
|
1760
1812
|
const propIds = new Set(comp.contract.props.map((p) => p.id));
|
|
@@ -1764,7 +1816,8 @@ function validate(doc, prelude) {
|
|
|
1764
1816
|
severity: "error",
|
|
1765
1817
|
code: "param.danglingProp",
|
|
1766
1818
|
message: `param "${param}" binds missing prop "${binding.prop}"`,
|
|
1767
|
-
where
|
|
1819
|
+
where,
|
|
1820
|
+
id: comp.id
|
|
1768
1821
|
});
|
|
1769
1822
|
}
|
|
1770
1823
|
}
|
|
@@ -1785,7 +1838,8 @@ function checkPart(comp, partName, part, ctx) {
|
|
|
1785
1838
|
severity: "error",
|
|
1786
1839
|
code: "facet.unknown",
|
|
1787
1840
|
message: `unknown facet "${name}"`,
|
|
1788
|
-
where: scope
|
|
1841
|
+
where: scope,
|
|
1842
|
+
id: comp.id
|
|
1789
1843
|
});
|
|
1790
1844
|
}
|
|
1791
1845
|
}
|
|
@@ -1795,7 +1849,8 @@ function checkPart(comp, partName, part, ctx) {
|
|
|
1795
1849
|
severity: "error",
|
|
1796
1850
|
code: "facet.danglingToken",
|
|
1797
1851
|
message: `facet references missing token "${id}"`,
|
|
1798
|
-
where: scope
|
|
1852
|
+
where: scope,
|
|
1853
|
+
id: comp.id
|
|
1799
1854
|
});
|
|
1800
1855
|
}
|
|
1801
1856
|
}
|
|
@@ -1807,7 +1862,8 @@ function checkPart(comp, partName, part, ctx) {
|
|
|
1807
1862
|
severity: "error",
|
|
1808
1863
|
code: "layout.danglingToken",
|
|
1809
1864
|
message: `layout references missing token "${id}"`,
|
|
1810
|
-
where: scope
|
|
1865
|
+
where: scope,
|
|
1866
|
+
id: comp.id
|
|
1811
1867
|
});
|
|
1812
1868
|
}
|
|
1813
1869
|
}
|
|
@@ -1821,7 +1877,8 @@ function checkPart(comp, partName, part, ctx) {
|
|
|
1821
1877
|
severity: "error",
|
|
1822
1878
|
code: "state.unknown",
|
|
1823
1879
|
message: `unknown state "${stateName}"`,
|
|
1824
|
-
where: scope
|
|
1880
|
+
where: scope,
|
|
1881
|
+
id: comp.id
|
|
1825
1882
|
});
|
|
1826
1883
|
}
|
|
1827
1884
|
checkFacets(override?.facets, scope);
|
|
@@ -1899,13 +1956,16 @@ var PUNCT = {
|
|
|
1899
1956
|
"}": "rbrace",
|
|
1900
1957
|
"(": "lparen",
|
|
1901
1958
|
")": "rparen",
|
|
1959
|
+
"[": "lbracket",
|
|
1960
|
+
"]": "rbracket",
|
|
1902
1961
|
"<": "lt",
|
|
1903
1962
|
">": "gt",
|
|
1904
1963
|
":": "colon",
|
|
1905
1964
|
"=": "equals",
|
|
1906
1965
|
",": "comma",
|
|
1907
1966
|
";": "semi",
|
|
1908
|
-
"?": "question"
|
|
1967
|
+
"?": "question",
|
|
1968
|
+
"@": "at"
|
|
1909
1969
|
};
|
|
1910
1970
|
function lex(src) {
|
|
1911
1971
|
const tokens = [];
|
|
@@ -1976,6 +2036,21 @@ function lex(src) {
|
|
|
1976
2036
|
push("arrow", "->", startPos, startLine, startCol);
|
|
1977
2037
|
continue;
|
|
1978
2038
|
}
|
|
2039
|
+
if (c === "!" && src[i + 1] === "=") {
|
|
2040
|
+
advance(2);
|
|
2041
|
+
push("ne", "!=", startPos, startLine, startCol);
|
|
2042
|
+
continue;
|
|
2043
|
+
}
|
|
2044
|
+
if (c === "<" && src[i + 1] === "=") {
|
|
2045
|
+
advance(2);
|
|
2046
|
+
push("lte", "<=", startPos, startLine, startCol);
|
|
2047
|
+
continue;
|
|
2048
|
+
}
|
|
2049
|
+
if (c === ">" && src[i + 1] === "=") {
|
|
2050
|
+
advance(2);
|
|
2051
|
+
push("gte", ">=", startPos, startLine, startCol);
|
|
2052
|
+
continue;
|
|
2053
|
+
}
|
|
1979
2054
|
if (PUNCT[c]) {
|
|
1980
2055
|
advance();
|
|
1981
2056
|
push(PUNCT[c], c, startPos, startLine, startCol);
|
|
@@ -2003,10 +2078,24 @@ var ctxId = (groupName, ctxName) => `ctx.${seg(groupName)}.${seg(ctxName)}`;
|
|
|
2003
2078
|
var tokId = (path) => `tok.${path.replace(/\//g, ".")}`;
|
|
2004
2079
|
var stId = (name) => `st.${seg(name)}`;
|
|
2005
2080
|
var cmpId = (name) => `cmp.${seg(name)}`;
|
|
2081
|
+
function metaValueType(v) {
|
|
2082
|
+
if (typeof v === "number") return { kind: "number" };
|
|
2083
|
+
if (typeof v === "boolean") return { kind: "boolean" };
|
|
2084
|
+
if (Array.isArray(v)) return { kind: "array", item: { kind: "string" } };
|
|
2085
|
+
return { kind: "string" };
|
|
2086
|
+
}
|
|
2006
2087
|
var FACET_MAP = {
|
|
2007
2088
|
fill: "fill",
|
|
2008
2089
|
ink: "ink",
|
|
2009
2090
|
border: "border",
|
|
2091
|
+
"border.top": "border.top",
|
|
2092
|
+
"border.right": "border.right",
|
|
2093
|
+
"border.bottom": "border.bottom",
|
|
2094
|
+
"border.left": "border.left",
|
|
2095
|
+
"border.inline-start": "border.inline-start",
|
|
2096
|
+
"border.inline-end": "border.inline-end",
|
|
2097
|
+
"border.block-start": "border.block-start",
|
|
2098
|
+
"border.block-end": "border.block-end",
|
|
2010
2099
|
corner: "corner",
|
|
2011
2100
|
inset: "inset",
|
|
2012
2101
|
gap: "gap",
|
|
@@ -2018,9 +2107,9 @@ var FACET_MAP = {
|
|
|
2018
2107
|
opacity: "opacity",
|
|
2019
2108
|
ring: "ring",
|
|
2020
2109
|
backdrop: "backdrop",
|
|
2021
|
-
weight:
|
|
2022
|
-
font:
|
|
2023
|
-
slant:
|
|
2110
|
+
weight: "weight",
|
|
2111
|
+
font: "font",
|
|
2112
|
+
slant: "slant"
|
|
2024
2113
|
};
|
|
2025
2114
|
var TYPE_MAP = {
|
|
2026
2115
|
color: "color",
|
|
@@ -2032,6 +2121,8 @@ var TYPE_MAP = {
|
|
|
2032
2121
|
typography: "typography",
|
|
2033
2122
|
"font-family": "fontFamily",
|
|
2034
2123
|
fontFamily: "fontFamily",
|
|
2124
|
+
"font-weight": "fontWeight",
|
|
2125
|
+
fontWeight: "fontWeight",
|
|
2035
2126
|
duration: "duration",
|
|
2036
2127
|
opacity: "opacity",
|
|
2037
2128
|
blur: "blur",
|
|
@@ -2055,10 +2146,34 @@ function hexToColor(hex) {
|
|
|
2055
2146
|
if (h.length === 8) value.alpha = byte(3);
|
|
2056
2147
|
return value;
|
|
2057
2148
|
}
|
|
2149
|
+
var COLOR_FNS = /* @__PURE__ */ new Set(["hex", "srgb", "rgb", "p3", "oklch", "hsl"]);
|
|
2150
|
+
var UNIT_RE = /^(-?\d*\.?\d+)(px|rem|em|%|ms|s)$/;
|
|
2151
|
+
function unitToValue(n, unit) {
|
|
2152
|
+
switch (unit) {
|
|
2153
|
+
case "px":
|
|
2154
|
+
return { value: n, unit: "fixed" };
|
|
2155
|
+
case "em":
|
|
2156
|
+
return { value: n, unit: "em" };
|
|
2157
|
+
case "%":
|
|
2158
|
+
return { value: n, unit: "percent" };
|
|
2159
|
+
case "ms":
|
|
2160
|
+
return { ms: n };
|
|
2161
|
+
case "s":
|
|
2162
|
+
return { ms: n * 1e3 };
|
|
2163
|
+
default:
|
|
2164
|
+
return { value: n, unit: "scaled" };
|
|
2165
|
+
}
|
|
2166
|
+
}
|
|
2058
2167
|
var Parser = class {
|
|
2059
2168
|
constructor(toks) {
|
|
2060
2169
|
this.pos = 0;
|
|
2061
2170
|
this.issues = [];
|
|
2171
|
+
// Source spans for declared entities (see NodeSpan). Populated as declarations are
|
|
2172
|
+
// parsed; the AST itself never gains position fields.
|
|
2173
|
+
this.spans = [];
|
|
2174
|
+
// The component currently being parsed, so nested part/prop/event spans can point
|
|
2175
|
+
// back at it via `parentId`.
|
|
2176
|
+
this.currentComponentId = null;
|
|
2062
2177
|
// Tokens synthesized while parsing (inline literals hoisted out of alias slots —
|
|
2063
2178
|
// the canonical AST's `refs` holds only token ids, so an inline value must become
|
|
2064
2179
|
// its own token). Deduped by (type, serialized value). Flushed into the layer.
|
|
@@ -2078,6 +2193,73 @@ var Parser = class {
|
|
|
2078
2193
|
this.issue("warning", "inline-literal", `inline ${astType} literal in an alias slot hoisted to token ${id}`, tok);
|
|
2079
2194
|
return id;
|
|
2080
2195
|
}
|
|
2196
|
+
// Hoist an already-typed token value (an inline literal in an alias slot) into a
|
|
2197
|
+
// synthesized token and return its id.
|
|
2198
|
+
hoistValue(astType, value, tok) {
|
|
2199
|
+
const key = `${astType} ${JSON.stringify(value)}`;
|
|
2200
|
+
const existing = this.synthKeys.get(key);
|
|
2201
|
+
if (existing) return existing;
|
|
2202
|
+
const id = `tok.lit-${seg(String(astType))}-${this.synth.length + 1}`;
|
|
2203
|
+
this.synth.push({ id, name: `(inline ${astType})`, type: astType, value });
|
|
2204
|
+
this.synthKeys.set(key, id);
|
|
2205
|
+
this.issue("warning", "inline-literal", `inline ${astType} literal in an alias slot hoisted to token ${id}`, tok);
|
|
2206
|
+
return id;
|
|
2207
|
+
}
|
|
2208
|
+
// A self-typing value literal: a color-space function (`oklch(…)`, `hex(rrggbb)`, …)
|
|
2209
|
+
// or a unit-suffixed number (`8px`, `150ms`). Returns a raw TokenValue, or null if the
|
|
2210
|
+
// cursor isn't at such a literal (then it consumes nothing).
|
|
2211
|
+
tryParseLiteralValue() {
|
|
2212
|
+
const t = this.peek();
|
|
2213
|
+
if (t.type !== "word") return null;
|
|
2214
|
+
if (COLOR_FNS.has(t.value) && this.toks[this.pos + 1]?.type === "lparen") {
|
|
2215
|
+
return { kind: "raw", value: this.parseColorFn(t.value) };
|
|
2216
|
+
}
|
|
2217
|
+
const m = UNIT_RE.exec(t.value);
|
|
2218
|
+
if (m) {
|
|
2219
|
+
this.next();
|
|
2220
|
+
return { kind: "raw", value: unitToValue(Number(m[1]), m[2]) };
|
|
2221
|
+
}
|
|
2222
|
+
return null;
|
|
2223
|
+
}
|
|
2224
|
+
// `fn( a b c [alpha] )` — space-separated numeric channels (hex takes raw digits).
|
|
2225
|
+
parseColorFn(fn) {
|
|
2226
|
+
this.next();
|
|
2227
|
+
this.expect("lparen", "color-lparen");
|
|
2228
|
+
const nums = [];
|
|
2229
|
+
let hex = "";
|
|
2230
|
+
while (!this.at("rparen") && !this.at("eof")) {
|
|
2231
|
+
const w = this.next().value;
|
|
2232
|
+
if (fn === "hex") hex += w;
|
|
2233
|
+
else {
|
|
2234
|
+
const n = Number(w);
|
|
2235
|
+
if (!Number.isNaN(n)) nums.push(n);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
this.expect("rparen", "color-rparen");
|
|
2239
|
+
if (fn === "hex") {
|
|
2240
|
+
return hexToColor(hex.startsWith("#") ? hex : `#${hex}`) ?? { space: "srgb", channels: [0, 0, 0] };
|
|
2241
|
+
}
|
|
2242
|
+
const c = (i) => nums[i] ?? 0;
|
|
2243
|
+
let cv;
|
|
2244
|
+
if (fn === "rgb") cv = { space: "srgb", channels: [c(0) / 255, c(1) / 255, c(2) / 255] };
|
|
2245
|
+
else if (fn === "p3") cv = { space: "display-p3", channels: [c(0), c(1), c(2)] };
|
|
2246
|
+
else cv = { space: fn, channels: [c(0), c(1), c(2)] };
|
|
2247
|
+
if (nums.length >= 4) cv.alpha = nums[3];
|
|
2248
|
+
return cv;
|
|
2249
|
+
}
|
|
2250
|
+
// Self-typing literals let us check the value's inherent kind against the token's
|
|
2251
|
+
// declared type (e.g. `token x : color = 8px` is a dimension in a color slot).
|
|
2252
|
+
checkLiteralType(astType, lit, tok) {
|
|
2253
|
+
if (lit.kind !== "raw") return;
|
|
2254
|
+
const v = lit.value;
|
|
2255
|
+
if (typeof v !== "object" || v === null) return;
|
|
2256
|
+
const cat = "space" in v ? "color" : "ms" in v ? "duration" : "unit" in v ? "dimension" : null;
|
|
2257
|
+
if (!cat) return;
|
|
2258
|
+
const expected = astType === "color" ? "color" : astType === "duration" ? "duration" : astType === "spacing" || astType === "radius" || astType === "size" ? "dimension" : null;
|
|
2259
|
+
if (expected && cat !== expected) {
|
|
2260
|
+
this.issue("error", "value-type", `${cat} literal in a "${astType}" token`, tok);
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2081
2263
|
peek() {
|
|
2082
2264
|
return this.toks[this.pos];
|
|
2083
2265
|
}
|
|
@@ -2103,6 +2285,28 @@ var Parser = class {
|
|
|
2103
2285
|
if (!this.at("eof")) this.next();
|
|
2104
2286
|
return true;
|
|
2105
2287
|
}
|
|
2288
|
+
// ─── span recording (source positions for tooling) ─────────────────────────
|
|
2289
|
+
recordSpan(span) {
|
|
2290
|
+
this.spans.push(span);
|
|
2291
|
+
}
|
|
2292
|
+
startPos(tok) {
|
|
2293
|
+
return { line: tok.line, col: tok.col };
|
|
2294
|
+
}
|
|
2295
|
+
// End position of a token = its start advanced past its source text. Strings add 2
|
|
2296
|
+
// for the surrounding quotes (escape sequences make this a slight over-estimate,
|
|
2297
|
+
// affecting only a rarely-used string span — never an identifier or brace).
|
|
2298
|
+
endPos(tok) {
|
|
2299
|
+
const len = tok.type === "newline" || tok.type === "eof" ? 0 : tok.type === "string" ? tok.value.length + 2 : tok.value.length;
|
|
2300
|
+
return { line: tok.line, col: tok.col + len };
|
|
2301
|
+
}
|
|
2302
|
+
rangeOf(start, end) {
|
|
2303
|
+
return { start: this.startPos(start), end: this.endPos(end) };
|
|
2304
|
+
}
|
|
2305
|
+
// The most-recently-consumed token — closes a span that has no trailing brace
|
|
2306
|
+
// (a single-line `token`/`type` declaration ends at its value).
|
|
2307
|
+
lastTok() {
|
|
2308
|
+
return this.toks[Math.max(0, this.pos - 1)];
|
|
2309
|
+
}
|
|
2106
2310
|
issue(severity, code, message, tok = this.peek()) {
|
|
2107
2311
|
this.issues.push({ severity, code, message, line: tok.line, col: tok.col });
|
|
2108
2312
|
}
|
|
@@ -2133,7 +2337,8 @@ var Parser = class {
|
|
|
2133
2337
|
}
|
|
2134
2338
|
// ── context-group NAME { ctx ctx … } ──
|
|
2135
2339
|
parseContextGroup() {
|
|
2136
|
-
this.next();
|
|
2340
|
+
const kw = this.next();
|
|
2341
|
+
const nameTok = this.peek();
|
|
2137
2342
|
const name = this.expectWord("cg-name");
|
|
2138
2343
|
this.expect("lbrace", "cg-lbrace");
|
|
2139
2344
|
const contexts = [];
|
|
@@ -2142,11 +2347,27 @@ var Parser = class {
|
|
|
2142
2347
|
this.pos++;
|
|
2143
2348
|
continue;
|
|
2144
2349
|
}
|
|
2350
|
+
const ctxTok = this.peek();
|
|
2145
2351
|
const ctxName = this.expectWord("cg-ctx");
|
|
2146
2352
|
if (!ctxName) break;
|
|
2147
2353
|
contexts.push({ id: ctxId(name, ctxName), name: ctxName });
|
|
2354
|
+
this.recordSpan({
|
|
2355
|
+
id: ctxId(name, ctxName),
|
|
2356
|
+
kind: "context",
|
|
2357
|
+
name: ctxName,
|
|
2358
|
+
nameRange: this.rangeOf(ctxTok, ctxTok),
|
|
2359
|
+
fullRange: this.rangeOf(ctxTok, ctxTok),
|
|
2360
|
+
parentId: cgId(name)
|
|
2361
|
+
});
|
|
2148
2362
|
}
|
|
2149
|
-
this.expect("rbrace", "cg-rbrace");
|
|
2363
|
+
const close = this.expect("rbrace", "cg-rbrace");
|
|
2364
|
+
this.recordSpan({
|
|
2365
|
+
id: cgId(name),
|
|
2366
|
+
kind: "contextGroup",
|
|
2367
|
+
name,
|
|
2368
|
+
nameRange: this.rangeOf(nameTok, nameTok),
|
|
2369
|
+
fullRange: this.rangeOf(kw, close)
|
|
2370
|
+
});
|
|
2150
2371
|
return { id: cgId(name), name, contexts };
|
|
2151
2372
|
}
|
|
2152
2373
|
// ── raw value literal → a JS value (string | number | nested object) ──
|
|
@@ -2155,6 +2376,12 @@ var Parser = class {
|
|
|
2155
2376
|
if (t.type === "string") return this.next().value;
|
|
2156
2377
|
if (t.type === "lbrace") return this.parseStructured();
|
|
2157
2378
|
if (t.type === "word") {
|
|
2379
|
+
if (COLOR_FNS.has(t.value) && this.toks[this.pos + 1]?.type === "lparen") return this.parseColorFn(t.value);
|
|
2380
|
+
const m = UNIT_RE.exec(t.value);
|
|
2381
|
+
if (m) {
|
|
2382
|
+
this.next();
|
|
2383
|
+
return unitToValue(Number(m[1]), m[2]);
|
|
2384
|
+
}
|
|
2158
2385
|
const w = this.next().value;
|
|
2159
2386
|
return NUMBER_RE.test(w) ? Number(w) : w;
|
|
2160
2387
|
}
|
|
@@ -2196,6 +2423,7 @@ var Parser = class {
|
|
|
2196
2423
|
}
|
|
2197
2424
|
}
|
|
2198
2425
|
if (astType === "opacity" && typeof raw === "number") return { kind: "raw", value: { value: raw } };
|
|
2426
|
+
if (astType === "fontWeight" && typeof raw === "number") return { kind: "raw", value: { weight: raw } };
|
|
2199
2427
|
if (astType === "duration" && typeof raw === "number") return { kind: "raw", value: { ms: raw } };
|
|
2200
2428
|
if (astType === "tonal" && typeof raw === "number") return { kind: "raw", value: { level: raw } };
|
|
2201
2429
|
return { kind: "raw", value: raw };
|
|
@@ -2221,7 +2449,11 @@ var Parser = class {
|
|
|
2221
2449
|
this.expect("arrow", "alias-arrow");
|
|
2222
2450
|
const slot = this.peek();
|
|
2223
2451
|
let refId = "";
|
|
2224
|
-
|
|
2452
|
+
const lit = this.tryParseLiteralValue();
|
|
2453
|
+
if (lit) {
|
|
2454
|
+
this.checkLiteralType(astType, lit, slot);
|
|
2455
|
+
refId = this.hoistValue(astType, lit, slot);
|
|
2456
|
+
} else if (slot.type === "string" || slot.type === "lbrace" || slot.type === "word" && NUMBER_RE.test(slot.value)) {
|
|
2225
2457
|
refId = this.hoistLiteral(astType, this.parseRawLiteral(), slot);
|
|
2226
2458
|
} else {
|
|
2227
2459
|
const refPath = this.expectWord("alias-ref");
|
|
@@ -2263,6 +2495,15 @@ var Parser = class {
|
|
|
2263
2495
|
return this.typedRaw(astType, raw, t);
|
|
2264
2496
|
}
|
|
2265
2497
|
if (t.type === "word") {
|
|
2498
|
+
const lit = this.tryParseLiteralValue();
|
|
2499
|
+
if (lit) {
|
|
2500
|
+
this.checkLiteralType(astType, lit, t);
|
|
2501
|
+
return lit;
|
|
2502
|
+
}
|
|
2503
|
+
if (NUMBER_RE.test(t.value)) {
|
|
2504
|
+
this.next();
|
|
2505
|
+
return this.typedRaw(astType, Number(t.value), t);
|
|
2506
|
+
}
|
|
2266
2507
|
const refPath = this.next().value;
|
|
2267
2508
|
this.issue("warning", "ref-sugar", `unconditional ref "${refPath}" lowered to a default-only alias`, t);
|
|
2268
2509
|
return { kind: "alias", by: "", refs: { default: tokId(refPath) } };
|
|
@@ -2302,6 +2543,7 @@ var Parser = class {
|
|
|
2302
2543
|
}
|
|
2303
2544
|
parseTokenDecl(_order) {
|
|
2304
2545
|
const startTok = this.next();
|
|
2546
|
+
const nameTok = this.peek();
|
|
2305
2547
|
const path = this.expectWord("token-path");
|
|
2306
2548
|
if (!path) return null;
|
|
2307
2549
|
this.expect("colon", "token-colon");
|
|
@@ -2310,6 +2552,13 @@ var Parser = class {
|
|
|
2310
2552
|
if (!astType) this.issue("warning", "token-type", `non-canonical token type "${typeName}" kept as-is`, startTok);
|
|
2311
2553
|
this.expect("equals", "token-equals");
|
|
2312
2554
|
const value = this.parseTokenValue(astType ?? typeName);
|
|
2555
|
+
this.recordSpan({
|
|
2556
|
+
id: tokId(path),
|
|
2557
|
+
kind: "token",
|
|
2558
|
+
name: path,
|
|
2559
|
+
nameRange: this.rangeOf(nameTok, nameTok),
|
|
2560
|
+
fullRange: this.rangeOf(startTok, this.lastTok())
|
|
2561
|
+
});
|
|
2313
2562
|
return {
|
|
2314
2563
|
id: tokId(path),
|
|
2315
2564
|
name: path,
|
|
@@ -2421,11 +2670,19 @@ var Parser = class {
|
|
|
2421
2670
|
return { kind: "ref", ref: stId(w) };
|
|
2422
2671
|
}
|
|
2423
2672
|
parseSharedType() {
|
|
2424
|
-
this.next();
|
|
2673
|
+
const kw = this.next();
|
|
2674
|
+
const nameTok = this.peek();
|
|
2425
2675
|
const name = this.expectWord("type-name");
|
|
2426
2676
|
if (!name) return null;
|
|
2427
2677
|
this.expect("equals", "type-equals");
|
|
2428
2678
|
const definition = this.parseTypeExpr();
|
|
2679
|
+
this.recordSpan({
|
|
2680
|
+
id: stId(name),
|
|
2681
|
+
kind: "sharedType",
|
|
2682
|
+
name,
|
|
2683
|
+
nameRange: this.rangeOf(nameTok, nameTok),
|
|
2684
|
+
fullRange: this.rangeOf(kw, this.lastTok())
|
|
2685
|
+
});
|
|
2429
2686
|
return { id: stId(name), name, definition };
|
|
2430
2687
|
}
|
|
2431
2688
|
// ════════════════════════════════════════════════════════════════════════
|
|
@@ -2455,6 +2712,125 @@ var Parser = class {
|
|
|
2455
2712
|
}
|
|
2456
2713
|
return { prop, eq };
|
|
2457
2714
|
}
|
|
2715
|
+
// ── Predicate expression parser (proposal 0032) ─────────────────────────────
|
|
2716
|
+
// Recursive-descent over the surface predicate grammar emitted by @mildastudio/
|
|
2717
|
+
// generate's `predToDsl` (targets/dsl/emit.ts). Precedence, lowest → highest:
|
|
2718
|
+
// or → and → unary(`not`) → primary(`( … )` | leaf)
|
|
2719
|
+
// A leaf names a source (`state s` / `context g` / `ancestor id s` / a bare prop path)
|
|
2720
|
+
// then an optional operator + value: a bare leaf is `set`, `not <leaf>` is `unset`, and
|
|
2721
|
+
// a parenthesized `not ( … )` negates a whole group. Values are bare idents/numbers/
|
|
2722
|
+
// bools or quoted strings (mirroring the emitter's `predValue`), coerced to a JS type.
|
|
2723
|
+
parsePredicate() {
|
|
2724
|
+
return this.parsePredOr();
|
|
2725
|
+
}
|
|
2726
|
+
parsePredOr() {
|
|
2727
|
+
const first = this.parsePredAnd();
|
|
2728
|
+
if (!this.atWord("or")) return first;
|
|
2729
|
+
const items = [first];
|
|
2730
|
+
while (this.atWord("or")) {
|
|
2731
|
+
this.next();
|
|
2732
|
+
items.push(this.parsePredAnd());
|
|
2733
|
+
}
|
|
2734
|
+
return { kind: "any", items };
|
|
2735
|
+
}
|
|
2736
|
+
parsePredAnd() {
|
|
2737
|
+
const first = this.parsePredUnary();
|
|
2738
|
+
if (!this.atWord("and")) return first;
|
|
2739
|
+
const items = [first];
|
|
2740
|
+
while (this.atWord("and")) {
|
|
2741
|
+
this.next();
|
|
2742
|
+
items.push(this.parsePredUnary());
|
|
2743
|
+
}
|
|
2744
|
+
return { kind: "all", items };
|
|
2745
|
+
}
|
|
2746
|
+
parsePredUnary() {
|
|
2747
|
+
if (this.atWord("not")) {
|
|
2748
|
+
this.next();
|
|
2749
|
+
if (this.at("lparen")) {
|
|
2750
|
+
this.next();
|
|
2751
|
+
const item = this.parsePredOr();
|
|
2752
|
+
this.expect("rparen", "pred-not-rparen");
|
|
2753
|
+
return { kind: "not", item };
|
|
2754
|
+
}
|
|
2755
|
+
return { kind: "cmp", ref: this.parsePredRef(), op: "unset" };
|
|
2756
|
+
}
|
|
2757
|
+
return this.parsePredPrimary();
|
|
2758
|
+
}
|
|
2759
|
+
parsePredPrimary() {
|
|
2760
|
+
if (this.at("lparen")) {
|
|
2761
|
+
this.next();
|
|
2762
|
+
const inner = this.parsePredOr();
|
|
2763
|
+
this.expect("rparen", "pred-rparen");
|
|
2764
|
+
return inner;
|
|
2765
|
+
}
|
|
2766
|
+
return this.parsePredLeaf();
|
|
2767
|
+
}
|
|
2768
|
+
// A comparison leaf: a source, then an optional operator + value. No operator ⇒ `set`.
|
|
2769
|
+
parsePredLeaf() {
|
|
2770
|
+
const ref = this.parsePredRef();
|
|
2771
|
+
const op = this.predOp();
|
|
2772
|
+
if (!op) return { kind: "cmp", ref, op: "set" };
|
|
2773
|
+
return { kind: "cmp", ref, op, value: this.parsePredValue() };
|
|
2774
|
+
}
|
|
2775
|
+
// The source of a leaf: `state <s>` / `context <g>` / `ancestor <id> <s>` / bare prop path.
|
|
2776
|
+
parsePredRef() {
|
|
2777
|
+
if (this.atWord("state")) {
|
|
2778
|
+
this.next();
|
|
2779
|
+
return { kind: "state", state: this.expectWord("pred-state") };
|
|
2780
|
+
}
|
|
2781
|
+
if (this.atWord("context")) {
|
|
2782
|
+
this.next();
|
|
2783
|
+
return { kind: "context", group: this.expectWord("pred-context") };
|
|
2784
|
+
}
|
|
2785
|
+
if (this.atWord("ancestor")) {
|
|
2786
|
+
this.next();
|
|
2787
|
+
const nodeId = this.expectWord("pred-ancestor-id");
|
|
2788
|
+
const state = this.expectWord("pred-ancestor-state");
|
|
2789
|
+
return { kind: "ancestor", nodeId, state };
|
|
2790
|
+
}
|
|
2791
|
+
return { kind: "prop", path: this.expectWord("pred-prop") };
|
|
2792
|
+
}
|
|
2793
|
+
// A comparison operator token → CompareOp, or null when the next token isn't one.
|
|
2794
|
+
predOp() {
|
|
2795
|
+
switch (this.peek().type) {
|
|
2796
|
+
case "equals":
|
|
2797
|
+
this.next();
|
|
2798
|
+
return "eq";
|
|
2799
|
+
case "ne":
|
|
2800
|
+
this.next();
|
|
2801
|
+
return "ne";
|
|
2802
|
+
case "lt":
|
|
2803
|
+
this.next();
|
|
2804
|
+
return "lt";
|
|
2805
|
+
case "lte":
|
|
2806
|
+
this.next();
|
|
2807
|
+
return "lte";
|
|
2808
|
+
case "gt":
|
|
2809
|
+
this.next();
|
|
2810
|
+
return "gt";
|
|
2811
|
+
case "gte":
|
|
2812
|
+
this.next();
|
|
2813
|
+
return "gte";
|
|
2814
|
+
default:
|
|
2815
|
+
if (this.atWord("contains")) {
|
|
2816
|
+
this.next();
|
|
2817
|
+
return "contains";
|
|
2818
|
+
}
|
|
2819
|
+
return null;
|
|
2820
|
+
}
|
|
2821
|
+
}
|
|
2822
|
+
// A leaf value: a quoted string, or a bare word coerced to number / boolean / string
|
|
2823
|
+
// (mirrors `parseRealizeCond` and the emitter's bare-vs-quoted `predValue`).
|
|
2824
|
+
parsePredValue() {
|
|
2825
|
+
const t = this.peek();
|
|
2826
|
+
if (t.type === "string") return this.next().value;
|
|
2827
|
+
if (t.type === "word") {
|
|
2828
|
+
const w = this.next().value;
|
|
2829
|
+
return NUMBER_RE.test(w) ? Number(w) : w === "true" ? true : w === "false" ? false : w;
|
|
2830
|
+
}
|
|
2831
|
+
this.issue("error", "pred-value", `expected a comparison value, got ${t.type}`);
|
|
2832
|
+
return "";
|
|
2833
|
+
}
|
|
2458
2834
|
parsePropDefault() {
|
|
2459
2835
|
const t = this.peek();
|
|
2460
2836
|
if (t.type === "string") return this.next().value;
|
|
@@ -2477,6 +2853,7 @@ var Parser = class {
|
|
|
2477
2853
|
return void 0;
|
|
2478
2854
|
}
|
|
2479
2855
|
parsePropDecl() {
|
|
2856
|
+
const nameTok = this.peek();
|
|
2480
2857
|
const name = this.expectWord("prop-name");
|
|
2481
2858
|
if (!name) return null;
|
|
2482
2859
|
const type = this.parseTypeExpr();
|
|
@@ -2490,6 +2867,28 @@ var Parser = class {
|
|
|
2490
2867
|
this.next();
|
|
2491
2868
|
prop.binding = this.expectWord("bind-target");
|
|
2492
2869
|
}
|
|
2870
|
+
const meta = {};
|
|
2871
|
+
while (this.at("at")) {
|
|
2872
|
+
this.next();
|
|
2873
|
+
const key = this.expectWord("annot-key");
|
|
2874
|
+
const t = this.peek();
|
|
2875
|
+
let value = true;
|
|
2876
|
+
if (t.type === "string") value = this.next().value;
|
|
2877
|
+
else if (t.type === "word") {
|
|
2878
|
+
const w = this.next().value;
|
|
2879
|
+
value = NUMBER_RE.test(w) ? Number(w) : w === "true" ? true : w === "false" ? false : w;
|
|
2880
|
+
}
|
|
2881
|
+
if (key) meta[key] = value;
|
|
2882
|
+
}
|
|
2883
|
+
if (Object.keys(meta).length) prop.meta = meta;
|
|
2884
|
+
this.recordSpan({
|
|
2885
|
+
id: `${this.currentComponentId ?? ""}#prop.${seg(name)}`,
|
|
2886
|
+
kind: "prop",
|
|
2887
|
+
name,
|
|
2888
|
+
nameRange: this.rangeOf(nameTok, nameTok),
|
|
2889
|
+
fullRange: this.rangeOf(nameTok, this.lastTok()),
|
|
2890
|
+
parentId: this.currentComponentId ?? void 0
|
|
2891
|
+
});
|
|
2493
2892
|
return prop;
|
|
2494
2893
|
}
|
|
2495
2894
|
parseProps(props) {
|
|
@@ -2517,6 +2916,7 @@ var Parser = class {
|
|
|
2517
2916
|
this.pos++;
|
|
2518
2917
|
continue;
|
|
2519
2918
|
}
|
|
2919
|
+
const nameTok = this.peek();
|
|
2520
2920
|
const name = this.expectWord("event-name");
|
|
2521
2921
|
if (name) {
|
|
2522
2922
|
const e = { name };
|
|
@@ -2525,6 +2925,14 @@ var Parser = class {
|
|
|
2525
2925
|
e.from = this.expectWord("event-from");
|
|
2526
2926
|
}
|
|
2527
2927
|
events.push(e);
|
|
2928
|
+
this.recordSpan({
|
|
2929
|
+
id: `${this.currentComponentId ?? ""}#event.${seg(name)}`,
|
|
2930
|
+
kind: "event",
|
|
2931
|
+
name,
|
|
2932
|
+
nameRange: this.rangeOf(nameTok, nameTok),
|
|
2933
|
+
fullRange: this.rangeOf(nameTok, this.lastTok()),
|
|
2934
|
+
parentId: this.currentComponentId ?? void 0
|
|
2935
|
+
});
|
|
2528
2936
|
}
|
|
2529
2937
|
if (this.at("semi") || this.at("newline")) this.pos++;
|
|
2530
2938
|
this.ensureProgress(mark);
|
|
@@ -2697,6 +3105,16 @@ var Parser = class {
|
|
|
2697
3105
|
if (mapped === "inset") {
|
|
2698
3106
|
return { name: "inset", value: { inset: this.parseInsetArgs() } };
|
|
2699
3107
|
}
|
|
3108
|
+
if (mapped === "border" || mapped.startsWith("border.")) {
|
|
3109
|
+
const border = {};
|
|
3110
|
+
while (this.at("word")) {
|
|
3111
|
+
const w = this.next().value;
|
|
3112
|
+
if (w === "solid" || w === "dashed" || w === "dotted") border.style = w;
|
|
3113
|
+
else if (w.includes(".")) border.color = tokId(w);
|
|
3114
|
+
else border.width = tokId(w);
|
|
3115
|
+
}
|
|
3116
|
+
return { name: mapped, value: { border } };
|
|
3117
|
+
}
|
|
2700
3118
|
const t = this.peek();
|
|
2701
3119
|
let value;
|
|
2702
3120
|
if (t.type === "string") {
|
|
@@ -2705,6 +3123,7 @@ var Parser = class {
|
|
|
2705
3123
|
const v = this.next().value;
|
|
2706
3124
|
if (NUMBER_RE.test(v)) value = { literal: Number(v) };
|
|
2707
3125
|
else if (isRawValue(v)) value = { raw: v };
|
|
3126
|
+
else if (!v.includes(".")) value = { raw: v };
|
|
2708
3127
|
else value = { token: tokId(v) };
|
|
2709
3128
|
} else {
|
|
2710
3129
|
this.issue("error", "facet-value", `expected a facet value, got ${t.type}`);
|
|
@@ -2726,11 +3145,13 @@ var Parser = class {
|
|
|
2726
3145
|
this.applyStyleBody(variant);
|
|
2727
3146
|
return variant;
|
|
2728
3147
|
}
|
|
2729
|
-
// `when
|
|
3148
|
+
// `when <predicate> { … }` → a state-less predicate-conditioned PartVariant. The rich
|
|
3149
|
+
// predicate grammar (proposal 0032) supersedes the flat single-condition form: it carries
|
|
3150
|
+
// OR / NOT / operators / state·context·ancestor leaves. A plain `when prop = value` parses
|
|
3151
|
+
// to the degenerate single-`eq` predicate, so the legacy one-condition case still works.
|
|
2730
3152
|
parseWhenBlock() {
|
|
2731
3153
|
this.next();
|
|
2732
|
-
const
|
|
2733
|
-
const variant = { when };
|
|
3154
|
+
const variant = { whenPredicate: this.parsePredicate() };
|
|
2734
3155
|
this.applyStyleBody(variant);
|
|
2735
3156
|
return variant;
|
|
2736
3157
|
}
|
|
@@ -2757,9 +3178,26 @@ var Parser = class {
|
|
|
2757
3178
|
this.expect("rbrace", "style-rbrace");
|
|
2758
3179
|
if (Object.keys(facets2).length) target.facets = facets2;
|
|
2759
3180
|
}
|
|
3181
|
+
// `slot [single|optional|many] [of content | of TYPE]` — a content slot on a part.
|
|
3182
|
+
// Bare `slot` defaults to a single content slot.
|
|
3183
|
+
parseSlot() {
|
|
3184
|
+
let arity = "single";
|
|
3185
|
+
if (this.atWord("single") || this.atWord("optional") || this.atWord("many")) {
|
|
3186
|
+
arity = this.next().value;
|
|
3187
|
+
}
|
|
3188
|
+
let accepts = { kind: "content" };
|
|
3189
|
+
if (this.atWord("of")) {
|
|
3190
|
+
this.next();
|
|
3191
|
+
const w = this.expectWord("slot-accepts");
|
|
3192
|
+
if (w && w !== "content") accepts = { kind: "type", ref: w };
|
|
3193
|
+
}
|
|
3194
|
+
this.skipToTerminator();
|
|
3195
|
+
return { arity, accepts };
|
|
3196
|
+
}
|
|
2760
3197
|
// `part NAME { pstmt* }` → [name, Part]
|
|
2761
3198
|
parsePart() {
|
|
2762
|
-
this.next();
|
|
3199
|
+
const kw = this.next();
|
|
3200
|
+
const nameTok = this.peek();
|
|
2763
3201
|
const name = this.expectWord("part-name");
|
|
2764
3202
|
if (!name) return null;
|
|
2765
3203
|
this.expect("lbrace", "part-lbrace");
|
|
@@ -2781,13 +3219,18 @@ var Parser = class {
|
|
|
2781
3219
|
} else {
|
|
2782
3220
|
part.layout = this.parseLayoutBody();
|
|
2783
3221
|
}
|
|
3222
|
+
} else if (this.atWord("present")) {
|
|
3223
|
+
this.next();
|
|
3224
|
+
if (this.atWord("when")) this.next();
|
|
3225
|
+
else this.issue("warning", "present-when", "expected 'when' after 'present'");
|
|
3226
|
+
part.presence = this.parsePredicate();
|
|
2784
3227
|
} else if (this.atWord("state")) {
|
|
2785
3228
|
variants.push(this.parseStateBlock());
|
|
2786
3229
|
} else if (this.atWord("when")) {
|
|
2787
3230
|
variants.push(this.parseWhenBlock());
|
|
2788
3231
|
} else if (this.atWord("slot")) {
|
|
2789
|
-
this.
|
|
2790
|
-
this.
|
|
3232
|
+
this.next();
|
|
3233
|
+
part.slot = this.parseSlot();
|
|
2791
3234
|
} else {
|
|
2792
3235
|
const f = this.parseFacetStmt();
|
|
2793
3236
|
if (f) facets2[f.name] = f.value;
|
|
@@ -2795,9 +3238,17 @@ var Parser = class {
|
|
|
2795
3238
|
if (this.at("semi") || this.at("newline")) this.pos++;
|
|
2796
3239
|
this.ensureProgress(mark);
|
|
2797
3240
|
}
|
|
2798
|
-
this.expect("rbrace", "part-rbrace");
|
|
3241
|
+
const close = this.expect("rbrace", "part-rbrace");
|
|
2799
3242
|
if (Object.keys(facets2).length) part.facets = facets2;
|
|
2800
3243
|
if (variants.length) part.variants = variants;
|
|
3244
|
+
this.recordSpan({
|
|
3245
|
+
id: `${this.currentComponentId ?? ""}#part.${seg(name)}`,
|
|
3246
|
+
kind: "part",
|
|
3247
|
+
name,
|
|
3248
|
+
nameRange: this.rangeOf(nameTok, nameTok),
|
|
3249
|
+
fullRange: this.rangeOf(kw, close),
|
|
3250
|
+
parentId: this.currentComponentId ?? void 0
|
|
3251
|
+
});
|
|
2801
3252
|
return [name, part];
|
|
2802
3253
|
}
|
|
2803
3254
|
// `realize TARGET [when COND] { rstmt* }`
|
|
@@ -2841,6 +3292,14 @@ var Parser = class {
|
|
|
2841
3292
|
} else if (this.atWord("note")) {
|
|
2842
3293
|
this.next();
|
|
2843
3294
|
r.note = this.at("string") ? this.next().value : this.expectWord("note");
|
|
3295
|
+
} else if (this.atWord("dismiss")) {
|
|
3296
|
+
this.next();
|
|
3297
|
+
const words = this.parseIdentListWords();
|
|
3298
|
+
r.dismiss = words.length === 1 && words[0] === "none" ? [] : words;
|
|
3299
|
+
} else if (this.atWord("backdrop")) {
|
|
3300
|
+
this.next();
|
|
3301
|
+
r.backdrop = this.at("word") ? this.next().value : void 0;
|
|
3302
|
+
this.skipToTerminator();
|
|
2844
3303
|
} else if (this.atWord("wire")) {
|
|
2845
3304
|
const w = this.parseWire();
|
|
2846
3305
|
if (w && w.kind === "wire") wires.push({ from: w.from, to: w.to, ...w.arg ? { arg: w.arg } : {} });
|
|
@@ -2882,6 +3341,21 @@ var Parser = class {
|
|
|
2882
3341
|
this.next();
|
|
2883
3342
|
commit.on = this.parseIdentListWords();
|
|
2884
3343
|
}
|
|
3344
|
+
if (this.at("lbrace")) {
|
|
3345
|
+
this.next();
|
|
3346
|
+
while (!this.at("rbrace") && !this.at("eof")) {
|
|
3347
|
+
if (this.at("newline") || this.at("semi")) {
|
|
3348
|
+
this.pos++;
|
|
3349
|
+
continue;
|
|
3350
|
+
}
|
|
3351
|
+
const key = this.expectWord("commit-key");
|
|
3352
|
+
const val = this.at("word") ? this.next().value : void 0;
|
|
3353
|
+
if (key === "live" && val) commit.live = val;
|
|
3354
|
+
else if (key === "settled" && val) commit.settled = val;
|
|
3355
|
+
else this.skipToTerminator();
|
|
3356
|
+
}
|
|
3357
|
+
this.expect("rbrace", "commit-rbrace");
|
|
3358
|
+
}
|
|
2885
3359
|
return commit;
|
|
2886
3360
|
}
|
|
2887
3361
|
// A run of bare words up to a terminator (for `drop a b c`, `commit … on click enter`).
|
|
@@ -2891,13 +3365,18 @@ var Parser = class {
|
|
|
2891
3365
|
return out;
|
|
2892
3366
|
}
|
|
2893
3367
|
parseComponent() {
|
|
2894
|
-
this.next();
|
|
3368
|
+
const kw = this.next();
|
|
3369
|
+
const nameTok = this.peek();
|
|
2895
3370
|
const name = this.expectWord("component-name");
|
|
2896
3371
|
if (!name) return null;
|
|
3372
|
+
const componentId = cmpId(name);
|
|
3373
|
+
const prevComponentId = this.currentComponentId;
|
|
3374
|
+
this.currentComponentId = componentId;
|
|
2897
3375
|
let description;
|
|
2898
3376
|
if (this.at("string")) description = this.next().value;
|
|
2899
3377
|
this.expect("lbrace", "component-lbrace");
|
|
2900
3378
|
let archetype = "";
|
|
3379
|
+
let meta;
|
|
2901
3380
|
const props = [];
|
|
2902
3381
|
const events = [];
|
|
2903
3382
|
const uses = [];
|
|
@@ -2913,6 +3392,8 @@ var Parser = class {
|
|
|
2913
3392
|
if (this.atWord("archetype")) {
|
|
2914
3393
|
this.next();
|
|
2915
3394
|
archetype = this.expectWord("archetype");
|
|
3395
|
+
} else if (this.atWord("meta")) {
|
|
3396
|
+
meta = this.parseMetaBlock();
|
|
2916
3397
|
} else if (this.atWord("props")) {
|
|
2917
3398
|
this.parseProps(props);
|
|
2918
3399
|
} else if (this.atWord("events")) {
|
|
@@ -2946,9 +3427,17 @@ var Parser = class {
|
|
|
2946
3427
|
}
|
|
2947
3428
|
this.ensureProgress(mark);
|
|
2948
3429
|
}
|
|
2949
|
-
this.expect("rbrace", "component-rbrace");
|
|
3430
|
+
const close = this.expect("rbrace", "component-rbrace");
|
|
3431
|
+
this.currentComponentId = prevComponentId;
|
|
3432
|
+
this.recordSpan({
|
|
3433
|
+
id: componentId,
|
|
3434
|
+
kind: "component",
|
|
3435
|
+
name,
|
|
3436
|
+
nameRange: this.rangeOf(nameTok, nameTok),
|
|
3437
|
+
fullRange: this.rangeOf(kw, close)
|
|
3438
|
+
});
|
|
2950
3439
|
const component = {
|
|
2951
|
-
id:
|
|
3440
|
+
id: componentId,
|
|
2952
3441
|
name,
|
|
2953
3442
|
archetype,
|
|
2954
3443
|
contract: { params: {}, props, events },
|
|
@@ -2960,18 +3449,91 @@ var Parser = class {
|
|
|
2960
3449
|
const composition = { uses, coordination };
|
|
2961
3450
|
component.composition = composition;
|
|
2962
3451
|
}
|
|
3452
|
+
if (meta && Object.keys(meta).length) component.meta = meta;
|
|
2963
3453
|
return component;
|
|
2964
3454
|
}
|
|
3455
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
3456
|
+
// Header & metadata (proposal 0026)
|
|
3457
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
3458
|
+
parseMetaValue() {
|
|
3459
|
+
const t = this.peek();
|
|
3460
|
+
if (t.type === "string") return this.next().value;
|
|
3461
|
+
if (t.type === "lbracket") {
|
|
3462
|
+
this.next();
|
|
3463
|
+
const arr = [];
|
|
3464
|
+
while (!this.at("rbracket") && !this.at("eof")) {
|
|
3465
|
+
const mark = this.pos;
|
|
3466
|
+
if (this.at("comma") || this.at("newline") || this.at("semi")) {
|
|
3467
|
+
this.pos++;
|
|
3468
|
+
continue;
|
|
3469
|
+
}
|
|
3470
|
+
const it = this.peek();
|
|
3471
|
+
if (it.type === "string") arr.push(this.next().value);
|
|
3472
|
+
else if (it.type === "word") {
|
|
3473
|
+
const w = this.next().value;
|
|
3474
|
+
arr.push(NUMBER_RE.test(w) ? Number(w) : w === "true" ? true : w === "false" ? false : w);
|
|
3475
|
+
} else this.next();
|
|
3476
|
+
this.ensureProgress(mark);
|
|
3477
|
+
}
|
|
3478
|
+
if (this.at("rbracket")) this.next();
|
|
3479
|
+
return arr;
|
|
3480
|
+
}
|
|
3481
|
+
if (t.type === "word") {
|
|
3482
|
+
this.next();
|
|
3483
|
+
return NUMBER_RE.test(t.value) ? Number(t.value) : t.value === "true" ? true : t.value === "false" ? false : t.value;
|
|
3484
|
+
}
|
|
3485
|
+
this.issue("error", "meta-value", `expected a metadata value, got ${t.type}`);
|
|
3486
|
+
return "";
|
|
3487
|
+
}
|
|
3488
|
+
parseMetaBlock() {
|
|
3489
|
+
this.next();
|
|
3490
|
+
this.expect("lbrace", "meta-lbrace");
|
|
3491
|
+
const out = {};
|
|
3492
|
+
while (!this.at("rbrace") && !this.at("eof")) {
|
|
3493
|
+
const mark = this.pos;
|
|
3494
|
+
if (this.at("newline") || this.at("semi")) {
|
|
3495
|
+
this.pos++;
|
|
3496
|
+
continue;
|
|
3497
|
+
}
|
|
3498
|
+
const key = this.expectWord("meta-key");
|
|
3499
|
+
const val = this.parseMetaValue();
|
|
3500
|
+
if (key) out[key] = val;
|
|
3501
|
+
if (this.at("semi") || this.at("newline")) this.pos++;
|
|
3502
|
+
this.ensureProgress(mark);
|
|
3503
|
+
}
|
|
3504
|
+
this.expect("rbrace", "meta-rbrace");
|
|
3505
|
+
return out;
|
|
3506
|
+
}
|
|
2965
3507
|
// ── top-level document driver ──
|
|
2966
3508
|
parseDocument(opts) {
|
|
2967
3509
|
const contextGroups = [];
|
|
2968
3510
|
const layer = { id: "layer.foundations", name: "Foundations", order: 0, tokens: [] };
|
|
2969
3511
|
const sharedTypes = [];
|
|
2970
3512
|
const components = [];
|
|
3513
|
+
const metadata = [];
|
|
3514
|
+
let mildaVer = opts.milda ?? "0.1";
|
|
3515
|
+
let preludeVer = opts.prelude ?? "0.0.2";
|
|
3516
|
+
let docName = opts.name ?? "Design system";
|
|
3517
|
+
let docVersion = "0.0.0";
|
|
3518
|
+
let docDescription;
|
|
2971
3519
|
this.skipNewlines();
|
|
2972
3520
|
while (!this.at("eof")) {
|
|
2973
3521
|
const mark = this.pos;
|
|
2974
|
-
if (this.atWord("
|
|
3522
|
+
if (this.atWord("milda")) {
|
|
3523
|
+
this.next();
|
|
3524
|
+
mildaVer = this.expectWord("milda-version") || mildaVer;
|
|
3525
|
+
} else if (this.atWord("prelude")) {
|
|
3526
|
+
this.next();
|
|
3527
|
+
preludeVer = this.expectWord("prelude-version") || preludeVer;
|
|
3528
|
+
} else if (this.atWord("meta")) {
|
|
3529
|
+
const m = this.parseMetaBlock();
|
|
3530
|
+
for (const [k, v] of Object.entries(m)) {
|
|
3531
|
+
if (k === "name") docName = String(v);
|
|
3532
|
+
else if (k === "version") docVersion = String(v);
|
|
3533
|
+
else if (k === "description") docDescription = String(v);
|
|
3534
|
+
else metadata.push({ id: `md.${seg(k)}`, key: k, type: metaValueType(v), value: v });
|
|
3535
|
+
}
|
|
3536
|
+
} else if (this.atWord("context-group")) {
|
|
2975
3537
|
const cg = this.parseContextGroup();
|
|
2976
3538
|
if (cg) contextGroups.push(cg);
|
|
2977
3539
|
} else if (this.atWord("foundations")) {
|
|
@@ -2991,19 +3553,20 @@ var Parser = class {
|
|
|
2991
3553
|
this.skipNewlines();
|
|
2992
3554
|
}
|
|
2993
3555
|
return {
|
|
2994
|
-
milda:
|
|
3556
|
+
milda: mildaVer,
|
|
2995
3557
|
kind: "design-system",
|
|
2996
|
-
prelude:
|
|
3558
|
+
prelude: preludeVer,
|
|
2997
3559
|
meta: {
|
|
2998
|
-
id: `ds.${seg(
|
|
2999
|
-
name:
|
|
3000
|
-
version:
|
|
3560
|
+
id: `ds.${seg(docName)}`,
|
|
3561
|
+
name: docName,
|
|
3562
|
+
version: docVersion,
|
|
3563
|
+
...docDescription ? { description: docDescription } : {}
|
|
3001
3564
|
},
|
|
3002
3565
|
foundations: {
|
|
3003
3566
|
contextGroups,
|
|
3004
3567
|
layers: layer.tokens.length ? [layer] : [],
|
|
3005
3568
|
sharedTypes,
|
|
3006
|
-
metadata
|
|
3569
|
+
metadata
|
|
3007
3570
|
},
|
|
3008
3571
|
components
|
|
3009
3572
|
};
|
|
@@ -3017,12 +3580,13 @@ function parseMildaDocument(src, opts = {}) {
|
|
|
3017
3580
|
const le = e;
|
|
3018
3581
|
return {
|
|
3019
3582
|
document: emptyDocument(opts),
|
|
3020
|
-
issues: [{ severity: "error", code: "lex", message: le.message, line: le.line ?? 1, col: le.col ?? 1 }]
|
|
3583
|
+
issues: [{ severity: "error", code: "lex", message: le.message, line: le.line ?? 1, col: le.col ?? 1 }],
|
|
3584
|
+
spans: []
|
|
3021
3585
|
};
|
|
3022
3586
|
}
|
|
3023
3587
|
const p = new Parser(toks);
|
|
3024
3588
|
const document = p.parseDocument(opts);
|
|
3025
|
-
return { document, issues: p.issues };
|
|
3589
|
+
return { document, issues: p.issues, spans: p.spans };
|
|
3026
3590
|
}
|
|
3027
3591
|
function emptyDocument(opts) {
|
|
3028
3592
|
return {
|
|
@@ -3041,6 +3605,7 @@ export {
|
|
|
3041
3605
|
archetypesByName,
|
|
3042
3606
|
atomsByName,
|
|
3043
3607
|
collectActiveStates,
|
|
3608
|
+
compositesByName,
|
|
3044
3609
|
conformance,
|
|
3045
3610
|
findToken,
|
|
3046
3611
|
lex,
|