@pikacss/core 0.0.52 → 0.0.53
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 +1 -1
- package/dist/index.d.mts +1171 -342
- package/dist/index.mjs +373 -144
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -489,6 +489,7 @@ const PROPERTY_EFFECTS = {
|
|
|
489
489
|
"flex-direction": ["flex-direction"],
|
|
490
490
|
"flex-flow": ["flex-direction", "flex-wrap"],
|
|
491
491
|
"flex-grow": ["flex-grow"],
|
|
492
|
+
"flex-line-count": ["flex-line-count"],
|
|
492
493
|
"flex-shrink": ["flex-shrink"],
|
|
493
494
|
"flex-wrap": ["flex-wrap"],
|
|
494
495
|
"float": ["float"],
|
|
@@ -1148,6 +1149,7 @@ const PROPERTY_EFFECTS = {
|
|
|
1148
1149
|
"widows": ["widows"],
|
|
1149
1150
|
"width": ["width"],
|
|
1150
1151
|
"will-change": ["will-change"],
|
|
1152
|
+
"window-drag": ["window-drag"],
|
|
1151
1153
|
"word-break": ["word-break"],
|
|
1152
1154
|
"word-space-transform": ["word-space-transform"],
|
|
1153
1155
|
"word-spacing": ["word-spacing"],
|
|
@@ -1301,13 +1303,13 @@ const numOfChars = chars.length;
|
|
|
1301
1303
|
* @param num - The non-negative integer to encode.
|
|
1302
1304
|
* @returns A short alphabetic string unique to the given integer.
|
|
1303
1305
|
*
|
|
1304
|
-
* @remarks Used to generate compact, human-readable atomic style class IDs. The encoding is deterministic
|
|
1306
|
+
* @remarks Used to generate compact, human-readable atomic style class IDs. The encoding is deterministic (the same number always produces the same string) and least-significant-digit first: `52` maps to `'aa'`, `53` to `'ba'`, and so on.
|
|
1305
1307
|
*
|
|
1306
1308
|
* @example
|
|
1307
1309
|
* ```ts
|
|
1308
1310
|
* numberToChars(0) // 'a'
|
|
1309
1311
|
* numberToChars(51) // 'Z'
|
|
1310
|
-
* numberToChars(52) // '
|
|
1312
|
+
* numberToChars(52) // 'aa'
|
|
1311
1313
|
* ```
|
|
1312
1314
|
*/
|
|
1313
1315
|
function numberToChars(num) {
|
|
@@ -1378,27 +1380,111 @@ function isNotString(value) {
|
|
|
1378
1380
|
return typeof value !== "string";
|
|
1379
1381
|
}
|
|
1380
1382
|
/**
|
|
1381
|
-
*
|
|
1383
|
+
* Type-narrowing guard that returns `true` when the value is a plain object record (non-null, non-array object).
|
|
1384
|
+
* @internal
|
|
1385
|
+
*
|
|
1386
|
+
* @param value - The value to test.
|
|
1387
|
+
* @returns `true` if the value is an object that is neither `null` nor an array, narrowing the type to `Record<string, unknown>`.
|
|
1388
|
+
*
|
|
1389
|
+
* @remarks Used to distinguish nested definition objects (variables, design tokens) from scalar values and arrays while walking configuration trees.
|
|
1390
|
+
*
|
|
1391
|
+
* @example
|
|
1392
|
+
* ```ts
|
|
1393
|
+
* isPlainObjectRecord({ a: 1 }) // true
|
|
1394
|
+
* isPlainObjectRecord([1, 2]) // false
|
|
1395
|
+
* isPlainObjectRecord(null) // false
|
|
1396
|
+
* ```
|
|
1397
|
+
*/
|
|
1398
|
+
function isPlainObjectRecord(value) {
|
|
1399
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1400
|
+
}
|
|
1401
|
+
const REGEXP_SPECIAL_CHARS_RE = /[.*+?^${}()|[\]\\/-]/g;
|
|
1402
|
+
/**
|
|
1403
|
+
* Escapes regular expression special characters in a string so it can be embedded in a `RegExp` source as a literal match.
|
|
1404
|
+
* @internal
|
|
1405
|
+
*
|
|
1406
|
+
* @param value - The literal text to escape.
|
|
1407
|
+
* @returns The input with every regex special character prefixed by a backslash.
|
|
1408
|
+
*
|
|
1409
|
+
* @remarks Runtime substitute for `RegExp.escape()` (available in Node.js >= 24 but not yet typed by TypeScript 5.9). Escapes all regex syntax characters plus `/` and `-`; the extra escapes are identity escapes in non-`u`-flag patterns, so the result is safe to embed in the non-unicode-mode regexes built by the engine and integrations.
|
|
1410
|
+
*
|
|
1411
|
+
* @example
|
|
1412
|
+
* ```ts
|
|
1413
|
+
* escapeRegExp('i-icon?') // 'i\\-icon\\?'
|
|
1414
|
+
* new RegExp(`^${escapeRegExp('a.b')}$`).test('a.b') // true
|
|
1415
|
+
* ```
|
|
1416
|
+
*/
|
|
1417
|
+
function escapeRegExp(value) {
|
|
1418
|
+
return value.replace(REGEXP_SPECIAL_CHARS_RE, "\\$&");
|
|
1419
|
+
}
|
|
1420
|
+
function isPropertyValueScalar(v) {
|
|
1421
|
+
return typeof v === "string" || typeof v === "number";
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Tests whether a value conforms to the `InternalPropertyValue` shape: a string, a number, a `[value, fallback[]]` tuple, or nullish.
|
|
1382
1425
|
* @internal
|
|
1383
1426
|
*
|
|
1384
1427
|
* @param v - The value to inspect.
|
|
1385
1428
|
* @returns `true` if the value is a valid property value.
|
|
1386
1429
|
*
|
|
1387
|
-
* @remarks During extraction, the engine uses this guard to distinguish CSS property values from nested selector objects or style item arrays.
|
|
1430
|
+
* @remarks During extraction, the engine uses this guard to distinguish CSS property values from nested selector objects or style item arrays. Numbers are accepted because the csstype-based input types allow numeric values such as `0`; they are converted to strings during value normalization.
|
|
1388
1431
|
*
|
|
1389
1432
|
* @example
|
|
1390
1433
|
* ```ts
|
|
1391
1434
|
* isPropertyValue('red') // true
|
|
1435
|
+
* isPropertyValue(0) // true
|
|
1392
1436
|
* isPropertyValue(['red', ['blue']]) // true
|
|
1437
|
+
* isPropertyValue(['auto', [0]]) // true
|
|
1393
1438
|
* isPropertyValue(null) // true
|
|
1394
1439
|
* isPropertyValue({ color: 'red' }) // false
|
|
1395
1440
|
* ```
|
|
1396
1441
|
*/
|
|
1397
1442
|
function isPropertyValue(v) {
|
|
1398
|
-
if (Array.isArray(v)) return v.length === 2 &&
|
|
1443
|
+
if (Array.isArray(v)) return v.length === 2 && isPropertyValueScalar(v[0]) && Array.isArray(v[1]) && v[1].every(isPropertyValueScalar);
|
|
1399
1444
|
if (v == null) return true;
|
|
1400
|
-
|
|
1401
|
-
|
|
1445
|
+
return isPropertyValueScalar(v);
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* Applies a transform to the parts of a string outside quoted segments,
|
|
1449
|
+
* leaving single- and double-quoted content (e.g. attribute values) untouched.
|
|
1450
|
+
* @internal
|
|
1451
|
+
*
|
|
1452
|
+
* @param str - The string to scan.
|
|
1453
|
+
* @param transform - Transform applied to each unquoted segment.
|
|
1454
|
+
* @returns The reassembled string with transformed unquoted segments and untouched quoted segments.
|
|
1455
|
+
*
|
|
1456
|
+
* @remarks A backslash-escaped character is treated as a literal both inside and outside quoted segments, so a CSS-escaped quote in an identifier (e.g. `.it\'s`) never starts quoted-segment scanning.
|
|
1457
|
+
*
|
|
1458
|
+
* @example
|
|
1459
|
+
* ```ts
|
|
1460
|
+
* transformOutsideQuotes('[data-x="%"] %', s => s.replace(/%/g, 'pk-a'))
|
|
1461
|
+
* // '[data-x="%"] pk-a'
|
|
1462
|
+
* ```
|
|
1463
|
+
*/
|
|
1464
|
+
function transformOutsideQuotes(str, transform) {
|
|
1465
|
+
let result = "";
|
|
1466
|
+
let segmentStart = 0;
|
|
1467
|
+
for (let i = 0; i < str.length; i++) {
|
|
1468
|
+
const ch = str[i];
|
|
1469
|
+
if (ch === "\\") {
|
|
1470
|
+
i++;
|
|
1471
|
+
continue;
|
|
1472
|
+
}
|
|
1473
|
+
if (ch === "\"" || ch === "'") {
|
|
1474
|
+
result += transform(str.slice(segmentStart, i));
|
|
1475
|
+
let j = i + 1;
|
|
1476
|
+
while (j < str.length && str[j] !== ch) {
|
|
1477
|
+
if (str[j] === "\\") j++;
|
|
1478
|
+
j++;
|
|
1479
|
+
}
|
|
1480
|
+
const end = Math.min(j, str.length - 1);
|
|
1481
|
+
result += str.slice(i, end + 1);
|
|
1482
|
+
i = end;
|
|
1483
|
+
segmentStart = i + 1;
|
|
1484
|
+
}
|
|
1485
|
+
}
|
|
1486
|
+
result += transform(str.slice(segmentStart));
|
|
1487
|
+
return result;
|
|
1402
1488
|
}
|
|
1403
1489
|
/**
|
|
1404
1490
|
* Serializes a value to a JSON string for use as a deterministic cache key.
|
|
@@ -1470,23 +1556,25 @@ function appendAutocompleteEntries(set, values) {
|
|
|
1470
1556
|
* @param entries - A record mapping keys to single or arrayed string values, or `undefined` to skip.
|
|
1471
1557
|
* @returns `true` if at least one entry was added or extended; `false` if the input was nullish or empty.
|
|
1472
1558
|
*
|
|
1473
|
-
* @remarks Existing map entries are extended (not replaced) with the new values, maintaining all previously registered suggestions for a given key. This accumulative behavior allows multiple plugins to contribute value suggestions for the same property.
|
|
1559
|
+
* @remarks Existing map entries are extended (not replaced) with the new values, maintaining all previously registered suggestions for a given key. This accumulative behavior allows multiple plugins to contribute value suggestions for the same property. Values already present for a key are skipped, and the function returns `false` when nothing new was added.
|
|
1474
1560
|
*
|
|
1475
1561
|
* @example
|
|
1476
1562
|
* ```ts
|
|
1477
1563
|
* const map = new Map<string, string[]>()
|
|
1478
1564
|
* appendAutocompleteRecordEntries(map, { color: ['red', 'blue'] }) // true
|
|
1479
1565
|
* appendAutocompleteRecordEntries(map, { color: 'green' }) // true (now ['red','blue','green'])
|
|
1566
|
+
* appendAutocompleteRecordEntries(map, { color: 'green' }) // false (no change)
|
|
1480
1567
|
* ```
|
|
1481
1568
|
*/
|
|
1482
1569
|
function appendAutocompleteRecordEntries(map, entries) {
|
|
1483
1570
|
if (entries == null) return false;
|
|
1484
1571
|
let changed = false;
|
|
1485
1572
|
for (const [key, value] of Object.entries(entries)) {
|
|
1486
|
-
const
|
|
1487
|
-
|
|
1488
|
-
const
|
|
1489
|
-
|
|
1573
|
+
const current = map.get(key);
|
|
1574
|
+
const existing = new Set(current);
|
|
1575
|
+
const added = [value].flat().filter((v) => !existing.has(v));
|
|
1576
|
+
if (added.length === 0) continue;
|
|
1577
|
+
map.set(key, [...current ?? [], ...added]);
|
|
1490
1578
|
changed = true;
|
|
1491
1579
|
}
|
|
1492
1580
|
return changed;
|
|
@@ -1552,17 +1640,25 @@ function renderCSSStyleBlocks(blocks, isFormatted, depth = 0) {
|
|
|
1552
1640
|
const propertySpace = isFormatted ? " " : "";
|
|
1553
1641
|
const lineEnd = isFormatted ? "\n" : "";
|
|
1554
1642
|
const lines = [];
|
|
1555
|
-
blocks.forEach((
|
|
1556
|
-
if (
|
|
1643
|
+
blocks.forEach((blockBody, selector) => {
|
|
1644
|
+
if (hasRenderableBlockContent(blockBody) === false) return;
|
|
1645
|
+
const { properties, children } = blockBody;
|
|
1646
|
+
const childrenCss = children != null && children.size > 0 ? renderCSSStyleBlocks(children, isFormatted, depth + 1) : "";
|
|
1557
1647
|
lines.push(...[
|
|
1558
1648
|
`${blockIndent}${selector}${selectorEnd}{`,
|
|
1559
1649
|
...properties.map(({ property, value }) => `${blockBodyIndent}${property}:${propertySpace}${value};`),
|
|
1560
|
-
...
|
|
1650
|
+
...childrenCss !== "" ? [childrenCss] : [],
|
|
1561
1651
|
`${blockIndent}}`
|
|
1562
1652
|
]);
|
|
1563
1653
|
});
|
|
1564
1654
|
return lines.join(lineEnd);
|
|
1565
1655
|
}
|
|
1656
|
+
function hasRenderableBlockContent(blockBody) {
|
|
1657
|
+
if (blockBody.properties.length > 0) return true;
|
|
1658
|
+
if (blockBody.children == null) return false;
|
|
1659
|
+
for (const child of blockBody.children.values()) if (hasRenderableBlockContent(child)) return true;
|
|
1660
|
+
return false;
|
|
1661
|
+
}
|
|
1566
1662
|
//#endregion
|
|
1567
1663
|
//#region src/atomic-style.ts
|
|
1568
1664
|
/**
|
|
@@ -1788,9 +1884,48 @@ const LAYER_SELECTOR_PREFIX = "@layer ";
|
|
|
1788
1884
|
* Global regex matching all occurrences of {@link ATOMIC_STYLE_ID_PLACEHOLDER}
|
|
1789
1885
|
* for batch replacement in selector templates.
|
|
1790
1886
|
*
|
|
1887
|
+
* A `%` directly preceded by a digit is treated as a literal percentage
|
|
1888
|
+
* (e.g. `@supports (width: 50%)`), not a placeholder.
|
|
1889
|
+
*
|
|
1890
|
+
* @internal
|
|
1891
|
+
*/
|
|
1892
|
+
const ATOMIC_STYLE_ID_PLACEHOLDER_RE_GLOBAL = /(?<!\d)%/g;
|
|
1893
|
+
/**
|
|
1894
|
+
* Tests whether a selector string contains the atomic style ID placeholder.
|
|
1895
|
+
*
|
|
1896
|
+
* Uses the same digit-protection rule as {@link ATOMIC_STYLE_ID_PLACEHOLDER_RE_GLOBAL}:
|
|
1897
|
+
* `%` preceded by a digit is a literal percentage, not a placeholder.
|
|
1898
|
+
* A `%` inside single- or double-quoted content (e.g. an attribute value)
|
|
1899
|
+
* is never treated as a placeholder.
|
|
1900
|
+
*
|
|
1791
1901
|
* @internal
|
|
1792
1902
|
*/
|
|
1793
|
-
|
|
1903
|
+
function hasAtomicStyleIdPlaceholder(selector) {
|
|
1904
|
+
if (!selector.includes("\"") && !selector.includes("'")) {
|
|
1905
|
+
ATOMIC_STYLE_ID_PLACEHOLDER_RE_GLOBAL.lastIndex = 0;
|
|
1906
|
+
return ATOMIC_STYLE_ID_PLACEHOLDER_RE_GLOBAL.test(selector);
|
|
1907
|
+
}
|
|
1908
|
+
let found = false;
|
|
1909
|
+
transformOutsideQuotes(selector, (segment) => {
|
|
1910
|
+
if (found === false) {
|
|
1911
|
+
ATOMIC_STYLE_ID_PLACEHOLDER_RE_GLOBAL.lastIndex = 0;
|
|
1912
|
+
found = ATOMIC_STYLE_ID_PLACEHOLDER_RE_GLOBAL.test(segment);
|
|
1913
|
+
}
|
|
1914
|
+
return segment;
|
|
1915
|
+
});
|
|
1916
|
+
return found;
|
|
1917
|
+
}
|
|
1918
|
+
/**
|
|
1919
|
+
* Replaces every atomic style ID placeholder in a selector string with the given atomic style ID.
|
|
1920
|
+
*
|
|
1921
|
+
* Applies the same rules as {@link hasAtomicStyleIdPlaceholder}: a `%` directly
|
|
1922
|
+
* preceded by a digit is a literal percentage, and quoted content is never rewritten.
|
|
1923
|
+
*
|
|
1924
|
+
* @internal
|
|
1925
|
+
*/
|
|
1926
|
+
function replaceAtomicStyleIdPlaceholder(selector, atomicStyleId) {
|
|
1927
|
+
return transformOutsideQuotes(selector, (segment) => segment.replace(ATOMIC_STYLE_ID_PLACEHOLDER_RE_GLOBAL, () => atomicStyleId));
|
|
1928
|
+
}
|
|
1794
1929
|
//#endregion
|
|
1795
1930
|
//#region src/extractor.ts
|
|
1796
1931
|
function replaceBySplitAndJoin(str, split, mapFn, join) {
|
|
@@ -1811,7 +1946,7 @@ const ATTRIBUTE_SUFFIX_MATCH_RE_GLOBAL = /\$=/g;
|
|
|
1811
1946
|
* @param options.defaultSelector - The selector template that replaces `$` placeholders.
|
|
1812
1947
|
* @returns An array of normalized selector strings with all placeholders resolved.
|
|
1813
1948
|
*
|
|
1814
|
-
* @remarks The `$` character in a selector is replaced with the engine's `defaultSelector`. The `%` character is the atomic style ID placeholder, preserved for later substitution. Attribute suffix matches (`$=`) are protected from the `$` replacement.
|
|
1949
|
+
* @remarks The `$` character in a selector is replaced with the engine's `defaultSelector`. The `%` character is the atomic style ID placeholder, preserved for later substitution. Attribute suffix matches (`$=`) are protected from the `$` replacement. Content inside single or double quotes is never rewritten, and a `%` directly preceded by a digit (e.g. `@supports (width: 50%)`) is treated as a literal percentage.
|
|
1815
1950
|
*
|
|
1816
1951
|
* @example
|
|
1817
1952
|
* ```ts
|
|
@@ -1820,7 +1955,7 @@ const ATTRIBUTE_SUFFIX_MATCH_RE_GLOBAL = /\$=/g;
|
|
|
1820
1955
|
* ```
|
|
1821
1956
|
*/
|
|
1822
1957
|
function normalizeSelectors({ selectors, defaultSelector }) {
|
|
1823
|
-
return selectors.map((s) => replaceBySplitAndJoin(
|
|
1958
|
+
return selectors.map((s) => transformOutsideQuotes(s, (segment) => replaceBySplitAndJoin(segment.replace(RE_SPLIT, ","), ATOMIC_STYLE_ID_PLACEHOLDER_RE_GLOBAL, (a) => replaceBySplitAndJoin(a, ATTRIBUTE_SUFFIX_MATCH_RE_GLOBAL, (b) => replaceBySplitAndJoin(b, DEFAULT_SELECTOR_PLACEHOLDER_RE_GLOBAL, null, defaultSelector), ATTRIBUTE_SUFFIX_MATCH), "%")));
|
|
1824
1959
|
}
|
|
1825
1960
|
/**
|
|
1826
1961
|
* Normalizes a raw `InternalPropertyValue` into the extraction output format: an array of trimmed, deduplicated CSS value strings with fallbacks ordered before the primary value, or `null`/`undefined` to signal removal.
|
|
@@ -1829,11 +1964,12 @@ function normalizeSelectors({ selectors, defaultSelector }) {
|
|
|
1829
1964
|
* @param value - The raw property value to normalize.
|
|
1830
1965
|
* @returns An array of CSS value strings (fallbacks first, primary last), or `null`/`undefined` for removal.
|
|
1831
1966
|
*
|
|
1832
|
-
* @remarks For tuple values `[primary, fallbacks]`, duplicates among fallbacks are removed and the primary value is appended last so CSS cascade uses it as the effective value while older browsers fall back to earlier entries.
|
|
1967
|
+
* @remarks For tuple values `[primary, fallbacks]`, duplicates among fallbacks are removed and the primary value is appended last so CSS cascade uses it as the effective value while older browsers fall back to earlier entries. Numeric values (e.g. `0`) are converted to strings.
|
|
1833
1968
|
*
|
|
1834
1969
|
* @example
|
|
1835
1970
|
* ```ts
|
|
1836
1971
|
* normalizeValue('red') // ['red']
|
|
1972
|
+
* normalizeValue(0) // ['0']
|
|
1837
1973
|
* normalizeValue(['red', ['blue']]) // ['blue', 'red']
|
|
1838
1974
|
* normalizeValue(null) // null
|
|
1839
1975
|
* ```
|
|
@@ -1842,11 +1978,11 @@ function normalizeValue(value) {
|
|
|
1842
1978
|
if (value == null) return value;
|
|
1843
1979
|
if (Array.isArray(value)) {
|
|
1844
1980
|
const [primary, fallbacks] = value;
|
|
1845
|
-
const p = primary.trim();
|
|
1981
|
+
const p = String(primary).trim();
|
|
1846
1982
|
const seen = new Set([p]);
|
|
1847
1983
|
const result = [];
|
|
1848
1984
|
for (const v of fallbacks) {
|
|
1849
|
-
const s = v.trim();
|
|
1985
|
+
const s = String(v).trim();
|
|
1850
1986
|
if (!seen.has(s)) {
|
|
1851
1987
|
seen.add(s);
|
|
1852
1988
|
result.push(s);
|
|
@@ -1855,7 +1991,7 @@ function normalizeValue(value) {
|
|
|
1855
1991
|
result.push(p);
|
|
1856
1992
|
return result;
|
|
1857
1993
|
}
|
|
1858
|
-
return [value.trim()];
|
|
1994
|
+
return [String(value).trim()];
|
|
1859
1995
|
}
|
|
1860
1996
|
/**
|
|
1861
1997
|
* Recursively walks a style definition tree, extracting each CSS property-value pair into a flat list of `ExtractedStyleContent` entries with their full selector chain.
|
|
@@ -1885,18 +2021,24 @@ function normalizeValue(value) {
|
|
|
1885
2021
|
* ```
|
|
1886
2022
|
*/
|
|
1887
2023
|
async function extract({ styleDefinition, levels = [], result = [], defaultSelector, transformSelectors, transformStyleItems, transformStyleDefinitions }) {
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
selector
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
}
|
|
2024
|
+
let scopeSelector;
|
|
2025
|
+
const resolveScopeSelector = async () => {
|
|
2026
|
+
if (scopeSelector == null) {
|
|
2027
|
+
const selector = normalizeSelectors({
|
|
2028
|
+
selectors: await transformSelectors(levels),
|
|
2029
|
+
defaultSelector
|
|
2030
|
+
});
|
|
2031
|
+
if (selector.length === 0 || selector.every((s) => hasAtomicStyleIdPlaceholder(s) === false)) selector.push(defaultSelector);
|
|
2032
|
+
scopeSelector = selector;
|
|
2033
|
+
}
|
|
2034
|
+
return scopeSelector;
|
|
2035
|
+
};
|
|
2036
|
+
for (const definition of await transformStyleDefinitions([styleDefinition])) for (const [k, v] of Object.entries(definition)) if (isPropertyValue(v)) result.push({
|
|
2037
|
+
selector: await resolveScopeSelector(),
|
|
2038
|
+
property: toKebab(k),
|
|
2039
|
+
value: normalizeValue(v)
|
|
2040
|
+
});
|
|
2041
|
+
else if (Array.isArray(v)) for (const styleItem of await transformStyleItems(v)) {
|
|
1900
2042
|
if (typeof styleItem === "string") continue;
|
|
1901
2043
|
await extract({
|
|
1902
2044
|
styleDefinition: styleItem,
|
|
@@ -2100,7 +2242,7 @@ function resolvePlugins(plugins) {
|
|
|
2100
2242
|
* ```ts
|
|
2101
2243
|
* export default defineEnginePlugin({
|
|
2102
2244
|
* name: 'my-plugin',
|
|
2103
|
-
* configureRawConfig: (config) => ({ ...config, important: true }),
|
|
2245
|
+
* configureRawConfig: (config) => ({ ...config, important: { default: true } }),
|
|
2104
2246
|
* })
|
|
2105
2247
|
* ```
|
|
2106
2248
|
*/
|
|
@@ -2110,8 +2252,10 @@ function defineEnginePlugin(plugin) {
|
|
|
2110
2252
|
/* c8 ignore end */
|
|
2111
2253
|
//#endregion
|
|
2112
2254
|
//#region src/plugins/important.ts
|
|
2255
|
+
const TRAILING_IMPORTANT_RE = /!\s*important\s*$/i;
|
|
2113
2256
|
function appendImportant(v) {
|
|
2114
|
-
|
|
2257
|
+
const value = String(v);
|
|
2258
|
+
return TRAILING_IMPORTANT_RE.test(value) ? value : `${value} !important`;
|
|
2115
2259
|
}
|
|
2116
2260
|
function modifyPropertyValue(value) {
|
|
2117
2261
|
if (value == null) return null;
|
|
@@ -2123,7 +2267,7 @@ function modifyPropertyValue(value) {
|
|
|
2123
2267
|
*
|
|
2124
2268
|
* @returns An `EnginePlugin` that intercepts `transformStyleDefinitions` to conditionally append `!important` to every property value.
|
|
2125
2269
|
*
|
|
2126
|
-
* @remarks When `EngineConfig.important.default` is `true`, all property values receive `!important` unless the style definition explicitly sets `__important: false`. Individual style definitions can also opt-in with `__important: true` regardless of the default.
|
|
2270
|
+
* @remarks When `EngineConfig.important.default` is `true`, all property values receive `!important` unless the style definition explicitly sets `__important: false`. Individual style definitions can also opt-in with `__important: true` regardless of the default. An explicit `__important` flag is propagated into nested selector blocks (which may override it with their own explicit flag). The `__shortcut` reference is never modified.
|
|
2127
2271
|
*
|
|
2128
2272
|
* @example
|
|
2129
2273
|
* ```ts
|
|
@@ -2132,6 +2276,16 @@ function modifyPropertyValue(value) {
|
|
|
2132
2276
|
*/
|
|
2133
2277
|
function important() {
|
|
2134
2278
|
let defaultValue;
|
|
2279
|
+
function propagateExplicitFlag(v, flag) {
|
|
2280
|
+
if (Array.isArray(v)) return v.map((item) => typeof item === "object" && item !== null && !Array.isArray(item) ? {
|
|
2281
|
+
__important: flag,
|
|
2282
|
+
...item
|
|
2283
|
+
} : item);
|
|
2284
|
+
return {
|
|
2285
|
+
__important: flag,
|
|
2286
|
+
...v
|
|
2287
|
+
};
|
|
2288
|
+
}
|
|
2135
2289
|
return defineEnginePlugin({
|
|
2136
2290
|
name: "core:important",
|
|
2137
2291
|
rawConfigConfigured(config) {
|
|
@@ -2146,10 +2300,13 @@ function important() {
|
|
|
2146
2300
|
transformStyleDefinitions(styleDefinitions) {
|
|
2147
2301
|
return styleDefinitions.map((styleDefinition) => {
|
|
2148
2302
|
const { __important, ...rest } = styleDefinition;
|
|
2149
|
-
|
|
2303
|
+
const explicit = __important;
|
|
2304
|
+
const important = explicit ?? defaultValue;
|
|
2305
|
+
if (important === false && explicit == null) return rest;
|
|
2150
2306
|
return Object.fromEntries(Object.entries(rest).map(([k, v]) => {
|
|
2151
|
-
if (
|
|
2152
|
-
return [k, v];
|
|
2307
|
+
if (k === "__shortcut") return [k, v];
|
|
2308
|
+
if (isPropertyValue(v)) return [k, important ? modifyPropertyValue(v) : v];
|
|
2309
|
+
return [k, explicit == null ? v : propagateExplicitFlag(v, explicit)];
|
|
2153
2310
|
}));
|
|
2154
2311
|
});
|
|
2155
2312
|
}
|
|
@@ -2195,11 +2352,12 @@ function keyframes() {
|
|
|
2195
2352
|
}
|
|
2196
2353
|
};
|
|
2197
2354
|
engine.keyframes.add(...configList);
|
|
2198
|
-
engine.addPreflight((engine) => {
|
|
2355
|
+
engine.addPreflight((engine, _isFormatted, ctx) => {
|
|
2199
2356
|
const maybeUsedName = /* @__PURE__ */ new Set();
|
|
2200
|
-
engine.store.atomicStyles.forEach(({ content: { property, value } }) => {
|
|
2357
|
+
engine.store.atomicStyles.forEach(({ content: { property, value } }, id) => {
|
|
2358
|
+
if (ctx?.usedAtomicStyleIds != null && ctx.usedAtomicStyleIds.has(id) === false) return;
|
|
2201
2359
|
if (property === "animation-name") {
|
|
2202
|
-
value.forEach((name) => maybeUsedName.
|
|
2360
|
+
value.forEach((name) => addToSet(maybeUsedName, ...name.split(",").map((v) => v.trim())));
|
|
2203
2361
|
return;
|
|
2204
2362
|
}
|
|
2205
2363
|
if (property === "animation") value.forEach((value) => {
|
|
@@ -2211,7 +2369,7 @@ function keyframes() {
|
|
|
2211
2369
|
const maybeUsedKeyframes = Array.from(engine.keyframes.store.values()).filter(({ name, frames, pruneUnused }) => (pruneUnused === false || maybeUsedName.has(name)) && frames != null);
|
|
2212
2370
|
const preflightDefinition = {};
|
|
2213
2371
|
maybeUsedKeyframes.forEach(({ name, frames }) => {
|
|
2214
|
-
preflightDefinition[`@keyframes ${name}`] =
|
|
2372
|
+
preflightDefinition[`@keyframes ${name}`] = frames;
|
|
2215
2373
|
});
|
|
2216
2374
|
return preflightDefinition;
|
|
2217
2375
|
});
|
|
@@ -2268,6 +2426,10 @@ function stripGlobalFlag(re) {
|
|
|
2268
2426
|
var AbstractResolver = class {
|
|
2269
2427
|
/** Cache of previously resolved input-string → result pairs. */
|
|
2270
2428
|
_resolvedResultsMap = /* @__PURE__ */ new Map();
|
|
2429
|
+
/** Negative cache of input strings that matched no rule at all. Retryable-unresolved dynamic results (a matched dynamic rule whose value fn returned nullish) are never stored here. */
|
|
2430
|
+
_unmatchedStrings = /* @__PURE__ */ new Set();
|
|
2431
|
+
/** Index of static rules keyed by their exact match string (first registered rule wins on string collisions), giving O(1) static lookups. */
|
|
2432
|
+
_staticRulesByString = /* @__PURE__ */ new Map();
|
|
2271
2433
|
/** Registry of static rules keyed by their unique key. */
|
|
2272
2434
|
staticRulesMap = /* @__PURE__ */ new Map();
|
|
2273
2435
|
/** Registry of dynamic rules keyed by their unique key. */
|
|
@@ -2286,7 +2448,7 @@ var AbstractResolver = class {
|
|
|
2286
2448
|
* @param rule - The static rule to register.
|
|
2287
2449
|
* @returns `this` for chaining.
|
|
2288
2450
|
*
|
|
2289
|
-
* @remarks Overwrites any existing static rule with the same key.
|
|
2451
|
+
* @remarks Overwrites any existing static rule with the same key. The entire resolution cache is cleared because cached results (including recursively expanded ones) may depend on the previous rule.
|
|
2290
2452
|
*
|
|
2291
2453
|
* @example
|
|
2292
2454
|
* ```ts
|
|
@@ -2295,7 +2457,14 @@ var AbstractResolver = class {
|
|
|
2295
2457
|
*/
|
|
2296
2458
|
addStaticRule(rule) {
|
|
2297
2459
|
log.debug(`Adding static rule: ${rule.key}`);
|
|
2460
|
+
const previous = this.staticRulesMap.get(rule.key);
|
|
2298
2461
|
this.staticRulesMap.set(rule.key, rule);
|
|
2462
|
+
if (previous != null) {
|
|
2463
|
+
if (previous.string !== rule.string) this._reindexStaticRuleString(previous.string);
|
|
2464
|
+
this._reindexStaticRuleString(rule.string);
|
|
2465
|
+
} else if (this._staticRulesByString.has(rule.string) === false) this._staticRulesByString.set(rule.string, rule);
|
|
2466
|
+
this._resolvedResultsMap.clear();
|
|
2467
|
+
this._unmatchedStrings.clear();
|
|
2299
2468
|
return this;
|
|
2300
2469
|
}
|
|
2301
2470
|
/**
|
|
@@ -2304,7 +2473,7 @@ var AbstractResolver = class {
|
|
|
2304
2473
|
* @param key - The key of the static rule to remove.
|
|
2305
2474
|
* @returns `this` for chaining.
|
|
2306
2475
|
*
|
|
2307
|
-
* @remarks Logs a warning if the key does not exist.
|
|
2476
|
+
* @remarks Logs a warning if the key does not exist. The entire resolution cache is cleared because cached results (including recursively expanded ones) may depend on the removed rule.
|
|
2308
2477
|
*
|
|
2309
2478
|
* @example
|
|
2310
2479
|
* ```ts
|
|
@@ -2319,16 +2488,32 @@ var AbstractResolver = class {
|
|
|
2319
2488
|
}
|
|
2320
2489
|
log.debug(`Removing static rule: ${key}`);
|
|
2321
2490
|
this.staticRulesMap.delete(key);
|
|
2322
|
-
this.
|
|
2491
|
+
if (this._staticRulesByString.get(rule.string) === rule) this._reindexStaticRuleString(rule.string);
|
|
2492
|
+
this._resolvedResultsMap.clear();
|
|
2493
|
+
this._unmatchedStrings.clear();
|
|
2323
2494
|
return this;
|
|
2324
2495
|
}
|
|
2325
2496
|
/**
|
|
2497
|
+
* Recomputes the string-index entry for a given match string after a rule mutation.
|
|
2498
|
+
*
|
|
2499
|
+
* @param string - The match string whose index entry should be recomputed.
|
|
2500
|
+
*
|
|
2501
|
+
* @remarks Scans `staticRulesMap` in insertion order so the first registered rule matching the string wins, mirroring the pre-index linear-scan behavior. Removes the entry when no rule matches the string anymore.
|
|
2502
|
+
*/
|
|
2503
|
+
_reindexStaticRuleString(string) {
|
|
2504
|
+
for (const rule of this.staticRulesMap.values()) if (rule.string === string) {
|
|
2505
|
+
this._staticRulesByString.set(string, rule);
|
|
2506
|
+
return;
|
|
2507
|
+
}
|
|
2508
|
+
this._staticRulesByString.delete(string);
|
|
2509
|
+
}
|
|
2510
|
+
/**
|
|
2326
2511
|
* Registers a dynamic rule in the resolver.
|
|
2327
2512
|
*
|
|
2328
2513
|
* @param rule - The dynamic rule to register.
|
|
2329
2514
|
* @returns `this` for chaining.
|
|
2330
2515
|
*
|
|
2331
|
-
* @remarks Overwrites any existing dynamic rule with the same key.
|
|
2516
|
+
* @remarks Overwrites any existing dynamic rule with the same key. The entire resolution cache is cleared because cached results (including recursively expanded ones) may depend on the previous rule.
|
|
2332
2517
|
*
|
|
2333
2518
|
* @example
|
|
2334
2519
|
* ```ts
|
|
@@ -2338,6 +2523,8 @@ var AbstractResolver = class {
|
|
|
2338
2523
|
addDynamicRule(rule) {
|
|
2339
2524
|
log.debug(`Adding dynamic rule: ${rule.key}`);
|
|
2340
2525
|
this.dynamicRulesMap.set(rule.key, rule);
|
|
2526
|
+
this._resolvedResultsMap.clear();
|
|
2527
|
+
this._unmatchedStrings.clear();
|
|
2341
2528
|
return this;
|
|
2342
2529
|
}
|
|
2343
2530
|
/**
|
|
@@ -2346,7 +2533,7 @@ var AbstractResolver = class {
|
|
|
2346
2533
|
* @param key - The key of the dynamic rule to remove.
|
|
2347
2534
|
* @returns `this` for chaining.
|
|
2348
2535
|
*
|
|
2349
|
-
* @remarks
|
|
2536
|
+
* @remarks Logs a warning if the key does not exist. The entire resolution cache is cleared because cached results (including recursively expanded ones) may depend on the removed rule.
|
|
2350
2537
|
*
|
|
2351
2538
|
* @example
|
|
2352
2539
|
* ```ts
|
|
@@ -2354,19 +2541,14 @@ var AbstractResolver = class {
|
|
|
2354
2541
|
* ```
|
|
2355
2542
|
*/
|
|
2356
2543
|
removeDynamicRule(key) {
|
|
2357
|
-
|
|
2358
|
-
if (rule == null) {
|
|
2544
|
+
if (this.dynamicRulesMap.get(key) == null) {
|
|
2359
2545
|
log.warn(`Dynamic rule not found for removal: ${key}`);
|
|
2360
2546
|
return this;
|
|
2361
2547
|
}
|
|
2362
2548
|
log.debug(`Removing dynamic rule: ${key}`);
|
|
2363
|
-
const matchedResolvedStringList = Array.from(this._resolvedResultsMap.keys()).filter((string) => {
|
|
2364
|
-
rule.stringPattern.lastIndex = 0;
|
|
2365
|
-
return rule.stringPattern.test(string);
|
|
2366
|
-
});
|
|
2367
2549
|
this.dynamicRulesMap.delete(key);
|
|
2368
|
-
|
|
2369
|
-
|
|
2550
|
+
this._resolvedResultsMap.clear();
|
|
2551
|
+
this._unmatchedStrings.clear();
|
|
2370
2552
|
return this;
|
|
2371
2553
|
}
|
|
2372
2554
|
/**
|
|
@@ -2375,7 +2557,7 @@ var AbstractResolver = class {
|
|
|
2375
2557
|
* @param string - The input string to resolve.
|
|
2376
2558
|
* @returns The resolved result wrapper, or `null`/`undefined` if no rule matches.
|
|
2377
2559
|
*
|
|
2378
|
-
* @remarks Results are cached for subsequent calls. Invokes `onResolved` after a successful match. Dynamic rule matching is async because `createResolved` may return a `Promise`.
|
|
2560
|
+
* @remarks Results are cached for subsequent calls. Invokes `onResolved` after a successful match. Dynamic rule matching is async because `createResolved` may return a `Promise`. When a dynamic rule's `createResolved` returns `undefined`/`null`, the input is treated as unresolved and no cache entry is stored, so a later resolve call re-invokes the rule.
|
|
2379
2561
|
*
|
|
2380
2562
|
* @example
|
|
2381
2563
|
* ```ts
|
|
@@ -2388,7 +2570,11 @@ var AbstractResolver = class {
|
|
|
2388
2570
|
log.debug(`Resolved from cache: ${string}`);
|
|
2389
2571
|
return existedResult;
|
|
2390
2572
|
}
|
|
2391
|
-
|
|
2573
|
+
if (this._unmatchedStrings.has(string)) {
|
|
2574
|
+
log.debug(`Resolution failed (cached): ${string}`);
|
|
2575
|
+
return;
|
|
2576
|
+
}
|
|
2577
|
+
const staticRule = this._staticRulesByString.get(string);
|
|
2392
2578
|
if (staticRule != null) {
|
|
2393
2579
|
log.debug(`Resolved by static rule: ${staticRule.key}`);
|
|
2394
2580
|
const resolvedResult = { value: staticRule.resolved };
|
|
@@ -2407,13 +2593,19 @@ var AbstractResolver = class {
|
|
|
2407
2593
|
}
|
|
2408
2594
|
}
|
|
2409
2595
|
if (dynamicRule != null && matched != null) {
|
|
2596
|
+
const value = await dynamicRule.createResolved(matched);
|
|
2597
|
+
if (value == null) {
|
|
2598
|
+
log.debug(`Dynamic rule "${dynamicRule.key}" returned no value for "${string}", treating as unresolved (not cached)`);
|
|
2599
|
+
return;
|
|
2600
|
+
}
|
|
2410
2601
|
log.debug(`Resolved by dynamic rule: ${dynamicRule.key}`);
|
|
2411
|
-
const resolvedResult = { value
|
|
2602
|
+
const resolvedResult = { value };
|
|
2412
2603
|
this._resolvedResultsMap.set(string, resolvedResult);
|
|
2413
2604
|
this.onResolved(string, "dynamic", resolvedResult);
|
|
2414
2605
|
return resolvedResult;
|
|
2415
2606
|
}
|
|
2416
2607
|
log.debug(`Resolution failed for: ${string}`);
|
|
2608
|
+
this._unmatchedStrings.add(string);
|
|
2417
2609
|
}
|
|
2418
2610
|
/**
|
|
2419
2611
|
* Updates or creates the cached resolved result for a given input string.
|
|
@@ -2425,10 +2617,11 @@ var AbstractResolver = class {
|
|
|
2425
2617
|
*
|
|
2426
2618
|
* @example
|
|
2427
2619
|
* ```ts
|
|
2428
|
-
* resolver._setResolvedResult('hover', ['
|
|
2620
|
+
* resolver._setResolvedResult('hover', ['$:hover'])
|
|
2429
2621
|
* ```
|
|
2430
2622
|
*/
|
|
2431
2623
|
_setResolvedResult(string, resolved) {
|
|
2624
|
+
this._unmatchedStrings.delete(string);
|
|
2432
2625
|
const resolvedResult = this._resolvedResultsMap.get(string);
|
|
2433
2626
|
if (resolvedResult) {
|
|
2434
2627
|
resolvedResult.value = resolved;
|
|
@@ -2449,7 +2642,7 @@ var AbstractResolver = class {
|
|
|
2449
2642
|
* ```ts
|
|
2450
2643
|
* class SelectorResolver extends RecursiveResolver<string> { }
|
|
2451
2644
|
* const result = await resolver.resolve('hover-focus')
|
|
2452
|
-
* // ['
|
|
2645
|
+
* // ['$:hover', '$:focus'] after recursive expansion
|
|
2453
2646
|
* ```
|
|
2454
2647
|
*/
|
|
2455
2648
|
var RecursiveResolver = class extends AbstractResolver {
|
|
@@ -2485,6 +2678,13 @@ var RecursiveResolver = class extends AbstractResolver {
|
|
|
2485
2678
|
return result;
|
|
2486
2679
|
}
|
|
2487
2680
|
};
|
|
2681
|
+
function createDynamicResolvedFactory(fn) {
|
|
2682
|
+
return async (match) => {
|
|
2683
|
+
const value = await fn(match);
|
|
2684
|
+
if (value == null) return value;
|
|
2685
|
+
return [value].flat(1);
|
|
2686
|
+
};
|
|
2687
|
+
}
|
|
2488
2688
|
/**
|
|
2489
2689
|
* Normalizes a user-supplied rule shorthand into a `ResolvedRuleConfig`, a plain redirect string, or `undefined`.
|
|
2490
2690
|
* @internal
|
|
@@ -2499,61 +2699,44 @@ var RecursiveResolver = class extends AbstractResolver {
|
|
|
2499
2699
|
* - **Tuple**: `[string, T | T[]]` for static rules, `[RegExp, fn, autocomplete?]` for dynamic rules.
|
|
2500
2700
|
* - **Object**: `{ [keyName]: string | RegExp, value: T | fn, autocomplete?: string[] }`.
|
|
2501
2701
|
*
|
|
2702
|
+
* A dynamic rule's value function may return `undefined`/`null` to signal a retryable-unresolved result: the resolver treats the input as unresolved and stores no cache entry, so the rule is re-invoked on a later resolve call.
|
|
2703
|
+
*
|
|
2502
2704
|
* @example
|
|
2503
2705
|
* ```ts
|
|
2504
|
-
* resolveRuleConfig(['hover', '
|
|
2706
|
+
* resolveRuleConfig(['hover', '$:hover'], 'selector')
|
|
2505
2707
|
* // { type: 'static', rule: { key: 'hover', ... }, autocomplete: ['hover'] }
|
|
2506
2708
|
* ```
|
|
2507
2709
|
*/
|
|
2508
2710
|
function resolveRuleConfig(config, keyName) {
|
|
2509
2711
|
if (typeof config === "string") return config;
|
|
2510
|
-
if (Array.isArray(config)) {
|
|
2511
|
-
if (typeof config[0] === "string" && typeof config[1] !== "function") return {
|
|
2512
|
-
type: "static",
|
|
2513
|
-
rule: {
|
|
2514
|
-
key: config[0],
|
|
2515
|
-
string: config[0],
|
|
2516
|
-
resolved: [config[1]].flat(1)
|
|
2517
|
-
},
|
|
2518
|
-
autocomplete: [config[0]]
|
|
2519
|
-
};
|
|
2520
|
-
if (config[0] instanceof RegExp && typeof config[1] === "function") {
|
|
2521
|
-
const fn = config[1];
|
|
2522
|
-
return {
|
|
2523
|
-
type: "dynamic",
|
|
2524
|
-
rule: {
|
|
2525
|
-
key: config[0].source,
|
|
2526
|
-
stringPattern: stripGlobalFlag(config[0]),
|
|
2527
|
-
createResolved: async (match) => [await fn(match)].flat(1)
|
|
2528
|
-
},
|
|
2529
|
-
autocomplete: config[2] != null ? [config[2]].flat(1) : []
|
|
2530
|
-
};
|
|
2531
|
-
}
|
|
2532
|
-
return;
|
|
2533
|
-
}
|
|
2534
2712
|
if (typeof config !== "object" || config === null) return;
|
|
2535
|
-
const
|
|
2536
|
-
|
|
2713
|
+
const { key, value, autocomplete } = Array.isArray(config) ? {
|
|
2714
|
+
key: config[0],
|
|
2715
|
+
value: config[1],
|
|
2716
|
+
autocomplete: config[2]
|
|
2717
|
+
} : {
|
|
2718
|
+
key: config[keyName],
|
|
2719
|
+
value: config.value,
|
|
2720
|
+
autocomplete: config.autocomplete
|
|
2721
|
+
};
|
|
2722
|
+
if (typeof key === "string" && typeof value !== "function") return {
|
|
2537
2723
|
type: "static",
|
|
2538
2724
|
rule: {
|
|
2539
|
-
key
|
|
2540
|
-
string:
|
|
2541
|
-
resolved: [
|
|
2725
|
+
key,
|
|
2726
|
+
string: key,
|
|
2727
|
+
resolved: [value].flat(1)
|
|
2542
2728
|
},
|
|
2543
|
-
autocomplete: [
|
|
2729
|
+
autocomplete: [key]
|
|
2730
|
+
};
|
|
2731
|
+
if (key instanceof RegExp && typeof value === "function") return {
|
|
2732
|
+
type: "dynamic",
|
|
2733
|
+
rule: {
|
|
2734
|
+
key: key.source,
|
|
2735
|
+
stringPattern: stripGlobalFlag(key),
|
|
2736
|
+
createResolved: createDynamicResolvedFactory(value)
|
|
2737
|
+
},
|
|
2738
|
+
autocomplete: autocomplete != null ? [autocomplete].flat(1) : []
|
|
2544
2739
|
};
|
|
2545
|
-
if (configKey instanceof RegExp && typeof config.value === "function") {
|
|
2546
|
-
const fn = config.value;
|
|
2547
|
-
return {
|
|
2548
|
-
type: "dynamic",
|
|
2549
|
-
rule: {
|
|
2550
|
-
key: configKey.source,
|
|
2551
|
-
stringPattern: stripGlobalFlag(configKey),
|
|
2552
|
-
createResolved: async (match) => [await fn(match)].flat(1)
|
|
2553
|
-
},
|
|
2554
|
-
autocomplete: "autocomplete" in config && config.autocomplete != null ? [config.autocomplete].flat(1) : []
|
|
2555
|
-
};
|
|
2556
|
-
}
|
|
2557
2740
|
}
|
|
2558
2741
|
//#endregion
|
|
2559
2742
|
//#region src/plugins/selectors.ts
|
|
@@ -2589,11 +2772,8 @@ function selectors() {
|
|
|
2589
2772
|
engine.appendAutocomplete({ selectors: resolved });
|
|
2590
2773
|
return;
|
|
2591
2774
|
}
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
dynamic: () => engine.selectors.resolver.addDynamicRule(resolved.rule)
|
|
2595
|
-
}[resolved.type];
|
|
2596
|
-
addRule?.();
|
|
2775
|
+
if (resolved.type === "static") engine.selectors.resolver.addStaticRule(resolved.rule);
|
|
2776
|
+
else engine.selectors.resolver.addDynamicRule(resolved.rule);
|
|
2597
2777
|
engine.appendAutocomplete({ selectors: resolved.autocomplete });
|
|
2598
2778
|
});
|
|
2599
2779
|
}
|
|
@@ -2621,7 +2801,7 @@ var SelectorResolver = class extends RecursiveResolver {};
|
|
|
2621
2801
|
*
|
|
2622
2802
|
* @example
|
|
2623
2803
|
* ```ts
|
|
2624
|
-
* const resolved = resolveSelectorConfig(['hover', '
|
|
2804
|
+
* const resolved = resolveSelectorConfig(['hover', '$:hover'])
|
|
2625
2805
|
* ```
|
|
2626
2806
|
*/
|
|
2627
2807
|
function resolveSelectorConfig(config) {
|
|
@@ -2661,11 +2841,8 @@ function shortcuts() {
|
|
|
2661
2841
|
engine.appendAutocomplete({ shortcuts: resolved });
|
|
2662
2842
|
return;
|
|
2663
2843
|
}
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
dynamic: () => engine.shortcuts.resolver.addDynamicRule(resolved.rule)
|
|
2667
|
-
}[resolved.type];
|
|
2668
|
-
addRule?.();
|
|
2844
|
+
if (resolved.type === "static") engine.shortcuts.resolver.addStaticRule(resolved.rule);
|
|
2845
|
+
else engine.shortcuts.resolver.addDynamicRule(resolved.rule);
|
|
2669
2846
|
engine.appendAutocomplete({ shortcuts: resolved.autocomplete });
|
|
2670
2847
|
});
|
|
2671
2848
|
}
|
|
@@ -2695,10 +2872,14 @@ function shortcuts() {
|
|
|
2695
2872
|
const result = [];
|
|
2696
2873
|
for (const styleDefinition of styleDefinitions) if ("__shortcut" in styleDefinition) {
|
|
2697
2874
|
const { __shortcut, ...rest } = styleDefinition;
|
|
2875
|
+
const explicitImportant = rest.__important ?? null;
|
|
2698
2876
|
const applied = [];
|
|
2699
2877
|
for (const shortcut of __shortcut == null ? [] : [__shortcut].flat(1)) {
|
|
2700
2878
|
const resolved = (await engine.shortcuts.resolver.resolve(shortcut)).filter(isNotString);
|
|
2701
|
-
applied.push(...resolved)
|
|
2879
|
+
applied.push(...explicitImportant == null ? resolved : resolved.map((definition) => ({
|
|
2880
|
+
__important: explicitImportant,
|
|
2881
|
+
...definition
|
|
2882
|
+
})));
|
|
2702
2883
|
}
|
|
2703
2884
|
result.push(...applied, rest);
|
|
2704
2885
|
} else result.push(styleDefinition);
|
|
@@ -2758,18 +2939,22 @@ function variables() {
|
|
|
2758
2939
|
rawVariables.forEach((variables) => engine.variables.add(variables));
|
|
2759
2940
|
engine.addPreflight({
|
|
2760
2941
|
id: "core:variables",
|
|
2761
|
-
preflight: async (engine) => {
|
|
2942
|
+
preflight: async (engine, isFormatted, ctx) => {
|
|
2762
2943
|
const used = /* @__PURE__ */ new Set();
|
|
2763
|
-
engine.store.atomicStyles.forEach(({ content: { value } }) => {
|
|
2944
|
+
engine.store.atomicStyles.forEach(({ content: { value } }, id) => {
|
|
2945
|
+
if (ctx?.usedAtomicStyleIds != null && ctx.usedAtomicStyleIds.has(id) === false) return;
|
|
2764
2946
|
value.flatMap(extractUsedVarNames).forEach((name) => used.add(normalizeVariableName(name)));
|
|
2765
2947
|
});
|
|
2766
2948
|
const otherPreflights = engine.config.preflights.filter((p) => p.id !== "core:variables");
|
|
2767
|
-
(await Promise.all(otherPreflights.map(({ fn }) =>
|
|
2949
|
+
(await Promise.all(otherPreflights.map(({ fn }) => engine.invokePreflight(fn, isFormatted, ctx).catch(() => null)))).forEach((result) => {
|
|
2768
2950
|
if (result == null) return;
|
|
2769
2951
|
extractUsedVarNamesFromPreflightResult(result).forEach((name) => used.add(name));
|
|
2770
2952
|
});
|
|
2771
|
-
const varMap =
|
|
2772
|
-
for (const [name,
|
|
2953
|
+
const varMap = engine.variables.store;
|
|
2954
|
+
for (const [name, entries] of varMap.entries()) {
|
|
2955
|
+
if (used.has(name)) continue;
|
|
2956
|
+
if (safeSet.has(name) || entries.some((entry) => entry.pruneUnused === false)) used.add(name);
|
|
2957
|
+
}
|
|
2773
2958
|
const queue = Array.from(used);
|
|
2774
2959
|
while (queue.length > 0) {
|
|
2775
2960
|
const name = queue.pop();
|
|
@@ -2816,9 +3001,6 @@ function mergeVariablesDefinition(target, source) {
|
|
|
2816
3001
|
}
|
|
2817
3002
|
return target;
|
|
2818
3003
|
}
|
|
2819
|
-
function isPlainObjectRecord(value) {
|
|
2820
|
-
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
2821
|
-
}
|
|
2822
3004
|
function createResolveVariablesFn({ pruneUnused: defaultPruneUnused = true } = {}) {
|
|
2823
3005
|
function _resolveVariables(variables, levels, result) {
|
|
2824
3006
|
for (const [key, value] of Object.entries(variables)) if (key.startsWith("--")) {
|
|
@@ -2857,7 +3039,7 @@ function resolveAutocompleteValueTargets({ asValueOf }) {
|
|
|
2857
3039
|
if (targets.has("*")) return ["*"];
|
|
2858
3040
|
return [...targets];
|
|
2859
3041
|
}
|
|
2860
|
-
const VAR_NAME_RE = /var\((--[\w-]+)/g;
|
|
3042
|
+
const VAR_NAME_RE = /var\(\s*(--[\w-]+)/g;
|
|
2861
3043
|
/**
|
|
2862
3044
|
* Extracts all CSS variable names referenced via `var(--*)` calls in a string.
|
|
2863
3045
|
*
|
|
@@ -2977,11 +3159,11 @@ const DEFAULT_LAYERS = {
|
|
|
2977
3159
|
async function createEngine(config = {}) {
|
|
2978
3160
|
log.debug("Creating engine with config:", config);
|
|
2979
3161
|
const corePlugins = [
|
|
2980
|
-
important(),
|
|
2981
3162
|
variables(),
|
|
2982
3163
|
keyframes(),
|
|
2983
3164
|
selectors(),
|
|
2984
|
-
shortcuts()
|
|
3165
|
+
shortcuts(),
|
|
3166
|
+
important()
|
|
2985
3167
|
];
|
|
2986
3168
|
log.debug("Core plugins loaded:", corePlugins.length);
|
|
2987
3169
|
const plugins = resolvePlugins([...corePlugins, ...config.plugins || []]);
|
|
@@ -3027,6 +3209,12 @@ var Engine = class {
|
|
|
3027
3209
|
/** The engine's runtime store holding registered atomic styles and their ID mappings. */
|
|
3028
3210
|
store = createEngineStore();
|
|
3029
3211
|
/**
|
|
3212
|
+
* Absolute paths of external files this engine's config depends on (e.g. token files loaded by plugins).
|
|
3213
|
+
*
|
|
3214
|
+
* @remarks Plugins register paths via `addConfigDependency` during `configureEngine`. Integration layers (e.g. the unplugin) watch these files and re-create the engine when they change.
|
|
3215
|
+
*/
|
|
3216
|
+
configDependencies = /* @__PURE__ */ new Set();
|
|
3217
|
+
/**
|
|
3030
3218
|
* Creates an engine instance from a resolved configuration.
|
|
3031
3219
|
*
|
|
3032
3220
|
* @param config - The fully resolved engine configuration.
|
|
@@ -3048,6 +3236,45 @@ var Engine = class {
|
|
|
3048
3236
|
});
|
|
3049
3237
|
}
|
|
3050
3238
|
/**
|
|
3239
|
+
* Invokes a preflight function, memoizing the result in the given render-pass context.
|
|
3240
|
+
*
|
|
3241
|
+
* @param fn - The preflight function to invoke.
|
|
3242
|
+
* @param isFormatted - Whether the preflight should produce formatted output.
|
|
3243
|
+
* @param ctx - The render-pass context created by `renderPreflights`. When provided, each function executes at most once per pass and the context is forwarded to the preflight function.
|
|
3244
|
+
* @returns A promise of the preflight result.
|
|
3245
|
+
*
|
|
3246
|
+
* @remarks Within one render pass each function executes at most once; concurrent callers (e.g. the variables pruning preflight scanning other preflights for `var()` usage) share the same promise. The memoization is scoped to `ctx`, so overlapping `renderPreflights` calls never interfere with each other. Without a context the function is invoked directly without caching — this is a deliberate change from the previous instance-scoped memoization, which could not tell overlapping passes apart. A preflight that inspects other preflights must therefore forward the `ctx` it receives (its third argument); calling with only `(fn, isFormatted)` from inside a pass runs `fn` again instead of reusing the pass result.
|
|
3247
|
+
*
|
|
3248
|
+
* @example
|
|
3249
|
+
* ```ts
|
|
3250
|
+
* const result = await engine.invokePreflight(preflight.fn, false, ctx)
|
|
3251
|
+
* ```
|
|
3252
|
+
*/
|
|
3253
|
+
invokePreflight(fn, isFormatted, ctx) {
|
|
3254
|
+
if (ctx == null) return Promise.resolve().then(() => fn(this, isFormatted));
|
|
3255
|
+
let invocation = ctx.invocations.get(fn);
|
|
3256
|
+
if (invocation == null) {
|
|
3257
|
+
invocation = Promise.resolve().then(() => fn(this, isFormatted, ctx));
|
|
3258
|
+
ctx.invocations.set(fn, invocation);
|
|
3259
|
+
}
|
|
3260
|
+
return invocation;
|
|
3261
|
+
}
|
|
3262
|
+
/**
|
|
3263
|
+
* Registers an external file path as a config dependency of this engine.
|
|
3264
|
+
*
|
|
3265
|
+
* @param path - The file path (ideally absolute) the current config was derived from.
|
|
3266
|
+
*
|
|
3267
|
+
* @remarks Call from a plugin (typically in `configureEngine`) after loading data from disk. Integration layers watch registered paths and rebuild the engine when any of them changes.
|
|
3268
|
+
*
|
|
3269
|
+
* @example
|
|
3270
|
+
* ```ts
|
|
3271
|
+
* engine.addConfigDependency('/project/design.md')
|
|
3272
|
+
* ```
|
|
3273
|
+
*/
|
|
3274
|
+
addConfigDependency(path) {
|
|
3275
|
+
this.configDependencies.add(path);
|
|
3276
|
+
}
|
|
3277
|
+
/**
|
|
3051
3278
|
* Fires the `preflightUpdated` hook to notify plugins that preflight content has changed.
|
|
3052
3279
|
*
|
|
3053
3280
|
*
|
|
@@ -3145,7 +3372,7 @@ var Engine = class {
|
|
|
3145
3372
|
* Processes style items through the plugin pipeline and registers the resulting atomic styles in the store.
|
|
3146
3373
|
*
|
|
3147
3374
|
* @param itemList - Style items to process: string references (shortcuts) and/or style definition objects.
|
|
3148
|
-
* @returns An array
|
|
3375
|
+
* @returns An array containing any unresolved string references first, followed by atomic style IDs in resolution order.
|
|
3149
3376
|
*
|
|
3150
3377
|
* @remarks Runs `transformStyleItems` and `extractStyleDefinition` hooks, resolves each extracted content into an atomic style, deduplicates by base key, and fires `atomicStyleAdded` for new entries.
|
|
3151
3378
|
*
|
|
@@ -3184,20 +3411,27 @@ var Engine = class {
|
|
|
3184
3411
|
* Renders all registered preflight definitions into a CSS string.
|
|
3185
3412
|
*
|
|
3186
3413
|
* @param isFormatted - Whether to produce human-readable CSS with newlines and indentation.
|
|
3414
|
+
* @param options - Optional render-pass options.
|
|
3415
|
+
* @param options.usedAtomicStyleIds - Atomic style IDs considered "in use" for this pass. When provided, pruning preflights (variables, keyframes) only consider these atomic styles instead of the whole append-only store; when omitted, all stored atomic styles are considered.
|
|
3187
3416
|
* @returns The rendered preflight CSS, including `@import` statements, optional `@layer` wrappers, and all preflight content.
|
|
3188
3417
|
*
|
|
3189
|
-
* @remarks Evaluates each preflight function, groups output by layer, wraps unlayered preflights in the default preflights layer (when present), and respects configured layer ordering.
|
|
3418
|
+
* @remarks Evaluates each preflight function, groups output by layer, wraps unlayered preflights in the default preflights layer (when present), and respects configured layer ordering. Each call creates its own `PreflightContext`, so within one pass each preflight function executes exactly once even when passes overlap.
|
|
3190
3419
|
*
|
|
3191
3420
|
* @example
|
|
3192
3421
|
* ```ts
|
|
3193
3422
|
* const css = await engine.renderPreflights(true)
|
|
3423
|
+
* const scoped = await engine.renderPreflights(true, { usedAtomicStyleIds: ['pk-a'] })
|
|
3194
3424
|
* ```
|
|
3195
3425
|
*/
|
|
3196
|
-
async renderPreflights(isFormatted) {
|
|
3426
|
+
async renderPreflights(isFormatted, options = {}) {
|
|
3197
3427
|
log.debug("Rendering preflights...");
|
|
3198
3428
|
const lineEnd = isFormatted ? "\n" : "";
|
|
3429
|
+
const ctx = {
|
|
3430
|
+
invocations: /* @__PURE__ */ new Map(),
|
|
3431
|
+
usedAtomicStyleIds: options.usedAtomicStyleIds == null ? void 0 : new Set(options.usedAtomicStyleIds)
|
|
3432
|
+
};
|
|
3199
3433
|
const rendered = (await Promise.all(this.config.preflights.map(async ({ layer, fn }) => {
|
|
3200
|
-
const result = await fn
|
|
3434
|
+
const result = await this.invokePreflight(fn, isFormatted, ctx);
|
|
3201
3435
|
return {
|
|
3202
3436
|
layer,
|
|
3203
3437
|
css: (typeof result === "string" ? result : await renderPreflightDefinition({
|
|
@@ -3394,15 +3628,10 @@ function groupAtomicStylesByLayer({ styles, layerOrder, defaultUtilitiesLayer })
|
|
|
3394
3628
|
layerGroups
|
|
3395
3629
|
};
|
|
3396
3630
|
}
|
|
3397
|
-
function
|
|
3398
|
-
if (typeof p !== "object" || p === null) return false;
|
|
3399
|
-
const record = p;
|
|
3400
|
-
return typeof record.layer === "string" && record.preflight !== void 0;
|
|
3401
|
-
}
|
|
3402
|
-
function isWithId(p) {
|
|
3631
|
+
function hasPreflightWrapper(p, key) {
|
|
3403
3632
|
if (typeof p !== "object" || p === null) return false;
|
|
3404
3633
|
const record = p;
|
|
3405
|
-
return typeof record
|
|
3634
|
+
return typeof record[key] === "string" && record.preflight !== void 0;
|
|
3406
3635
|
}
|
|
3407
3636
|
/**
|
|
3408
3637
|
* Normalizes a `Preflight` input into a `ResolvedPreflight` by extracting optional `layer` and `id` wrappers.
|
|
@@ -3421,11 +3650,11 @@ function isWithId(p) {
|
|
|
3421
3650
|
function resolvePreflight(preflight) {
|
|
3422
3651
|
let layer;
|
|
3423
3652
|
let id;
|
|
3424
|
-
if (
|
|
3653
|
+
if (hasPreflightWrapper(preflight, "layer")) {
|
|
3425
3654
|
layer = preflight.layer;
|
|
3426
3655
|
preflight = preflight.preflight;
|
|
3427
3656
|
}
|
|
3428
|
-
if (
|
|
3657
|
+
if (hasPreflightWrapper(preflight, "id")) {
|
|
3429
3658
|
id = preflight.id;
|
|
3430
3659
|
preflight = preflight.preflight;
|
|
3431
3660
|
}
|
|
@@ -3543,9 +3772,9 @@ function renderAtomicStylesCss({ atomicStyles, isPreview, isFormatted }) {
|
|
|
3543
3772
|
const blocks = /* @__PURE__ */ new Map();
|
|
3544
3773
|
atomicStyles.forEach(({ id, content: { selector: rawSelector, property, value } }) => {
|
|
3545
3774
|
const { selector } = splitLayerSelector(rawSelector);
|
|
3546
|
-
if (selector.some((s) => s
|
|
3775
|
+
if (selector.some((s) => hasAtomicStyleIdPlaceholder(s)) === false || value == null) return;
|
|
3547
3776
|
const renderObject = {
|
|
3548
|
-
selector: isPreview ? selector : selector.map((s) => s
|
|
3777
|
+
selector: isPreview ? selector : selector.map((s) => replaceAtomicStyleIdPlaceholder(s, id)),
|
|
3549
3778
|
properties: value.map((v) => ({
|
|
3550
3779
|
property,
|
|
3551
3780
|
value: v
|
|
@@ -3720,4 +3949,4 @@ function defineEngineConfig(config) {
|
|
|
3720
3949
|
}
|
|
3721
3950
|
/* c8 ignore end */
|
|
3722
3951
|
//#endregion
|
|
3723
|
-
export { appendAutocomplete, createEngine, createLogger, defineEngineConfig, defineEnginePlugin, log, renderCSSStyleBlocks, sortLayerNames };
|
|
3952
|
+
export { appendAutocomplete, createEngine, createLogger, defineEngineConfig, defineEnginePlugin, escapeRegExp, isPlainObjectRecord, log, renderCSSStyleBlocks, sortLayerNames };
|