@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.js
CHANGED
|
@@ -26,6 +26,7 @@ __export(index_exports, {
|
|
|
26
26
|
archetypesByName: () => archetypesByName,
|
|
27
27
|
atomsByName: () => atomsByName,
|
|
28
28
|
collectActiveStates: () => collectActiveStates,
|
|
29
|
+
compositesByName: () => compositesByName,
|
|
29
30
|
conformance: () => conformance,
|
|
30
31
|
findToken: () => findToken,
|
|
31
32
|
lex: () => lex,
|
|
@@ -47,6 +48,7 @@ var TOKEN_VALUE_KIND = {
|
|
|
47
48
|
shadow: "shadow",
|
|
48
49
|
typography: "typography",
|
|
49
50
|
fontFamily: "fontFamily",
|
|
51
|
+
fontWeight: "fontWeight",
|
|
50
52
|
duration: "duration",
|
|
51
53
|
opacity: "opacity",
|
|
52
54
|
blur: "blur",
|
|
@@ -576,6 +578,7 @@ var composites = [
|
|
|
576
578
|
{ atom: "presentation", as: "P", params: { surface: "inline" } }
|
|
577
579
|
],
|
|
578
580
|
composition: [{ kind: "wire", from: "T.change", to: "P.toggle" }],
|
|
581
|
+
emits: [{ name: "toggle" }],
|
|
579
582
|
note: "\u2248 toggle(expanded) + presentation(inline); exposes expanded"
|
|
580
583
|
},
|
|
581
584
|
{
|
|
@@ -613,6 +616,7 @@ var composites = [
|
|
|
613
616
|
kind: "composite",
|
|
614
617
|
name: "stepping-nav",
|
|
615
618
|
builtFrom: ["navigation"],
|
|
619
|
+
emits: [{ name: "stepChange" }, { name: "complete" }],
|
|
616
620
|
note: "\u2248 navigation(ordered) + constraint; ordered progress through steps; exposes active, complete"
|
|
617
621
|
},
|
|
618
622
|
{
|
|
@@ -966,7 +970,11 @@ var archetypes = [
|
|
|
966
970
|
}
|
|
967
971
|
],
|
|
968
972
|
composes: ["selection.single", "presentation", "browse", "dismiss", "masking"],
|
|
969
|
-
|
|
973
|
+
// Behavior is the shared framework-agnostic calendar core (proposal 0029,
|
|
974
|
+
// targets/behavior/calendar.ts); node.calendar picks single|range + weekStartsOn
|
|
975
|
+
// + disabledWeekdays. The `day` grid cell carries calendar states (today,
|
|
976
|
+
// outsideMonth, selected, rangeStart, rangeEnd, inRange) rendered as data-* attrs.
|
|
977
|
+
realizationNote: "web calendar-grid (single|range via node.calendar) \xB7 iOS wheel (momentum+detent+haptic) \xB7 Android dialog"
|
|
970
978
|
},
|
|
971
979
|
{
|
|
972
980
|
kind: "archetype",
|
|
@@ -998,7 +1006,10 @@ var archetypes = [
|
|
|
998
1006
|
}
|
|
999
1007
|
],
|
|
1000
1008
|
composes: ["selection.single", "presentation", "browse", "dismiss", "masking"],
|
|
1001
|
-
|
|
1009
|
+
// Behavior is the shared framework-agnostic time core (proposal 0029,
|
|
1010
|
+
// targets/behavior/timepicker.ts); node.timePicker picks the slot step + 12/24h.
|
|
1011
|
+
// Web realization = a listbox of time slots; iOS wheel is a per-platform view.
|
|
1012
|
+
realizationNote: "web time-slot listbox (step via node.timePicker) \xB7 iOS wheel \xB7 Android dialog"
|
|
1002
1013
|
},
|
|
1003
1014
|
{
|
|
1004
1015
|
kind: "archetype",
|
|
@@ -1021,11 +1032,25 @@ var archetypes = [
|
|
|
1021
1032
|
children: [
|
|
1022
1033
|
{ name: "area", role: "container" },
|
|
1023
1034
|
{ name: "hue", role: "container" },
|
|
1024
|
-
{ name: "
|
|
1035
|
+
{ name: "alpha", role: "container" },
|
|
1036
|
+
{ name: "eyedropper", role: "control" },
|
|
1037
|
+
{ name: "input", role: "input" },
|
|
1038
|
+
{
|
|
1039
|
+
name: "swatches",
|
|
1040
|
+
role: "container",
|
|
1041
|
+
children: [{ name: "swatch", role: "item" }]
|
|
1042
|
+
}
|
|
1025
1043
|
]
|
|
1026
1044
|
}
|
|
1027
1045
|
],
|
|
1028
|
-
composes: ["range", "value-entry", "presentation", "dismiss"]
|
|
1046
|
+
composes: ["range", "value-entry", "presentation", "dismiss"],
|
|
1047
|
+
// Behavior is the shared framework-agnostic color core (proposal 0029/0035,
|
|
1048
|
+
// targets/behavior/color.ts): HSV<->RGB<->hex + OKLCH math and the area/hue/alpha
|
|
1049
|
+
// geometry, so every target computes an identical picker. node.colorPicker (0035)
|
|
1050
|
+
// picks the working space (srgb|oklch), the editable format tabs (hex/rgb/hsl/hsb),
|
|
1051
|
+
// opacity, eyedropper, preset swatches, and popover-vs-inline layout. The `alpha`,
|
|
1052
|
+
// `eyedropper` and `swatches` parts render only when their config flag is on.
|
|
1053
|
+
realizationNote: "web: SV/OKLCH area + hue + optional alpha, format tabs, eyedropper, swatches, popover|inline \xB7 native color input fallback"
|
|
1029
1054
|
},
|
|
1030
1055
|
{
|
|
1031
1056
|
kind: "archetype",
|
|
@@ -1502,10 +1527,11 @@ var archetypes = [
|
|
|
1502
1527
|
},
|
|
1503
1528
|
{
|
|
1504
1529
|
kind: "archetype",
|
|
1505
|
-
name: "
|
|
1530
|
+
name: "Media",
|
|
1506
1531
|
group: "Media",
|
|
1507
|
-
contract: "
|
|
1532
|
+
contract: "image/video/audio/embed w/ alt + fit (proposal 0028)",
|
|
1508
1533
|
produces: { level: "phrase", interactive: false },
|
|
1534
|
+
anatomy: [{ name: "media", role: "content" }],
|
|
1509
1535
|
composes: []
|
|
1510
1536
|
},
|
|
1511
1537
|
{
|
|
@@ -1535,6 +1561,14 @@ var facets = [
|
|
|
1535
1561
|
covers: "edge: color + width (hairline|thin|thick) + style",
|
|
1536
1562
|
divergence: "iOS often overlay-stroke"
|
|
1537
1563
|
},
|
|
1564
|
+
{ name: "border.top", covers: "top edge (physical side)" },
|
|
1565
|
+
{ name: "border.right", covers: "right edge (physical side)" },
|
|
1566
|
+
{ name: "border.bottom", covers: "bottom edge (physical side)" },
|
|
1567
|
+
{ name: "border.left", covers: "left edge (physical side)" },
|
|
1568
|
+
{ name: "border.inline-start", covers: "leading edge (logical, writing-direction)" },
|
|
1569
|
+
{ name: "border.inline-end", covers: "trailing edge (logical, writing-direction)" },
|
|
1570
|
+
{ name: "border.block-start", covers: "block-start edge (logical)" },
|
|
1571
|
+
{ name: "border.block-end", covers: "block-end edge (logical)" },
|
|
1538
1572
|
{ name: "corner", covers: "corner radius (token role)" },
|
|
1539
1573
|
{ name: "inset", covers: "internal padding (block/inline)" },
|
|
1540
1574
|
{ name: "gap", covers: "spacing between children" },
|
|
@@ -1549,6 +1583,17 @@ var facets = [
|
|
|
1549
1583
|
divergence: "web -webkit-line-clamp \xB7 .lineLimit \xB7 maxLines+overflow"
|
|
1550
1584
|
},
|
|
1551
1585
|
{ name: "textWrap", covers: "wrap | nowrap | break" },
|
|
1586
|
+
{
|
|
1587
|
+
name: "weight",
|
|
1588
|
+
covers: "font-weight (typography axis \xA73, split out of the `text` role bundle)",
|
|
1589
|
+
divergence: "web font-weight \xB7 .fontWeight \xB7 TextStyle.fontWeight"
|
|
1590
|
+
},
|
|
1591
|
+
{
|
|
1592
|
+
name: "font",
|
|
1593
|
+
covers: "font-family (typography axis \xA73)",
|
|
1594
|
+
divergence: "web font-family \xB7 UIFont \xB7 Typeface"
|
|
1595
|
+
},
|
|
1596
|
+
{ name: "slant", covers: "font-style: normal | italic | oblique (typography axis \xA73)" },
|
|
1552
1597
|
{
|
|
1553
1598
|
name: "elevation",
|
|
1554
1599
|
covers: "depth",
|
|
@@ -1659,6 +1704,9 @@ var preludeV0 = {
|
|
|
1659
1704
|
nonBehaviors
|
|
1660
1705
|
};
|
|
1661
1706
|
var atomsByName = Object.fromEntries(atoms.map((a) => [a.name, a]));
|
|
1707
|
+
var compositesByName = Object.fromEntries(
|
|
1708
|
+
composites.map((c) => [c.name, c])
|
|
1709
|
+
);
|
|
1662
1710
|
var archetypesByName = Object.fromEntries(
|
|
1663
1711
|
archetypes.map((a) => [a.name, a])
|
|
1664
1712
|
);
|
|
@@ -1734,6 +1782,8 @@ function facetTokenRefs(facets2) {
|
|
|
1734
1782
|
if ("token" in v) ids.push(v.token);
|
|
1735
1783
|
else if ("inset" in v) {
|
|
1736
1784
|
for (const edge of Object.values(v.inset)) if (typeof edge === "string") ids.push(edge);
|
|
1785
|
+
} else if ("border" in v) {
|
|
1786
|
+
if (v.border.color) ids.push(v.border.color);
|
|
1737
1787
|
}
|
|
1738
1788
|
}
|
|
1739
1789
|
return ids;
|
|
@@ -1766,12 +1816,13 @@ function validate(doc, prelude) {
|
|
|
1766
1816
|
for (const tok of layer.tokens) {
|
|
1767
1817
|
if (tok.value.kind === "alias") {
|
|
1768
1818
|
const where = `token ${tok.name} (${tok.id})`;
|
|
1769
|
-
if (!groupIds.has(tok.value.by)) {
|
|
1819
|
+
if (tok.value.by !== "" && !groupIds.has(tok.value.by)) {
|
|
1770
1820
|
issues.push({
|
|
1771
1821
|
severity: "error",
|
|
1772
1822
|
code: "alias.unknownGroup",
|
|
1773
1823
|
message: `alias by unknown context group "${tok.value.by}"`,
|
|
1774
|
-
where
|
|
1824
|
+
where,
|
|
1825
|
+
id: tok.id
|
|
1775
1826
|
});
|
|
1776
1827
|
}
|
|
1777
1828
|
for (const refId of Object.values(tok.value.refs)) {
|
|
@@ -1780,7 +1831,8 @@ function validate(doc, prelude) {
|
|
|
1780
1831
|
severity: "error",
|
|
1781
1832
|
code: "alias.danglingRef",
|
|
1782
1833
|
message: `alias ref points at missing token "${refId}"`,
|
|
1783
|
-
where
|
|
1834
|
+
where,
|
|
1835
|
+
id: tok.id
|
|
1784
1836
|
});
|
|
1785
1837
|
}
|
|
1786
1838
|
}
|
|
@@ -1789,12 +1841,13 @@ function validate(doc, prelude) {
|
|
|
1789
1841
|
}
|
|
1790
1842
|
for (const comp of doc.components) {
|
|
1791
1843
|
const where = `component ${comp.name} (${comp.id})`;
|
|
1792
|
-
if (!archetypeNames.has(comp.archetype)) {
|
|
1844
|
+
if (comp.archetype !== "" && !archetypeNames.has(comp.archetype)) {
|
|
1793
1845
|
issues.push({
|
|
1794
1846
|
severity: "error",
|
|
1795
1847
|
code: "component.unknownArchetype",
|
|
1796
1848
|
message: `unknown archetype "${comp.archetype}"`,
|
|
1797
|
-
where
|
|
1849
|
+
where,
|
|
1850
|
+
id: comp.id
|
|
1798
1851
|
});
|
|
1799
1852
|
}
|
|
1800
1853
|
const propIds = new Set(comp.contract.props.map((p) => p.id));
|
|
@@ -1804,7 +1857,8 @@ function validate(doc, prelude) {
|
|
|
1804
1857
|
severity: "error",
|
|
1805
1858
|
code: "param.danglingProp",
|
|
1806
1859
|
message: `param "${param}" binds missing prop "${binding.prop}"`,
|
|
1807
|
-
where
|
|
1860
|
+
where,
|
|
1861
|
+
id: comp.id
|
|
1808
1862
|
});
|
|
1809
1863
|
}
|
|
1810
1864
|
}
|
|
@@ -1825,7 +1879,8 @@ function checkPart(comp, partName, part, ctx) {
|
|
|
1825
1879
|
severity: "error",
|
|
1826
1880
|
code: "facet.unknown",
|
|
1827
1881
|
message: `unknown facet "${name}"`,
|
|
1828
|
-
where: scope
|
|
1882
|
+
where: scope,
|
|
1883
|
+
id: comp.id
|
|
1829
1884
|
});
|
|
1830
1885
|
}
|
|
1831
1886
|
}
|
|
@@ -1835,7 +1890,8 @@ function checkPart(comp, partName, part, ctx) {
|
|
|
1835
1890
|
severity: "error",
|
|
1836
1891
|
code: "facet.danglingToken",
|
|
1837
1892
|
message: `facet references missing token "${id}"`,
|
|
1838
|
-
where: scope
|
|
1893
|
+
where: scope,
|
|
1894
|
+
id: comp.id
|
|
1839
1895
|
});
|
|
1840
1896
|
}
|
|
1841
1897
|
}
|
|
@@ -1847,7 +1903,8 @@ function checkPart(comp, partName, part, ctx) {
|
|
|
1847
1903
|
severity: "error",
|
|
1848
1904
|
code: "layout.danglingToken",
|
|
1849
1905
|
message: `layout references missing token "${id}"`,
|
|
1850
|
-
where: scope
|
|
1906
|
+
where: scope,
|
|
1907
|
+
id: comp.id
|
|
1851
1908
|
});
|
|
1852
1909
|
}
|
|
1853
1910
|
}
|
|
@@ -1861,7 +1918,8 @@ function checkPart(comp, partName, part, ctx) {
|
|
|
1861
1918
|
severity: "error",
|
|
1862
1919
|
code: "state.unknown",
|
|
1863
1920
|
message: `unknown state "${stateName}"`,
|
|
1864
|
-
where: scope
|
|
1921
|
+
where: scope,
|
|
1922
|
+
id: comp.id
|
|
1865
1923
|
});
|
|
1866
1924
|
}
|
|
1867
1925
|
checkFacets(override?.facets, scope);
|
|
@@ -1939,13 +1997,16 @@ var PUNCT = {
|
|
|
1939
1997
|
"}": "rbrace",
|
|
1940
1998
|
"(": "lparen",
|
|
1941
1999
|
")": "rparen",
|
|
2000
|
+
"[": "lbracket",
|
|
2001
|
+
"]": "rbracket",
|
|
1942
2002
|
"<": "lt",
|
|
1943
2003
|
">": "gt",
|
|
1944
2004
|
":": "colon",
|
|
1945
2005
|
"=": "equals",
|
|
1946
2006
|
",": "comma",
|
|
1947
2007
|
";": "semi",
|
|
1948
|
-
"?": "question"
|
|
2008
|
+
"?": "question",
|
|
2009
|
+
"@": "at"
|
|
1949
2010
|
};
|
|
1950
2011
|
function lex(src) {
|
|
1951
2012
|
const tokens = [];
|
|
@@ -2016,6 +2077,21 @@ function lex(src) {
|
|
|
2016
2077
|
push("arrow", "->", startPos, startLine, startCol);
|
|
2017
2078
|
continue;
|
|
2018
2079
|
}
|
|
2080
|
+
if (c === "!" && src[i + 1] === "=") {
|
|
2081
|
+
advance(2);
|
|
2082
|
+
push("ne", "!=", startPos, startLine, startCol);
|
|
2083
|
+
continue;
|
|
2084
|
+
}
|
|
2085
|
+
if (c === "<" && src[i + 1] === "=") {
|
|
2086
|
+
advance(2);
|
|
2087
|
+
push("lte", "<=", startPos, startLine, startCol);
|
|
2088
|
+
continue;
|
|
2089
|
+
}
|
|
2090
|
+
if (c === ">" && src[i + 1] === "=") {
|
|
2091
|
+
advance(2);
|
|
2092
|
+
push("gte", ">=", startPos, startLine, startCol);
|
|
2093
|
+
continue;
|
|
2094
|
+
}
|
|
2019
2095
|
if (PUNCT[c]) {
|
|
2020
2096
|
advance();
|
|
2021
2097
|
push(PUNCT[c], c, startPos, startLine, startCol);
|
|
@@ -2043,10 +2119,24 @@ var ctxId = (groupName, ctxName) => `ctx.${seg(groupName)}.${seg(ctxName)}`;
|
|
|
2043
2119
|
var tokId = (path) => `tok.${path.replace(/\//g, ".")}`;
|
|
2044
2120
|
var stId = (name) => `st.${seg(name)}`;
|
|
2045
2121
|
var cmpId = (name) => `cmp.${seg(name)}`;
|
|
2122
|
+
function metaValueType(v) {
|
|
2123
|
+
if (typeof v === "number") return { kind: "number" };
|
|
2124
|
+
if (typeof v === "boolean") return { kind: "boolean" };
|
|
2125
|
+
if (Array.isArray(v)) return { kind: "array", item: { kind: "string" } };
|
|
2126
|
+
return { kind: "string" };
|
|
2127
|
+
}
|
|
2046
2128
|
var FACET_MAP = {
|
|
2047
2129
|
fill: "fill",
|
|
2048
2130
|
ink: "ink",
|
|
2049
2131
|
border: "border",
|
|
2132
|
+
"border.top": "border.top",
|
|
2133
|
+
"border.right": "border.right",
|
|
2134
|
+
"border.bottom": "border.bottom",
|
|
2135
|
+
"border.left": "border.left",
|
|
2136
|
+
"border.inline-start": "border.inline-start",
|
|
2137
|
+
"border.inline-end": "border.inline-end",
|
|
2138
|
+
"border.block-start": "border.block-start",
|
|
2139
|
+
"border.block-end": "border.block-end",
|
|
2050
2140
|
corner: "corner",
|
|
2051
2141
|
inset: "inset",
|
|
2052
2142
|
gap: "gap",
|
|
@@ -2058,9 +2148,9 @@ var FACET_MAP = {
|
|
|
2058
2148
|
opacity: "opacity",
|
|
2059
2149
|
ring: "ring",
|
|
2060
2150
|
backdrop: "backdrop",
|
|
2061
|
-
weight:
|
|
2062
|
-
font:
|
|
2063
|
-
slant:
|
|
2151
|
+
weight: "weight",
|
|
2152
|
+
font: "font",
|
|
2153
|
+
slant: "slant"
|
|
2064
2154
|
};
|
|
2065
2155
|
var TYPE_MAP = {
|
|
2066
2156
|
color: "color",
|
|
@@ -2072,6 +2162,8 @@ var TYPE_MAP = {
|
|
|
2072
2162
|
typography: "typography",
|
|
2073
2163
|
"font-family": "fontFamily",
|
|
2074
2164
|
fontFamily: "fontFamily",
|
|
2165
|
+
"font-weight": "fontWeight",
|
|
2166
|
+
fontWeight: "fontWeight",
|
|
2075
2167
|
duration: "duration",
|
|
2076
2168
|
opacity: "opacity",
|
|
2077
2169
|
blur: "blur",
|
|
@@ -2095,10 +2187,34 @@ function hexToColor(hex) {
|
|
|
2095
2187
|
if (h.length === 8) value.alpha = byte(3);
|
|
2096
2188
|
return value;
|
|
2097
2189
|
}
|
|
2190
|
+
var COLOR_FNS = /* @__PURE__ */ new Set(["hex", "srgb", "rgb", "p3", "oklch", "hsl"]);
|
|
2191
|
+
var UNIT_RE = /^(-?\d*\.?\d+)(px|rem|em|%|ms|s)$/;
|
|
2192
|
+
function unitToValue(n, unit) {
|
|
2193
|
+
switch (unit) {
|
|
2194
|
+
case "px":
|
|
2195
|
+
return { value: n, unit: "fixed" };
|
|
2196
|
+
case "em":
|
|
2197
|
+
return { value: n, unit: "em" };
|
|
2198
|
+
case "%":
|
|
2199
|
+
return { value: n, unit: "percent" };
|
|
2200
|
+
case "ms":
|
|
2201
|
+
return { ms: n };
|
|
2202
|
+
case "s":
|
|
2203
|
+
return { ms: n * 1e3 };
|
|
2204
|
+
default:
|
|
2205
|
+
return { value: n, unit: "scaled" };
|
|
2206
|
+
}
|
|
2207
|
+
}
|
|
2098
2208
|
var Parser = class {
|
|
2099
2209
|
constructor(toks) {
|
|
2100
2210
|
this.pos = 0;
|
|
2101
2211
|
this.issues = [];
|
|
2212
|
+
// Source spans for declared entities (see NodeSpan). Populated as declarations are
|
|
2213
|
+
// parsed; the AST itself never gains position fields.
|
|
2214
|
+
this.spans = [];
|
|
2215
|
+
// The component currently being parsed, so nested part/prop/event spans can point
|
|
2216
|
+
// back at it via `parentId`.
|
|
2217
|
+
this.currentComponentId = null;
|
|
2102
2218
|
// Tokens synthesized while parsing (inline literals hoisted out of alias slots —
|
|
2103
2219
|
// the canonical AST's `refs` holds only token ids, so an inline value must become
|
|
2104
2220
|
// its own token). Deduped by (type, serialized value). Flushed into the layer.
|
|
@@ -2118,6 +2234,73 @@ var Parser = class {
|
|
|
2118
2234
|
this.issue("warning", "inline-literal", `inline ${astType} literal in an alias slot hoisted to token ${id}`, tok);
|
|
2119
2235
|
return id;
|
|
2120
2236
|
}
|
|
2237
|
+
// Hoist an already-typed token value (an inline literal in an alias slot) into a
|
|
2238
|
+
// synthesized token and return its id.
|
|
2239
|
+
hoistValue(astType, value, tok) {
|
|
2240
|
+
const key = `${astType} ${JSON.stringify(value)}`;
|
|
2241
|
+
const existing = this.synthKeys.get(key);
|
|
2242
|
+
if (existing) return existing;
|
|
2243
|
+
const id = `tok.lit-${seg(String(astType))}-${this.synth.length + 1}`;
|
|
2244
|
+
this.synth.push({ id, name: `(inline ${astType})`, type: astType, value });
|
|
2245
|
+
this.synthKeys.set(key, id);
|
|
2246
|
+
this.issue("warning", "inline-literal", `inline ${astType} literal in an alias slot hoisted to token ${id}`, tok);
|
|
2247
|
+
return id;
|
|
2248
|
+
}
|
|
2249
|
+
// A self-typing value literal: a color-space function (`oklch(…)`, `hex(rrggbb)`, …)
|
|
2250
|
+
// or a unit-suffixed number (`8px`, `150ms`). Returns a raw TokenValue, or null if the
|
|
2251
|
+
// cursor isn't at such a literal (then it consumes nothing).
|
|
2252
|
+
tryParseLiteralValue() {
|
|
2253
|
+
const t = this.peek();
|
|
2254
|
+
if (t.type !== "word") return null;
|
|
2255
|
+
if (COLOR_FNS.has(t.value) && this.toks[this.pos + 1]?.type === "lparen") {
|
|
2256
|
+
return { kind: "raw", value: this.parseColorFn(t.value) };
|
|
2257
|
+
}
|
|
2258
|
+
const m = UNIT_RE.exec(t.value);
|
|
2259
|
+
if (m) {
|
|
2260
|
+
this.next();
|
|
2261
|
+
return { kind: "raw", value: unitToValue(Number(m[1]), m[2]) };
|
|
2262
|
+
}
|
|
2263
|
+
return null;
|
|
2264
|
+
}
|
|
2265
|
+
// `fn( a b c [alpha] )` — space-separated numeric channels (hex takes raw digits).
|
|
2266
|
+
parseColorFn(fn) {
|
|
2267
|
+
this.next();
|
|
2268
|
+
this.expect("lparen", "color-lparen");
|
|
2269
|
+
const nums = [];
|
|
2270
|
+
let hex = "";
|
|
2271
|
+
while (!this.at("rparen") && !this.at("eof")) {
|
|
2272
|
+
const w = this.next().value;
|
|
2273
|
+
if (fn === "hex") hex += w;
|
|
2274
|
+
else {
|
|
2275
|
+
const n = Number(w);
|
|
2276
|
+
if (!Number.isNaN(n)) nums.push(n);
|
|
2277
|
+
}
|
|
2278
|
+
}
|
|
2279
|
+
this.expect("rparen", "color-rparen");
|
|
2280
|
+
if (fn === "hex") {
|
|
2281
|
+
return hexToColor(hex.startsWith("#") ? hex : `#${hex}`) ?? { space: "srgb", channels: [0, 0, 0] };
|
|
2282
|
+
}
|
|
2283
|
+
const c = (i) => nums[i] ?? 0;
|
|
2284
|
+
let cv;
|
|
2285
|
+
if (fn === "rgb") cv = { space: "srgb", channels: [c(0) / 255, c(1) / 255, c(2) / 255] };
|
|
2286
|
+
else if (fn === "p3") cv = { space: "display-p3", channels: [c(0), c(1), c(2)] };
|
|
2287
|
+
else cv = { space: fn, channels: [c(0), c(1), c(2)] };
|
|
2288
|
+
if (nums.length >= 4) cv.alpha = nums[3];
|
|
2289
|
+
return cv;
|
|
2290
|
+
}
|
|
2291
|
+
// Self-typing literals let us check the value's inherent kind against the token's
|
|
2292
|
+
// declared type (e.g. `token x : color = 8px` is a dimension in a color slot).
|
|
2293
|
+
checkLiteralType(astType, lit, tok) {
|
|
2294
|
+
if (lit.kind !== "raw") return;
|
|
2295
|
+
const v = lit.value;
|
|
2296
|
+
if (typeof v !== "object" || v === null) return;
|
|
2297
|
+
const cat = "space" in v ? "color" : "ms" in v ? "duration" : "unit" in v ? "dimension" : null;
|
|
2298
|
+
if (!cat) return;
|
|
2299
|
+
const expected = astType === "color" ? "color" : astType === "duration" ? "duration" : astType === "spacing" || astType === "radius" || astType === "size" ? "dimension" : null;
|
|
2300
|
+
if (expected && cat !== expected) {
|
|
2301
|
+
this.issue("error", "value-type", `${cat} literal in a "${astType}" token`, tok);
|
|
2302
|
+
}
|
|
2303
|
+
}
|
|
2121
2304
|
peek() {
|
|
2122
2305
|
return this.toks[this.pos];
|
|
2123
2306
|
}
|
|
@@ -2143,6 +2326,28 @@ var Parser = class {
|
|
|
2143
2326
|
if (!this.at("eof")) this.next();
|
|
2144
2327
|
return true;
|
|
2145
2328
|
}
|
|
2329
|
+
// ─── span recording (source positions for tooling) ─────────────────────────
|
|
2330
|
+
recordSpan(span) {
|
|
2331
|
+
this.spans.push(span);
|
|
2332
|
+
}
|
|
2333
|
+
startPos(tok) {
|
|
2334
|
+
return { line: tok.line, col: tok.col };
|
|
2335
|
+
}
|
|
2336
|
+
// End position of a token = its start advanced past its source text. Strings add 2
|
|
2337
|
+
// for the surrounding quotes (escape sequences make this a slight over-estimate,
|
|
2338
|
+
// affecting only a rarely-used string span — never an identifier or brace).
|
|
2339
|
+
endPos(tok) {
|
|
2340
|
+
const len = tok.type === "newline" || tok.type === "eof" ? 0 : tok.type === "string" ? tok.value.length + 2 : tok.value.length;
|
|
2341
|
+
return { line: tok.line, col: tok.col + len };
|
|
2342
|
+
}
|
|
2343
|
+
rangeOf(start, end) {
|
|
2344
|
+
return { start: this.startPos(start), end: this.endPos(end) };
|
|
2345
|
+
}
|
|
2346
|
+
// The most-recently-consumed token — closes a span that has no trailing brace
|
|
2347
|
+
// (a single-line `token`/`type` declaration ends at its value).
|
|
2348
|
+
lastTok() {
|
|
2349
|
+
return this.toks[Math.max(0, this.pos - 1)];
|
|
2350
|
+
}
|
|
2146
2351
|
issue(severity, code, message, tok = this.peek()) {
|
|
2147
2352
|
this.issues.push({ severity, code, message, line: tok.line, col: tok.col });
|
|
2148
2353
|
}
|
|
@@ -2173,7 +2378,8 @@ var Parser = class {
|
|
|
2173
2378
|
}
|
|
2174
2379
|
// ── context-group NAME { ctx ctx … } ──
|
|
2175
2380
|
parseContextGroup() {
|
|
2176
|
-
this.next();
|
|
2381
|
+
const kw = this.next();
|
|
2382
|
+
const nameTok = this.peek();
|
|
2177
2383
|
const name = this.expectWord("cg-name");
|
|
2178
2384
|
this.expect("lbrace", "cg-lbrace");
|
|
2179
2385
|
const contexts = [];
|
|
@@ -2182,11 +2388,27 @@ var Parser = class {
|
|
|
2182
2388
|
this.pos++;
|
|
2183
2389
|
continue;
|
|
2184
2390
|
}
|
|
2391
|
+
const ctxTok = this.peek();
|
|
2185
2392
|
const ctxName = this.expectWord("cg-ctx");
|
|
2186
2393
|
if (!ctxName) break;
|
|
2187
2394
|
contexts.push({ id: ctxId(name, ctxName), name: ctxName });
|
|
2395
|
+
this.recordSpan({
|
|
2396
|
+
id: ctxId(name, ctxName),
|
|
2397
|
+
kind: "context",
|
|
2398
|
+
name: ctxName,
|
|
2399
|
+
nameRange: this.rangeOf(ctxTok, ctxTok),
|
|
2400
|
+
fullRange: this.rangeOf(ctxTok, ctxTok),
|
|
2401
|
+
parentId: cgId(name)
|
|
2402
|
+
});
|
|
2188
2403
|
}
|
|
2189
|
-
this.expect("rbrace", "cg-rbrace");
|
|
2404
|
+
const close = this.expect("rbrace", "cg-rbrace");
|
|
2405
|
+
this.recordSpan({
|
|
2406
|
+
id: cgId(name),
|
|
2407
|
+
kind: "contextGroup",
|
|
2408
|
+
name,
|
|
2409
|
+
nameRange: this.rangeOf(nameTok, nameTok),
|
|
2410
|
+
fullRange: this.rangeOf(kw, close)
|
|
2411
|
+
});
|
|
2190
2412
|
return { id: cgId(name), name, contexts };
|
|
2191
2413
|
}
|
|
2192
2414
|
// ── raw value literal → a JS value (string | number | nested object) ──
|
|
@@ -2195,6 +2417,12 @@ var Parser = class {
|
|
|
2195
2417
|
if (t.type === "string") return this.next().value;
|
|
2196
2418
|
if (t.type === "lbrace") return this.parseStructured();
|
|
2197
2419
|
if (t.type === "word") {
|
|
2420
|
+
if (COLOR_FNS.has(t.value) && this.toks[this.pos + 1]?.type === "lparen") return this.parseColorFn(t.value);
|
|
2421
|
+
const m = UNIT_RE.exec(t.value);
|
|
2422
|
+
if (m) {
|
|
2423
|
+
this.next();
|
|
2424
|
+
return unitToValue(Number(m[1]), m[2]);
|
|
2425
|
+
}
|
|
2198
2426
|
const w = this.next().value;
|
|
2199
2427
|
return NUMBER_RE.test(w) ? Number(w) : w;
|
|
2200
2428
|
}
|
|
@@ -2236,6 +2464,7 @@ var Parser = class {
|
|
|
2236
2464
|
}
|
|
2237
2465
|
}
|
|
2238
2466
|
if (astType === "opacity" && typeof raw === "number") return { kind: "raw", value: { value: raw } };
|
|
2467
|
+
if (astType === "fontWeight" && typeof raw === "number") return { kind: "raw", value: { weight: raw } };
|
|
2239
2468
|
if (astType === "duration" && typeof raw === "number") return { kind: "raw", value: { ms: raw } };
|
|
2240
2469
|
if (astType === "tonal" && typeof raw === "number") return { kind: "raw", value: { level: raw } };
|
|
2241
2470
|
return { kind: "raw", value: raw };
|
|
@@ -2261,7 +2490,11 @@ var Parser = class {
|
|
|
2261
2490
|
this.expect("arrow", "alias-arrow");
|
|
2262
2491
|
const slot = this.peek();
|
|
2263
2492
|
let refId = "";
|
|
2264
|
-
|
|
2493
|
+
const lit = this.tryParseLiteralValue();
|
|
2494
|
+
if (lit) {
|
|
2495
|
+
this.checkLiteralType(astType, lit, slot);
|
|
2496
|
+
refId = this.hoistValue(astType, lit, slot);
|
|
2497
|
+
} else if (slot.type === "string" || slot.type === "lbrace" || slot.type === "word" && NUMBER_RE.test(slot.value)) {
|
|
2265
2498
|
refId = this.hoistLiteral(astType, this.parseRawLiteral(), slot);
|
|
2266
2499
|
} else {
|
|
2267
2500
|
const refPath = this.expectWord("alias-ref");
|
|
@@ -2303,6 +2536,15 @@ var Parser = class {
|
|
|
2303
2536
|
return this.typedRaw(astType, raw, t);
|
|
2304
2537
|
}
|
|
2305
2538
|
if (t.type === "word") {
|
|
2539
|
+
const lit = this.tryParseLiteralValue();
|
|
2540
|
+
if (lit) {
|
|
2541
|
+
this.checkLiteralType(astType, lit, t);
|
|
2542
|
+
return lit;
|
|
2543
|
+
}
|
|
2544
|
+
if (NUMBER_RE.test(t.value)) {
|
|
2545
|
+
this.next();
|
|
2546
|
+
return this.typedRaw(astType, Number(t.value), t);
|
|
2547
|
+
}
|
|
2306
2548
|
const refPath = this.next().value;
|
|
2307
2549
|
this.issue("warning", "ref-sugar", `unconditional ref "${refPath}" lowered to a default-only alias`, t);
|
|
2308
2550
|
return { kind: "alias", by: "", refs: { default: tokId(refPath) } };
|
|
@@ -2342,6 +2584,7 @@ var Parser = class {
|
|
|
2342
2584
|
}
|
|
2343
2585
|
parseTokenDecl(_order) {
|
|
2344
2586
|
const startTok = this.next();
|
|
2587
|
+
const nameTok = this.peek();
|
|
2345
2588
|
const path = this.expectWord("token-path");
|
|
2346
2589
|
if (!path) return null;
|
|
2347
2590
|
this.expect("colon", "token-colon");
|
|
@@ -2350,6 +2593,13 @@ var Parser = class {
|
|
|
2350
2593
|
if (!astType) this.issue("warning", "token-type", `non-canonical token type "${typeName}" kept as-is`, startTok);
|
|
2351
2594
|
this.expect("equals", "token-equals");
|
|
2352
2595
|
const value = this.parseTokenValue(astType ?? typeName);
|
|
2596
|
+
this.recordSpan({
|
|
2597
|
+
id: tokId(path),
|
|
2598
|
+
kind: "token",
|
|
2599
|
+
name: path,
|
|
2600
|
+
nameRange: this.rangeOf(nameTok, nameTok),
|
|
2601
|
+
fullRange: this.rangeOf(startTok, this.lastTok())
|
|
2602
|
+
});
|
|
2353
2603
|
return {
|
|
2354
2604
|
id: tokId(path),
|
|
2355
2605
|
name: path,
|
|
@@ -2461,11 +2711,19 @@ var Parser = class {
|
|
|
2461
2711
|
return { kind: "ref", ref: stId(w) };
|
|
2462
2712
|
}
|
|
2463
2713
|
parseSharedType() {
|
|
2464
|
-
this.next();
|
|
2714
|
+
const kw = this.next();
|
|
2715
|
+
const nameTok = this.peek();
|
|
2465
2716
|
const name = this.expectWord("type-name");
|
|
2466
2717
|
if (!name) return null;
|
|
2467
2718
|
this.expect("equals", "type-equals");
|
|
2468
2719
|
const definition = this.parseTypeExpr();
|
|
2720
|
+
this.recordSpan({
|
|
2721
|
+
id: stId(name),
|
|
2722
|
+
kind: "sharedType",
|
|
2723
|
+
name,
|
|
2724
|
+
nameRange: this.rangeOf(nameTok, nameTok),
|
|
2725
|
+
fullRange: this.rangeOf(kw, this.lastTok())
|
|
2726
|
+
});
|
|
2469
2727
|
return { id: stId(name), name, definition };
|
|
2470
2728
|
}
|
|
2471
2729
|
// ════════════════════════════════════════════════════════════════════════
|
|
@@ -2495,6 +2753,125 @@ var Parser = class {
|
|
|
2495
2753
|
}
|
|
2496
2754
|
return { prop, eq };
|
|
2497
2755
|
}
|
|
2756
|
+
// ── Predicate expression parser (proposal 0032) ─────────────────────────────
|
|
2757
|
+
// Recursive-descent over the surface predicate grammar emitted by @mildastudio/
|
|
2758
|
+
// generate's `predToDsl` (targets/dsl/emit.ts). Precedence, lowest → highest:
|
|
2759
|
+
// or → and → unary(`not`) → primary(`( … )` | leaf)
|
|
2760
|
+
// A leaf names a source (`state s` / `context g` / `ancestor id s` / a bare prop path)
|
|
2761
|
+
// then an optional operator + value: a bare leaf is `set`, `not <leaf>` is `unset`, and
|
|
2762
|
+
// a parenthesized `not ( … )` negates a whole group. Values are bare idents/numbers/
|
|
2763
|
+
// bools or quoted strings (mirroring the emitter's `predValue`), coerced to a JS type.
|
|
2764
|
+
parsePredicate() {
|
|
2765
|
+
return this.parsePredOr();
|
|
2766
|
+
}
|
|
2767
|
+
parsePredOr() {
|
|
2768
|
+
const first = this.parsePredAnd();
|
|
2769
|
+
if (!this.atWord("or")) return first;
|
|
2770
|
+
const items = [first];
|
|
2771
|
+
while (this.atWord("or")) {
|
|
2772
|
+
this.next();
|
|
2773
|
+
items.push(this.parsePredAnd());
|
|
2774
|
+
}
|
|
2775
|
+
return { kind: "any", items };
|
|
2776
|
+
}
|
|
2777
|
+
parsePredAnd() {
|
|
2778
|
+
const first = this.parsePredUnary();
|
|
2779
|
+
if (!this.atWord("and")) return first;
|
|
2780
|
+
const items = [first];
|
|
2781
|
+
while (this.atWord("and")) {
|
|
2782
|
+
this.next();
|
|
2783
|
+
items.push(this.parsePredUnary());
|
|
2784
|
+
}
|
|
2785
|
+
return { kind: "all", items };
|
|
2786
|
+
}
|
|
2787
|
+
parsePredUnary() {
|
|
2788
|
+
if (this.atWord("not")) {
|
|
2789
|
+
this.next();
|
|
2790
|
+
if (this.at("lparen")) {
|
|
2791
|
+
this.next();
|
|
2792
|
+
const item = this.parsePredOr();
|
|
2793
|
+
this.expect("rparen", "pred-not-rparen");
|
|
2794
|
+
return { kind: "not", item };
|
|
2795
|
+
}
|
|
2796
|
+
return { kind: "cmp", ref: this.parsePredRef(), op: "unset" };
|
|
2797
|
+
}
|
|
2798
|
+
return this.parsePredPrimary();
|
|
2799
|
+
}
|
|
2800
|
+
parsePredPrimary() {
|
|
2801
|
+
if (this.at("lparen")) {
|
|
2802
|
+
this.next();
|
|
2803
|
+
const inner = this.parsePredOr();
|
|
2804
|
+
this.expect("rparen", "pred-rparen");
|
|
2805
|
+
return inner;
|
|
2806
|
+
}
|
|
2807
|
+
return this.parsePredLeaf();
|
|
2808
|
+
}
|
|
2809
|
+
// A comparison leaf: a source, then an optional operator + value. No operator ⇒ `set`.
|
|
2810
|
+
parsePredLeaf() {
|
|
2811
|
+
const ref = this.parsePredRef();
|
|
2812
|
+
const op = this.predOp();
|
|
2813
|
+
if (!op) return { kind: "cmp", ref, op: "set" };
|
|
2814
|
+
return { kind: "cmp", ref, op, value: this.parsePredValue() };
|
|
2815
|
+
}
|
|
2816
|
+
// The source of a leaf: `state <s>` / `context <g>` / `ancestor <id> <s>` / bare prop path.
|
|
2817
|
+
parsePredRef() {
|
|
2818
|
+
if (this.atWord("state")) {
|
|
2819
|
+
this.next();
|
|
2820
|
+
return { kind: "state", state: this.expectWord("pred-state") };
|
|
2821
|
+
}
|
|
2822
|
+
if (this.atWord("context")) {
|
|
2823
|
+
this.next();
|
|
2824
|
+
return { kind: "context", group: this.expectWord("pred-context") };
|
|
2825
|
+
}
|
|
2826
|
+
if (this.atWord("ancestor")) {
|
|
2827
|
+
this.next();
|
|
2828
|
+
const nodeId = this.expectWord("pred-ancestor-id");
|
|
2829
|
+
const state = this.expectWord("pred-ancestor-state");
|
|
2830
|
+
return { kind: "ancestor", nodeId, state };
|
|
2831
|
+
}
|
|
2832
|
+
return { kind: "prop", path: this.expectWord("pred-prop") };
|
|
2833
|
+
}
|
|
2834
|
+
// A comparison operator token → CompareOp, or null when the next token isn't one.
|
|
2835
|
+
predOp() {
|
|
2836
|
+
switch (this.peek().type) {
|
|
2837
|
+
case "equals":
|
|
2838
|
+
this.next();
|
|
2839
|
+
return "eq";
|
|
2840
|
+
case "ne":
|
|
2841
|
+
this.next();
|
|
2842
|
+
return "ne";
|
|
2843
|
+
case "lt":
|
|
2844
|
+
this.next();
|
|
2845
|
+
return "lt";
|
|
2846
|
+
case "lte":
|
|
2847
|
+
this.next();
|
|
2848
|
+
return "lte";
|
|
2849
|
+
case "gt":
|
|
2850
|
+
this.next();
|
|
2851
|
+
return "gt";
|
|
2852
|
+
case "gte":
|
|
2853
|
+
this.next();
|
|
2854
|
+
return "gte";
|
|
2855
|
+
default:
|
|
2856
|
+
if (this.atWord("contains")) {
|
|
2857
|
+
this.next();
|
|
2858
|
+
return "contains";
|
|
2859
|
+
}
|
|
2860
|
+
return null;
|
|
2861
|
+
}
|
|
2862
|
+
}
|
|
2863
|
+
// A leaf value: a quoted string, or a bare word coerced to number / boolean / string
|
|
2864
|
+
// (mirrors `parseRealizeCond` and the emitter's bare-vs-quoted `predValue`).
|
|
2865
|
+
parsePredValue() {
|
|
2866
|
+
const t = this.peek();
|
|
2867
|
+
if (t.type === "string") return this.next().value;
|
|
2868
|
+
if (t.type === "word") {
|
|
2869
|
+
const w = this.next().value;
|
|
2870
|
+
return NUMBER_RE.test(w) ? Number(w) : w === "true" ? true : w === "false" ? false : w;
|
|
2871
|
+
}
|
|
2872
|
+
this.issue("error", "pred-value", `expected a comparison value, got ${t.type}`);
|
|
2873
|
+
return "";
|
|
2874
|
+
}
|
|
2498
2875
|
parsePropDefault() {
|
|
2499
2876
|
const t = this.peek();
|
|
2500
2877
|
if (t.type === "string") return this.next().value;
|
|
@@ -2517,6 +2894,7 @@ var Parser = class {
|
|
|
2517
2894
|
return void 0;
|
|
2518
2895
|
}
|
|
2519
2896
|
parsePropDecl() {
|
|
2897
|
+
const nameTok = this.peek();
|
|
2520
2898
|
const name = this.expectWord("prop-name");
|
|
2521
2899
|
if (!name) return null;
|
|
2522
2900
|
const type = this.parseTypeExpr();
|
|
@@ -2530,6 +2908,28 @@ var Parser = class {
|
|
|
2530
2908
|
this.next();
|
|
2531
2909
|
prop.binding = this.expectWord("bind-target");
|
|
2532
2910
|
}
|
|
2911
|
+
const meta = {};
|
|
2912
|
+
while (this.at("at")) {
|
|
2913
|
+
this.next();
|
|
2914
|
+
const key = this.expectWord("annot-key");
|
|
2915
|
+
const t = this.peek();
|
|
2916
|
+
let value = true;
|
|
2917
|
+
if (t.type === "string") value = this.next().value;
|
|
2918
|
+
else if (t.type === "word") {
|
|
2919
|
+
const w = this.next().value;
|
|
2920
|
+
value = NUMBER_RE.test(w) ? Number(w) : w === "true" ? true : w === "false" ? false : w;
|
|
2921
|
+
}
|
|
2922
|
+
if (key) meta[key] = value;
|
|
2923
|
+
}
|
|
2924
|
+
if (Object.keys(meta).length) prop.meta = meta;
|
|
2925
|
+
this.recordSpan({
|
|
2926
|
+
id: `${this.currentComponentId ?? ""}#prop.${seg(name)}`,
|
|
2927
|
+
kind: "prop",
|
|
2928
|
+
name,
|
|
2929
|
+
nameRange: this.rangeOf(nameTok, nameTok),
|
|
2930
|
+
fullRange: this.rangeOf(nameTok, this.lastTok()),
|
|
2931
|
+
parentId: this.currentComponentId ?? void 0
|
|
2932
|
+
});
|
|
2533
2933
|
return prop;
|
|
2534
2934
|
}
|
|
2535
2935
|
parseProps(props) {
|
|
@@ -2557,6 +2957,7 @@ var Parser = class {
|
|
|
2557
2957
|
this.pos++;
|
|
2558
2958
|
continue;
|
|
2559
2959
|
}
|
|
2960
|
+
const nameTok = this.peek();
|
|
2560
2961
|
const name = this.expectWord("event-name");
|
|
2561
2962
|
if (name) {
|
|
2562
2963
|
const e = { name };
|
|
@@ -2565,6 +2966,14 @@ var Parser = class {
|
|
|
2565
2966
|
e.from = this.expectWord("event-from");
|
|
2566
2967
|
}
|
|
2567
2968
|
events.push(e);
|
|
2969
|
+
this.recordSpan({
|
|
2970
|
+
id: `${this.currentComponentId ?? ""}#event.${seg(name)}`,
|
|
2971
|
+
kind: "event",
|
|
2972
|
+
name,
|
|
2973
|
+
nameRange: this.rangeOf(nameTok, nameTok),
|
|
2974
|
+
fullRange: this.rangeOf(nameTok, this.lastTok()),
|
|
2975
|
+
parentId: this.currentComponentId ?? void 0
|
|
2976
|
+
});
|
|
2568
2977
|
}
|
|
2569
2978
|
if (this.at("semi") || this.at("newline")) this.pos++;
|
|
2570
2979
|
this.ensureProgress(mark);
|
|
@@ -2737,6 +3146,16 @@ var Parser = class {
|
|
|
2737
3146
|
if (mapped === "inset") {
|
|
2738
3147
|
return { name: "inset", value: { inset: this.parseInsetArgs() } };
|
|
2739
3148
|
}
|
|
3149
|
+
if (mapped === "border" || mapped.startsWith("border.")) {
|
|
3150
|
+
const border = {};
|
|
3151
|
+
while (this.at("word")) {
|
|
3152
|
+
const w = this.next().value;
|
|
3153
|
+
if (w === "solid" || w === "dashed" || w === "dotted") border.style = w;
|
|
3154
|
+
else if (w.includes(".")) border.color = tokId(w);
|
|
3155
|
+
else border.width = tokId(w);
|
|
3156
|
+
}
|
|
3157
|
+
return { name: mapped, value: { border } };
|
|
3158
|
+
}
|
|
2740
3159
|
const t = this.peek();
|
|
2741
3160
|
let value;
|
|
2742
3161
|
if (t.type === "string") {
|
|
@@ -2745,6 +3164,7 @@ var Parser = class {
|
|
|
2745
3164
|
const v = this.next().value;
|
|
2746
3165
|
if (NUMBER_RE.test(v)) value = { literal: Number(v) };
|
|
2747
3166
|
else if (isRawValue(v)) value = { raw: v };
|
|
3167
|
+
else if (!v.includes(".")) value = { raw: v };
|
|
2748
3168
|
else value = { token: tokId(v) };
|
|
2749
3169
|
} else {
|
|
2750
3170
|
this.issue("error", "facet-value", `expected a facet value, got ${t.type}`);
|
|
@@ -2766,11 +3186,13 @@ var Parser = class {
|
|
|
2766
3186
|
this.applyStyleBody(variant);
|
|
2767
3187
|
return variant;
|
|
2768
3188
|
}
|
|
2769
|
-
// `when
|
|
3189
|
+
// `when <predicate> { … }` → a state-less predicate-conditioned PartVariant. The rich
|
|
3190
|
+
// predicate grammar (proposal 0032) supersedes the flat single-condition form: it carries
|
|
3191
|
+
// OR / NOT / operators / state·context·ancestor leaves. A plain `when prop = value` parses
|
|
3192
|
+
// to the degenerate single-`eq` predicate, so the legacy one-condition case still works.
|
|
2770
3193
|
parseWhenBlock() {
|
|
2771
3194
|
this.next();
|
|
2772
|
-
const
|
|
2773
|
-
const variant = { when };
|
|
3195
|
+
const variant = { whenPredicate: this.parsePredicate() };
|
|
2774
3196
|
this.applyStyleBody(variant);
|
|
2775
3197
|
return variant;
|
|
2776
3198
|
}
|
|
@@ -2797,9 +3219,26 @@ var Parser = class {
|
|
|
2797
3219
|
this.expect("rbrace", "style-rbrace");
|
|
2798
3220
|
if (Object.keys(facets2).length) target.facets = facets2;
|
|
2799
3221
|
}
|
|
3222
|
+
// `slot [single|optional|many] [of content | of TYPE]` — a content slot on a part.
|
|
3223
|
+
// Bare `slot` defaults to a single content slot.
|
|
3224
|
+
parseSlot() {
|
|
3225
|
+
let arity = "single";
|
|
3226
|
+
if (this.atWord("single") || this.atWord("optional") || this.atWord("many")) {
|
|
3227
|
+
arity = this.next().value;
|
|
3228
|
+
}
|
|
3229
|
+
let accepts = { kind: "content" };
|
|
3230
|
+
if (this.atWord("of")) {
|
|
3231
|
+
this.next();
|
|
3232
|
+
const w = this.expectWord("slot-accepts");
|
|
3233
|
+
if (w && w !== "content") accepts = { kind: "type", ref: w };
|
|
3234
|
+
}
|
|
3235
|
+
this.skipToTerminator();
|
|
3236
|
+
return { arity, accepts };
|
|
3237
|
+
}
|
|
2800
3238
|
// `part NAME { pstmt* }` → [name, Part]
|
|
2801
3239
|
parsePart() {
|
|
2802
|
-
this.next();
|
|
3240
|
+
const kw = this.next();
|
|
3241
|
+
const nameTok = this.peek();
|
|
2803
3242
|
const name = this.expectWord("part-name");
|
|
2804
3243
|
if (!name) return null;
|
|
2805
3244
|
this.expect("lbrace", "part-lbrace");
|
|
@@ -2821,13 +3260,18 @@ var Parser = class {
|
|
|
2821
3260
|
} else {
|
|
2822
3261
|
part.layout = this.parseLayoutBody();
|
|
2823
3262
|
}
|
|
3263
|
+
} else if (this.atWord("present")) {
|
|
3264
|
+
this.next();
|
|
3265
|
+
if (this.atWord("when")) this.next();
|
|
3266
|
+
else this.issue("warning", "present-when", "expected 'when' after 'present'");
|
|
3267
|
+
part.presence = this.parsePredicate();
|
|
2824
3268
|
} else if (this.atWord("state")) {
|
|
2825
3269
|
variants.push(this.parseStateBlock());
|
|
2826
3270
|
} else if (this.atWord("when")) {
|
|
2827
3271
|
variants.push(this.parseWhenBlock());
|
|
2828
3272
|
} else if (this.atWord("slot")) {
|
|
2829
|
-
this.
|
|
2830
|
-
this.
|
|
3273
|
+
this.next();
|
|
3274
|
+
part.slot = this.parseSlot();
|
|
2831
3275
|
} else {
|
|
2832
3276
|
const f = this.parseFacetStmt();
|
|
2833
3277
|
if (f) facets2[f.name] = f.value;
|
|
@@ -2835,9 +3279,17 @@ var Parser = class {
|
|
|
2835
3279
|
if (this.at("semi") || this.at("newline")) this.pos++;
|
|
2836
3280
|
this.ensureProgress(mark);
|
|
2837
3281
|
}
|
|
2838
|
-
this.expect("rbrace", "part-rbrace");
|
|
3282
|
+
const close = this.expect("rbrace", "part-rbrace");
|
|
2839
3283
|
if (Object.keys(facets2).length) part.facets = facets2;
|
|
2840
3284
|
if (variants.length) part.variants = variants;
|
|
3285
|
+
this.recordSpan({
|
|
3286
|
+
id: `${this.currentComponentId ?? ""}#part.${seg(name)}`,
|
|
3287
|
+
kind: "part",
|
|
3288
|
+
name,
|
|
3289
|
+
nameRange: this.rangeOf(nameTok, nameTok),
|
|
3290
|
+
fullRange: this.rangeOf(kw, close),
|
|
3291
|
+
parentId: this.currentComponentId ?? void 0
|
|
3292
|
+
});
|
|
2841
3293
|
return [name, part];
|
|
2842
3294
|
}
|
|
2843
3295
|
// `realize TARGET [when COND] { rstmt* }`
|
|
@@ -2881,6 +3333,14 @@ var Parser = class {
|
|
|
2881
3333
|
} else if (this.atWord("note")) {
|
|
2882
3334
|
this.next();
|
|
2883
3335
|
r.note = this.at("string") ? this.next().value : this.expectWord("note");
|
|
3336
|
+
} else if (this.atWord("dismiss")) {
|
|
3337
|
+
this.next();
|
|
3338
|
+
const words = this.parseIdentListWords();
|
|
3339
|
+
r.dismiss = words.length === 1 && words[0] === "none" ? [] : words;
|
|
3340
|
+
} else if (this.atWord("backdrop")) {
|
|
3341
|
+
this.next();
|
|
3342
|
+
r.backdrop = this.at("word") ? this.next().value : void 0;
|
|
3343
|
+
this.skipToTerminator();
|
|
2884
3344
|
} else if (this.atWord("wire")) {
|
|
2885
3345
|
const w = this.parseWire();
|
|
2886
3346
|
if (w && w.kind === "wire") wires.push({ from: w.from, to: w.to, ...w.arg ? { arg: w.arg } : {} });
|
|
@@ -2922,6 +3382,21 @@ var Parser = class {
|
|
|
2922
3382
|
this.next();
|
|
2923
3383
|
commit.on = this.parseIdentListWords();
|
|
2924
3384
|
}
|
|
3385
|
+
if (this.at("lbrace")) {
|
|
3386
|
+
this.next();
|
|
3387
|
+
while (!this.at("rbrace") && !this.at("eof")) {
|
|
3388
|
+
if (this.at("newline") || this.at("semi")) {
|
|
3389
|
+
this.pos++;
|
|
3390
|
+
continue;
|
|
3391
|
+
}
|
|
3392
|
+
const key = this.expectWord("commit-key");
|
|
3393
|
+
const val = this.at("word") ? this.next().value : void 0;
|
|
3394
|
+
if (key === "live" && val) commit.live = val;
|
|
3395
|
+
else if (key === "settled" && val) commit.settled = val;
|
|
3396
|
+
else this.skipToTerminator();
|
|
3397
|
+
}
|
|
3398
|
+
this.expect("rbrace", "commit-rbrace");
|
|
3399
|
+
}
|
|
2925
3400
|
return commit;
|
|
2926
3401
|
}
|
|
2927
3402
|
// A run of bare words up to a terminator (for `drop a b c`, `commit … on click enter`).
|
|
@@ -2931,13 +3406,18 @@ var Parser = class {
|
|
|
2931
3406
|
return out;
|
|
2932
3407
|
}
|
|
2933
3408
|
parseComponent() {
|
|
2934
|
-
this.next();
|
|
3409
|
+
const kw = this.next();
|
|
3410
|
+
const nameTok = this.peek();
|
|
2935
3411
|
const name = this.expectWord("component-name");
|
|
2936
3412
|
if (!name) return null;
|
|
3413
|
+
const componentId = cmpId(name);
|
|
3414
|
+
const prevComponentId = this.currentComponentId;
|
|
3415
|
+
this.currentComponentId = componentId;
|
|
2937
3416
|
let description;
|
|
2938
3417
|
if (this.at("string")) description = this.next().value;
|
|
2939
3418
|
this.expect("lbrace", "component-lbrace");
|
|
2940
3419
|
let archetype = "";
|
|
3420
|
+
let meta;
|
|
2941
3421
|
const props = [];
|
|
2942
3422
|
const events = [];
|
|
2943
3423
|
const uses = [];
|
|
@@ -2953,6 +3433,8 @@ var Parser = class {
|
|
|
2953
3433
|
if (this.atWord("archetype")) {
|
|
2954
3434
|
this.next();
|
|
2955
3435
|
archetype = this.expectWord("archetype");
|
|
3436
|
+
} else if (this.atWord("meta")) {
|
|
3437
|
+
meta = this.parseMetaBlock();
|
|
2956
3438
|
} else if (this.atWord("props")) {
|
|
2957
3439
|
this.parseProps(props);
|
|
2958
3440
|
} else if (this.atWord("events")) {
|
|
@@ -2986,9 +3468,17 @@ var Parser = class {
|
|
|
2986
3468
|
}
|
|
2987
3469
|
this.ensureProgress(mark);
|
|
2988
3470
|
}
|
|
2989
|
-
this.expect("rbrace", "component-rbrace");
|
|
3471
|
+
const close = this.expect("rbrace", "component-rbrace");
|
|
3472
|
+
this.currentComponentId = prevComponentId;
|
|
3473
|
+
this.recordSpan({
|
|
3474
|
+
id: componentId,
|
|
3475
|
+
kind: "component",
|
|
3476
|
+
name,
|
|
3477
|
+
nameRange: this.rangeOf(nameTok, nameTok),
|
|
3478
|
+
fullRange: this.rangeOf(kw, close)
|
|
3479
|
+
});
|
|
2990
3480
|
const component = {
|
|
2991
|
-
id:
|
|
3481
|
+
id: componentId,
|
|
2992
3482
|
name,
|
|
2993
3483
|
archetype,
|
|
2994
3484
|
contract: { params: {}, props, events },
|
|
@@ -3000,18 +3490,91 @@ var Parser = class {
|
|
|
3000
3490
|
const composition = { uses, coordination };
|
|
3001
3491
|
component.composition = composition;
|
|
3002
3492
|
}
|
|
3493
|
+
if (meta && Object.keys(meta).length) component.meta = meta;
|
|
3003
3494
|
return component;
|
|
3004
3495
|
}
|
|
3496
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
3497
|
+
// Header & metadata (proposal 0026)
|
|
3498
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
3499
|
+
parseMetaValue() {
|
|
3500
|
+
const t = this.peek();
|
|
3501
|
+
if (t.type === "string") return this.next().value;
|
|
3502
|
+
if (t.type === "lbracket") {
|
|
3503
|
+
this.next();
|
|
3504
|
+
const arr = [];
|
|
3505
|
+
while (!this.at("rbracket") && !this.at("eof")) {
|
|
3506
|
+
const mark = this.pos;
|
|
3507
|
+
if (this.at("comma") || this.at("newline") || this.at("semi")) {
|
|
3508
|
+
this.pos++;
|
|
3509
|
+
continue;
|
|
3510
|
+
}
|
|
3511
|
+
const it = this.peek();
|
|
3512
|
+
if (it.type === "string") arr.push(this.next().value);
|
|
3513
|
+
else if (it.type === "word") {
|
|
3514
|
+
const w = this.next().value;
|
|
3515
|
+
arr.push(NUMBER_RE.test(w) ? Number(w) : w === "true" ? true : w === "false" ? false : w);
|
|
3516
|
+
} else this.next();
|
|
3517
|
+
this.ensureProgress(mark);
|
|
3518
|
+
}
|
|
3519
|
+
if (this.at("rbracket")) this.next();
|
|
3520
|
+
return arr;
|
|
3521
|
+
}
|
|
3522
|
+
if (t.type === "word") {
|
|
3523
|
+
this.next();
|
|
3524
|
+
return NUMBER_RE.test(t.value) ? Number(t.value) : t.value === "true" ? true : t.value === "false" ? false : t.value;
|
|
3525
|
+
}
|
|
3526
|
+
this.issue("error", "meta-value", `expected a metadata value, got ${t.type}`);
|
|
3527
|
+
return "";
|
|
3528
|
+
}
|
|
3529
|
+
parseMetaBlock() {
|
|
3530
|
+
this.next();
|
|
3531
|
+
this.expect("lbrace", "meta-lbrace");
|
|
3532
|
+
const out = {};
|
|
3533
|
+
while (!this.at("rbrace") && !this.at("eof")) {
|
|
3534
|
+
const mark = this.pos;
|
|
3535
|
+
if (this.at("newline") || this.at("semi")) {
|
|
3536
|
+
this.pos++;
|
|
3537
|
+
continue;
|
|
3538
|
+
}
|
|
3539
|
+
const key = this.expectWord("meta-key");
|
|
3540
|
+
const val = this.parseMetaValue();
|
|
3541
|
+
if (key) out[key] = val;
|
|
3542
|
+
if (this.at("semi") || this.at("newline")) this.pos++;
|
|
3543
|
+
this.ensureProgress(mark);
|
|
3544
|
+
}
|
|
3545
|
+
this.expect("rbrace", "meta-rbrace");
|
|
3546
|
+
return out;
|
|
3547
|
+
}
|
|
3005
3548
|
// ── top-level document driver ──
|
|
3006
3549
|
parseDocument(opts) {
|
|
3007
3550
|
const contextGroups = [];
|
|
3008
3551
|
const layer = { id: "layer.foundations", name: "Foundations", order: 0, tokens: [] };
|
|
3009
3552
|
const sharedTypes = [];
|
|
3010
3553
|
const components = [];
|
|
3554
|
+
const metadata = [];
|
|
3555
|
+
let mildaVer = opts.milda ?? "0.1";
|
|
3556
|
+
let preludeVer = opts.prelude ?? "0.0.2";
|
|
3557
|
+
let docName = opts.name ?? "Design system";
|
|
3558
|
+
let docVersion = "0.0.0";
|
|
3559
|
+
let docDescription;
|
|
3011
3560
|
this.skipNewlines();
|
|
3012
3561
|
while (!this.at("eof")) {
|
|
3013
3562
|
const mark = this.pos;
|
|
3014
|
-
if (this.atWord("
|
|
3563
|
+
if (this.atWord("milda")) {
|
|
3564
|
+
this.next();
|
|
3565
|
+
mildaVer = this.expectWord("milda-version") || mildaVer;
|
|
3566
|
+
} else if (this.atWord("prelude")) {
|
|
3567
|
+
this.next();
|
|
3568
|
+
preludeVer = this.expectWord("prelude-version") || preludeVer;
|
|
3569
|
+
} else if (this.atWord("meta")) {
|
|
3570
|
+
const m = this.parseMetaBlock();
|
|
3571
|
+
for (const [k, v] of Object.entries(m)) {
|
|
3572
|
+
if (k === "name") docName = String(v);
|
|
3573
|
+
else if (k === "version") docVersion = String(v);
|
|
3574
|
+
else if (k === "description") docDescription = String(v);
|
|
3575
|
+
else metadata.push({ id: `md.${seg(k)}`, key: k, type: metaValueType(v), value: v });
|
|
3576
|
+
}
|
|
3577
|
+
} else if (this.atWord("context-group")) {
|
|
3015
3578
|
const cg = this.parseContextGroup();
|
|
3016
3579
|
if (cg) contextGroups.push(cg);
|
|
3017
3580
|
} else if (this.atWord("foundations")) {
|
|
@@ -3031,19 +3594,20 @@ var Parser = class {
|
|
|
3031
3594
|
this.skipNewlines();
|
|
3032
3595
|
}
|
|
3033
3596
|
return {
|
|
3034
|
-
milda:
|
|
3597
|
+
milda: mildaVer,
|
|
3035
3598
|
kind: "design-system",
|
|
3036
|
-
prelude:
|
|
3599
|
+
prelude: preludeVer,
|
|
3037
3600
|
meta: {
|
|
3038
|
-
id: `ds.${seg(
|
|
3039
|
-
name:
|
|
3040
|
-
version:
|
|
3601
|
+
id: `ds.${seg(docName)}`,
|
|
3602
|
+
name: docName,
|
|
3603
|
+
version: docVersion,
|
|
3604
|
+
...docDescription ? { description: docDescription } : {}
|
|
3041
3605
|
},
|
|
3042
3606
|
foundations: {
|
|
3043
3607
|
contextGroups,
|
|
3044
3608
|
layers: layer.tokens.length ? [layer] : [],
|
|
3045
3609
|
sharedTypes,
|
|
3046
|
-
metadata
|
|
3610
|
+
metadata
|
|
3047
3611
|
},
|
|
3048
3612
|
components
|
|
3049
3613
|
};
|
|
@@ -3057,12 +3621,13 @@ function parseMildaDocument(src, opts = {}) {
|
|
|
3057
3621
|
const le = e;
|
|
3058
3622
|
return {
|
|
3059
3623
|
document: emptyDocument(opts),
|
|
3060
|
-
issues: [{ severity: "error", code: "lex", message: le.message, line: le.line ?? 1, col: le.col ?? 1 }]
|
|
3624
|
+
issues: [{ severity: "error", code: "lex", message: le.message, line: le.line ?? 1, col: le.col ?? 1 }],
|
|
3625
|
+
spans: []
|
|
3061
3626
|
};
|
|
3062
3627
|
}
|
|
3063
3628
|
const p = new Parser(toks);
|
|
3064
3629
|
const document = p.parseDocument(opts);
|
|
3065
|
-
return { document, issues: p.issues };
|
|
3630
|
+
return { document, issues: p.issues, spans: p.spans };
|
|
3066
3631
|
}
|
|
3067
3632
|
function emptyDocument(opts) {
|
|
3068
3633
|
return {
|
|
@@ -3082,6 +3647,7 @@ function emptyDocument(opts) {
|
|
|
3082
3647
|
archetypesByName,
|
|
3083
3648
|
atomsByName,
|
|
3084
3649
|
collectActiveStates,
|
|
3650
|
+
compositesByName,
|
|
3085
3651
|
conformance,
|
|
3086
3652
|
findToken,
|
|
3087
3653
|
lex,
|